From bbd0b3478456beeec3ce42c8f0a54cf99bf5f6a6 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 29 Mar 2022 15:45:21 +0200 Subject: [PATCH 01/55] rename funding_locked -> channel_ready --- .../acinq/eclair/balance/BalanceActor.scala | 2 +- .../acinq/eclair/balance/CheckBalance.scala | 6 ++-- .../fr/acinq/eclair/balance/Monitoring.scala | 2 +- .../fr/acinq/eclair/channel/ChannelData.scala | 8 ++--- .../fr/acinq/eclair/channel/Commitments.scala | 2 +- .../fr/acinq/eclair/channel/Helpers.scala | 2 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 32 +++++++++---------- .../channel/fsm/ChannelOpenSingleFunder.scala | 22 ++++++------- .../fr/acinq/eclair/db/DbEventHandler.scala | 2 +- .../acinq/eclair/json/JsonSerializers.scala | 2 +- .../channel/version0/ChannelCodecs0.scala | 14 ++++---- .../channel/version1/ChannelCodecs1.scala | 8 ++--- .../channel/version2/ChannelCodecs2.scala | 8 ++--- .../channel/version3/ChannelCodecs3.scala | 8 ++--- .../eclair/wire/protocol/ChannelTlv.scala | 6 ++-- .../protocol/LightningMessageCodecs.scala | 6 ++-- .../wire/protocol/LightningMessageTypes.scala | 4 +-- .../scala/fr/acinq/eclair/TestDatabases.scala | 2 +- .../fr/acinq/eclair/channel/RestoreSpec.scala | 6 ++-- .../ChannelStateTestsHelperMethods.scala | 4 +-- ...ala => WaitForChannelReadyStateSpec.scala} | 24 +++++++------- .../c/WaitForFundingConfirmedStateSpec.scala | 12 +++---- .../channel/states/e/OfflineStateSpec.scala | 12 +++---- .../integration/ChannelIntegrationSpec.scala | 8 ++--- .../acinq/eclair/io/PeerConnectionSpec.scala | 2 +- .../eclair/json/JsonSerializersSpec.scala | 2 +- .../protocol/LightningMessageCodecsSpec.scala | 2 +- 27 files changed, 104 insertions(+), 104 deletions(-) rename eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/{WaitForFundingLockedStateSpec.scala => WaitForChannelReadyStateSpec.scala} (85%) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/balance/BalanceActor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/balance/BalanceActor.scala index 966ae3024d..3924d70f34 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/balance/BalanceActor.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/balance/BalanceActor.scala @@ -105,7 +105,7 @@ private class BalanceActor(context: ActorContext[Command], Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.OnchainConfirmed).update(result.onChain.confirmed.toMilliBtc.toDouble) Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.OnchainUnconfirmed).update(result.onChain.unconfirmed.toMilliBtc.toDouble) Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.waitForFundingConfirmed).update(result.offChain.waitForFundingConfirmed.toMilliBtc.toDouble) - Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.waitForFundingLocked).update(result.offChain.waitForFundingLocked.toMilliBtc.toDouble) + Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.waitForChannelReady).update(result.offChain.waitForChannelReady.toMilliBtc.toDouble) Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.normal).update(result.offChain.normal.total.toMilliBtc.toDouble) Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.shutdown).update(result.offChain.shutdown.total.toMilliBtc.toDouble) Metrics.GlobalBalanceDetailed.withTag(Tags.BalanceType, Tags.BalanceTypes.Offchain).withTag(Tags.OffchainState, Tags.OffchainStates.closingLocal).update(result.offChain.closing.localCloseBalance.total.toMilliBtc.toDouble) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala index 8dd65b359c..7d08a54d09 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/balance/CheckBalance.scala @@ -77,13 +77,13 @@ object CheckBalance { * The overall balance among all channels in all states. */ case class OffChainBalance(waitForFundingConfirmed: Btc = 0.sat, - waitForFundingLocked: Btc = 0.sat, + waitForChannelReady: Btc = 0.sat, normal: MainAndHtlcBalance = MainAndHtlcBalance(), shutdown: MainAndHtlcBalance = MainAndHtlcBalance(), negotiating: Btc = 0.sat, closing: ClosingBalance = ClosingBalance(), waitForPublishFutureCommitment: Btc = 0.sat) { - val total: Btc = waitForFundingConfirmed + waitForFundingLocked + normal.total + shutdown.total + negotiating + closing.total + waitForPublishFutureCommitment + val total: Btc = waitForFundingConfirmed + waitForChannelReady + normal.total + shutdown.total + negotiating + closing.total + waitForPublishFutureCommitment } def updateMainBalance(localCommit: LocalCommit): Btc => Btc = { v: Btc => @@ -201,7 +201,7 @@ object CheckBalance { channels .foldLeft(OffChainBalance()) { case (r, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => r.modify(_.waitForFundingConfirmed).using(updateMainBalance(d.commitments.localCommit)) - case (r, d: DATA_WAIT_FOR_FUNDING_LOCKED) => r.modify(_.waitForFundingLocked).using(updateMainBalance(d.commitments.localCommit)) + case (r, d: DATA_WAIT_FOR_CHANNEL_READY) => r.modify(_.waitForChannelReady).using(updateMainBalance(d.commitments.localCommit)) case (r, d: DATA_NORMAL) => r.modify(_.normal).using(updateMainAndHtlcBalance(d.commitments, knownPreimages)) case (r, d: DATA_SHUTDOWN) => r.modify(_.shutdown).using(updateMainAndHtlcBalance(d.commitments, knownPreimages)) case (r, d: DATA_NEGOTIATING) => r.modify(_.negotiating).using(updateMainBalance(d.commitments.localCommit)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/balance/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/balance/Monitoring.scala index cd672f27a4..0fb652c107 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/balance/Monitoring.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/balance/Monitoring.scala @@ -44,7 +44,7 @@ object Monitoring { object OffchainStates { val waitForFundingConfirmed = "waitForFundingConfirmed" - val waitForFundingLocked = "waitForFundingLocked" + val waitForChannelReady = "waitForChannelReady" val normal = "normal" val shutdown = "shutdown" val negotiating = "negotiating" diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index b77a4c1797..a2d6a147da 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, ShortChannelId, UInt64} import scodec.bits.ByteVector @@ -53,7 +53,7 @@ case object WAIT_FOR_FUNDING_INTERNAL extends ChannelState case object WAIT_FOR_FUNDING_CREATED extends ChannelState case object WAIT_FOR_FUNDING_SIGNED extends ChannelState case object WAIT_FOR_FUNDING_CONFIRMED extends ChannelState -case object WAIT_FOR_FUNDING_LOCKED extends ChannelState +case object WAIT_FOR_CHANNEL_READY extends ChannelState case object NORMAL extends ChannelState case object SHUTDOWN extends ChannelState case object NEGOTIATING extends ChannelState @@ -422,9 +422,9 @@ final case class DATA_WAIT_FOR_FUNDING_SIGNED(channelId: ByteVector32, final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, fundingTx: Option[Transaction], waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm - deferred: Option[FundingLocked], + deferred: Option[ChannelReady], lastSent: Either[FundingCreated, FundingSigned]) extends PersistentChannelData -final case class DATA_WAIT_FOR_FUNDING_LOCKED(commitments: Commitments, shortChannelId: ShortChannelId, lastSent: FundingLocked) extends PersistentChannelData +final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, shortChannelId: ShortChannelId, lastSent: ChannelReady) extends PersistentChannelData final case class DATA_NORMAL(commitments: Commitments, shortChannelId: ShortChannelId, buried: Boolean, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 00d0b26989..2294a09a6c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -915,7 +915,7 @@ object Commitments { case _: CommitSig => s"sig" case _: RevokeAndAck => s"rev" case _: Error => s"err" - case _: FundingLocked => s"funding_locked" + case _: ChannelReady => s"channel_ready" case _ => "???" } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index bde048e81b..4a97f09160 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -56,7 +56,7 @@ object Helpers { remoteParams = data.commitments.remoteParams.copy(initFeatures = remoteInit.features)) data match { case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = commitments1) - case d: DATA_WAIT_FOR_FUNDING_LOCKED => d.copy(commitments = commitments1) + case d: DATA_WAIT_FOR_CHANNEL_READY => d.copy(commitments = commitments1) case d: DATA_NORMAL => d.copy(commitments = commitments1) case d: DATA_SHUTDOWN => d.copy(commitments = commitments1) case d: DATA_NEGOTIATING => d.copy(commitments = commitments1) 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..faa6c067ec 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 @@ -714,7 +714,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d) - case Event(_: FundingLocked, _: DATA_NORMAL) => stay() // will happen after a reconnection if no updates were ever committed to the channel + case Event(_: ChannelReady, _: DATA_NORMAL) => stay() // will happen after a reconnection if no updates were ever committed to the channel }) @@ -1316,12 +1316,12 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, minDepth) goto(WAIT_FOR_FUNDING_CONFIRMED) - case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_LOCKED) => - log.debug("re-sending fundingLocked") + case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_CHANNEL_READY) => + log.debug("re-sending channelReady") val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) - val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) - goto(WAIT_FOR_FUNDING_LOCKED) sending fundingLocked + val channelReady = ChannelReady(d.commitments.channelId, nextPerCommitmentPoint) + goto(WAIT_FOR_CHANNEL_READY) sending channelReady case Event(channelReestablish: ChannelReestablish, d: DATA_NORMAL) => Syncing.checkSync(keyManager, d, channelReestablish) match { @@ -1332,12 +1332,12 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // normal case, our data is up-to-date if (channelReestablish.nextLocalCommitmentNumber == 1 && d.commitments.localCommit.index == 0) { - // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit funding_locked, otherwise it MUST NOT - log.debug("re-sending fundingLocked") + // If next_local_commitment_number is 1 in both the channel_reestablish it sent and received, then the node MUST retransmit channel_ready, otherwise it MUST NOT + log.debug("re-sending channelReady") val channelKeyPath = keyManager.keyPath(d.commitments.localParams, d.commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) - val fundingLocked = FundingLocked(d.commitments.channelId, nextPerCommitmentPoint) - sendQueue = sendQueue :+ fundingLocked + val channelReady = ChannelReady(d.commitments.channelId, nextPerCommitmentPoint) + sendQueue = sendQueue :+ channelReady } // we may need to retransmit updates and/or commit_sig and/or revocation @@ -1441,14 +1441,14 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } // This handler is a workaround for an issue in lnd: starting with versions 0.10 / 0.11, they sometimes fail to send - // a channel_reestablish when reconnecting a channel that recently got confirmed, and instead send a funding_locked + // a channel_reestablish when reconnecting a channel that recently got confirmed, and instead send a channel_ready // first and then go silent. This is due to a race condition on their side, so we trigger a reconnection, hoping that // we will eventually receive their channel_reestablish. - case Event(_: FundingLocked, d) => - log.warning("received funding_locked before channel_reestablish (known lnd bug): disconnecting...") + case Event(_: ChannelReady, d) => + log.warning("received channel_ready before channel_reestablish (known lnd bug): disconnecting...") // NB: we use a small delay to ensure we've sent our warning before disconnecting. context.system.scheduler.scheduleOnce(2 second, peer, Peer.Disconnect(remoteNodeId)) - stay() sending Warning(d.channelId, "spec violation: you sent funding_locked before channel_reestablish") + stay() sending Warning(d.channelId, "spec violation: you sent channel_ready before channel_reestablish") // This handler is a workaround for an issue in lnd similar to the one above: they sometimes send announcement_signatures // before channel_reestablish, which is a minor spec violation. It doesn't halt the channel, we can simply postpone @@ -1615,7 +1615,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val val emitEvent_opt: Option[EmitLocalChannelEvent] = (state, nextState, stateData, nextStateData) match { case (WAIT_FOR_INIT_INTERNAL, OFFLINE, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate(d)) - case (WAIT_FOR_FUNDING_LOCKED, NORMAL, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate(d)) + case (WAIT_FOR_CHANNEL_READY, NORMAL, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate(d)) case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) case (SYNCING, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) case (NORMAL, OFFLINE, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) @@ -1637,8 +1637,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val (stateData, nextStateData) match { // NORMAL->NORMAL, NORMAL->OFFLINE, SYNCING->NORMAL case (d1: DATA_NORMAL, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = Some(d1.channelUpdate), d2) - // WAIT_FOR_FUNDING_LOCKED->NORMAL - case (_: DATA_WAIT_FOR_FUNDING_LOCKED, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = None, d2) + // WAIT_FOR_CHANNEL_READY->NORMAL + case (_: DATA_WAIT_FOR_CHANNEL_READY, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = None, d2) case _ => () } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 97f71b18fc..1995fbd470 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.TxOwner import fr.acinq.eclair.transactions.{Scripts, Transactions} -import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelTlv, Error, FundingCreated, FundingLocked, FundingSigned, OpenChannel, TlvStream} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelTlv, Error, FundingCreated, ChannelReady, FundingSigned, OpenChannel, TlvStream} import fr.acinq.eclair.{Features, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} import scodec.bits.ByteVector @@ -342,8 +342,8 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions { - case Event(msg: FundingLocked, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => - log.info(s"received their FundingLocked, deferring message") + case Event(msg: ChannelReady, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => + log.info(s"received their ChannelReady, deferring message") stay() using d.copy(deferred = Some(msg)) // no need to store, they will re-send if we get disconnected case Event(WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTx), d@DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, _, _, deferred, _)) => @@ -355,13 +355,13 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(TransactionConfirmed(commitments.channelId, remoteNodeId, fundingTx)) val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) - val fundingLocked = FundingLocked(commitments.channelId, nextPerCommitmentPoint) + val channelReady = ChannelReady(commitments.channelId, nextPerCommitmentPoint) deferred.foreach(self ! _) // this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel // as soon as it reaches NORMAL state, and before it is announced on the network // (this id might be updated when the funding tx gets deeply buried, if there was a reorg in the meantime) val shortChannelId = ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt) - goto(WAIT_FOR_FUNDING_LOCKED) using DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, fundingLocked) storing() sending fundingLocked + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortChannelId, channelReady) storing() sending channelReady case Failure(t) => log.error(t, s"rejecting channel with invalid funding tx: ${fundingTx.bin}") goto(CLOSED) @@ -396,8 +396,8 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => handleRemoteError(e, d) }) - when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions { - case Event(FundingLocked(_, nextPerCommitmentPoint, _), d@DATA_WAIT_FOR_FUNDING_LOCKED(commitments, shortChannelId, _)) => + when(WAIT_FOR_CHANNEL_READY)(handleExceptions { + case Event(ChannelReady(_, nextPerCommitmentPoint, _), d@DATA_WAIT_FOR_CHANNEL_READY(commitments, shortChannelId, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId, None)) @@ -408,18 +408,18 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.scheduler.scheduleWithFixedDelay(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, delay = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh)) goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None, None) storing() - case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel => + case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_CHANNEL_READY) if d.commitments.announceChannel => log.debug("received remote announcement signatures, delaying") // we may receive their announcement sigs before our watcher notifies us that the channel has reached min_conf (especially during testing when blocks are generated in bulk) // note: no need to persist their message, in case of disconnection they will resend it context.system.scheduler.scheduleOnce(2 seconds, self, remoteAnnSigs) stay() - case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_FUNDING_LOCKED) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d) + case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_CHANNEL_READY) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d) - case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_FUNDING_LOCKED) => handleInformationLeak(tx, d) + case Event(WatchFundingSpentTriggered(tx), d: DATA_WAIT_FOR_CHANNEL_READY) => handleInformationLeak(tx, d) - case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_LOCKED) => handleRemoteError(e, d) + case Event(e: Error, d: DATA_WAIT_FOR_CHANNEL_READY) => handleRemoteError(e, d) }) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala index e112fea7b6..a1f63f57ca 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/DbEventHandler.scala @@ -108,7 +108,7 @@ class DbEventHandler(nodeParams: NodeParams) extends Actor with DiagnosticActorL case e: ChannelStateChanged => // NB: order matters! e match { - case ChannelStateChanged(_, channelId, _, remoteNodeId, WAIT_FOR_FUNDING_LOCKED, NORMAL, Some(commitments: Commitments)) => + case ChannelStateChanged(_, channelId, _, remoteNodeId, WAIT_FOR_CHANNEL_READY, NORMAL, Some(commitments: Commitments)) => ChannelMetrics.ChannelLifecycleEvents.withTag(ChannelTags.Event, ChannelTags.Events.Created).increment() val event = ChannelEvent.EventType.Created auditDb.add(ChannelEvent(channelId, remoteNodeId, commitments.capacity, commitments.localParams.isInitiator, !commitments.announceChannel, event)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 5c44a25968..4a7c91b160 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -495,7 +495,7 @@ object CustomTypeHints { classOf[DATA_WAIT_FOR_FUNDING_INTERNAL], classOf[DATA_WAIT_FOR_FUNDING_CREATED], classOf[DATA_WAIT_FOR_FUNDING_SIGNED], - classOf[DATA_WAIT_FOR_FUNDING_LOCKED], + classOf[DATA_WAIT_FOR_CHANNEL_READY], classOf[DATA_WAIT_FOR_FUNDING_CONFIRMED], classOf[DATA_NORMAL], classOf[DATA_SHUTDOWN], diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index fa23ee1f42..976b02dad2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -349,30 +349,30 @@ private[channel] object ChannelCodecs0 { ("signature" | bytes64) :: ("tlvStream" | provide(TlvStream.empty[FundingSignedTlv]))).as[FundingSigned] - val fundingLockedCodec: Codec[FundingLocked] = ( + val channelReadyCodec: Codec[ChannelReady] = ( ("channelId" | bytes32) :: ("nextPerCommitmentPoint" | publicKey) :: - ("tlvStream" | provide(TlvStream.empty[FundingLockedTlv]))).as[FundingLocked] + ("tlvStream" | provide(TlvStream.empty[ChannelReadyTlv]))).as[ChannelReady] // this is a decode-only codec compatible with versions 997acee and below, with placeholders for new fields val DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentsCodec) :: ("fundingTx" | provide[Option[Transaction]](None)) :: ("waitingSince" | provide(BlockHeight(TimestampSecond.now().toLong))) :: - ("deferred" | optional(bool, fundingLockedCodec)) :: + ("deferred" | optional(bool, channelReadyCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly val DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec: Codec[DATA_WAIT_FOR_FUNDING_CONFIRMED] = ( ("commitments" | commitmentsCodec) :: ("fundingTx" | optional(bool, txCodec)) :: ("waitingSince" | int64.as[BlockHeight]) :: - ("deferred" | optional(bool, fundingLockedCodec)) :: + ("deferred" | optional(bool, channelReadyCodec)) :: ("lastSent" | either(bool, fundingCreatedCodec, fundingSignedCodec))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED].decodeOnly - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | fundingLockedCodec)).as[DATA_WAIT_FOR_FUNDING_LOCKED].decodeOnly + ("lastSent" | channelReadyCodec)).as[DATA_WAIT_FOR_CHANNEL_READY].decodeOnly val shutdownCodec: Codec[Shutdown] = ( ("channelId" | bytes32) :: @@ -457,7 +457,7 @@ private[channel] object ChannelCodecs0 { .typecase(0x09, Codecs.DATA_CLOSING_Codec) .typecase(0x08, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_COMPAT_01_Codec) - .typecase(0x02, Codecs.DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x02, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x03, Codecs.DATA_NORMAL_COMPAT_03_Codec) .typecase(0x04, Codecs.DATA_SHUTDOWN_Codec) .typecase(0x05, Codecs.DATA_NEGOTIATING_Codec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index d4173bef30..7dbca5ba58 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -236,13 +236,13 @@ private[channel] object ChannelCodecs1 { ("commitments" | commitmentsCodec) :: ("fundingTx" | optional(bool8, txCodec)) :: ("waitingSince" | int64.as[BlockHeight]) :: - ("deferred" | optional(bool8, lengthDelimited(fundingLockedCodec))) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | lengthDelimited(fundingLockedCodec))).as[DATA_WAIT_FOR_FUNDING_LOCKED] + ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: @@ -287,7 +287,7 @@ private[channel] object ChannelCodecs1 { // Order matters! val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) .typecase(0x20, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) - .typecase(0x21, Codecs.DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x21, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x22, Codecs.DATA_NORMAL_Codec) .typecase(0x23, Codecs.DATA_SHUTDOWN_Codec) .typecase(0x24, Codecs.DATA_NEGOTIATING_Codec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index 4001e6e3bb..1033445807 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -271,13 +271,13 @@ private[channel] object ChannelCodecs2 { ("commitments" | commitmentsCodec) :: ("fundingTx" | optional(bool8, txCodec)) :: ("waitingSince" | int64.as[BlockHeight]) :: - ("deferred" | optional(bool8, lengthDelimited(fundingLockedCodec))) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | lengthDelimited(fundingLockedCodec))).as[DATA_WAIT_FOR_FUNDING_LOCKED] + ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: @@ -321,7 +321,7 @@ private[channel] object ChannelCodecs2 { val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) .typecase(0x00, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) - .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x02, Codecs.DATA_NORMAL_Codec) .typecase(0x03, Codecs.DATA_SHUTDOWN_Codec) .typecase(0x04, Codecs.DATA_NEGOTIATING_Codec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index be8ebdf99c..8a4cb224d8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -320,13 +320,13 @@ private[channel] object ChannelCodecs3 { ("fundingTx" | optional(bool8, txCodec)) :: // TODO: next time we define a new channel codec version, we should use the blockHeight codec here (32 bytes) ("waitingSince" | int64.as[BlockHeight]) :: - ("deferred" | optional(bool8, lengthDelimited(fundingLockedCodec))) :: + ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_FUNDING_LOCKED_Codec: Codec[DATA_WAIT_FOR_FUNDING_LOCKED] = ( + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | lengthDelimited(fundingLockedCodec))).as[DATA_WAIT_FOR_FUNDING_LOCKED] + ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] val DATA_NORMAL_COMPAT_02_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: @@ -394,7 +394,7 @@ private[channel] object ChannelCodecs3 { .typecase(0x04, Codecs.DATA_NEGOTIATING_Codec) .typecase(0x03, Codecs.DATA_SHUTDOWN_COMPAT_03_Codec) .typecase(0x02, Codecs.DATA_NORMAL_COMPAT_02_Codec) - .typecase(0x01, Codecs.DATA_WAIT_FOR_FUNDING_LOCKED_Codec) + .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x00, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala index f49187f7a6..eadd23a458 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala @@ -107,10 +107,10 @@ object FundingSignedTlv { val fundingSignedTlvCodec: Codec[TlvStream[FundingSignedTlv]] = tlvStream(discriminated[FundingSignedTlv].by(varint)) } -sealed trait FundingLockedTlv extends Tlv +sealed trait ChannelReadyTlv extends Tlv -object FundingLockedTlv { - val fundingLockedTlvCodec: Codec[TlvStream[FundingLockedTlv]] = tlvStream(discriminated[FundingLockedTlv].by(varint)) +object ChannelReadyTlv { + val channelReadyTlvCodec: Codec[TlvStream[ChannelReadyTlv]] = tlvStream(discriminated[ChannelReadyTlv].by(varint)) } sealed trait ChannelReestablishTlv extends Tlv diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 770b5c0839..0315ea9a1d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -164,10 +164,10 @@ object LightningMessageCodecs { ("signature" | bytes64) :: ("tlvStream" | FundingSignedTlv.fundingSignedTlvCodec)).as[FundingSigned] - val fundingLockedCodec: Codec[FundingLocked] = ( + val channelReadyCodec: Codec[ChannelReady] = ( ("channelId" | bytes32) :: ("nextPerCommitmentPoint" | publicKey) :: - ("tlvStream" | FundingLockedTlv.fundingLockedTlvCodec)).as[FundingLocked] + ("tlvStream" | ChannelReadyTlv.channelReadyTlvCodec)).as[ChannelReady] val txAddInputCodec: Codec[TxAddInput] = ( ("channelId" | bytes32) :: @@ -441,7 +441,7 @@ object LightningMessageCodecs { .typecase(33, acceptChannelCodec) .typecase(34, fundingCreatedCodec) .typecase(35, fundingSignedCodec) - .typecase(36, fundingLockedCodec) + .typecase(36, channelReadyCodec) .typecase(38, shutdownCodec) .typecase(39, closingSignedCodec) .typecase(64, openDualFundedChannelCodec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index c614990f12..7cc27910d8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -225,9 +225,9 @@ case class FundingSigned(channelId: ByteVector32, signature: ByteVector64, tlvStream: TlvStream[FundingSignedTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId -case class FundingLocked(channelId: ByteVector32, +case class ChannelReady(channelId: ByteVector32, nextPerCommitmentPoint: PublicKey, - tlvStream: TlvStream[FundingLockedTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId + tlvStream: TlvStream[ChannelReadyTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId case class Shutdown(channelId: ByteVector32, scriptPubKey: ByteVector, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala index 2930d4c07a..e613e7ab1c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala @@ -71,7 +71,7 @@ object TestDatabases { // serialized and deserialized we need to turn "hot" payments into cold ones def freeze3(input: PersistentChannelData): PersistentChannelData = input match { case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => d.copy(commitments = freeze2(d.commitments)) - case d: DATA_WAIT_FOR_FUNDING_LOCKED => d.copy(commitments = freeze2(d.commitments)) + case d: DATA_WAIT_FOR_CHANNEL_READY => d.copy(commitments = freeze2(d.commitments)) case d: DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT => d.copy(commitments = freeze2(d.commitments)) case d: DATA_NORMAL => d.copy(commitments = freeze2(d.commitments)) case d: DATA_CLOSING => d.copy(commitments = freeze2(d.commitments)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala index 687cf9a917..3da94a0bae 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala @@ -19,7 +19,7 @@ import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.transactions.Transactions.{ClaimP2WPKHOutputTx, DefaultCommitmentFormat, InputInfo, TxOwner} -import fr.acinq.eclair.wire.protocol.{ChannelReestablish, CommitSig, Error, FundingLocked, Init, RevokeAndAck} +import fr.acinq.eclair.wire.protocol.{ChannelReestablish, CommitSig, Error, ChannelReady, Init, RevokeAndAck} import fr.acinq.eclair.{TestKitBaseClass, _} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -234,8 +234,8 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan bob2alice.expectMsgType[ChannelReestablish] alice2bob.forward(bob) bob2alice.forward(newAlice) - alice2bob.expectMsgType[FundingLocked] - bob2alice.expectMsgType[FundingLocked] + alice2bob.expectMsgType[ChannelReady] + bob2alice.expectMsgType[ChannelReady] alice2bob.forward(bob) bob2alice.forward(newAlice) awaitCond(newAlice.stateName == NORMAL) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 5caba83160..2af37fd6a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -229,9 +229,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually { bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] - alice2bob.expectMsgType[FundingLocked] + alice2bob.expectMsgType[ChannelReady] alice2bob.forward(bob) - bob2alice.expectMsgType[FundingLocked] + bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] bob2blockchain.expectMsgType[WatchFundingDeeplyBuried] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala similarity index 85% rename from eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala rename to eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 01345af1e2..f3d772e571 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -35,7 +35,7 @@ import scala.concurrent.duration._ * Created by PM on 05/07/2016. */ -class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { +class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { val relayFees: RelayFees = RelayFees(999 msat, 1234) @@ -76,16 +76,16 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] - alice2bob.expectMsgType[FundingLocked] - awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED) - awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED) + alice2bob.expectMsgType[ChannelReady] + awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) + awaitCond(bob.stateName == WAIT_FOR_CHANNEL_READY) withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router))) } } - test("recv FundingLocked") { f => + test("recv ChannelReady") { f => import f._ - bob2alice.expectMsgType[FundingLocked] + bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) awaitCond(alice.stateName == NORMAL) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate @@ -97,7 +97,7 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv WatchFundingSpentTriggered (remote commit)") { f => import f._ // bob publishes his commitment tx - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! WatchFundingSpentTriggered(tx) alice2blockchain.expectMsgType[TxPublisher.PublishTx] assert(alice2blockchain.expectMsgType[WatchTxConfirmed].txId == tx.txid) @@ -106,7 +106,7 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv WatchFundingSpentTriggered (other commit)") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! WatchFundingSpentTriggered(Transaction(0, Nil, Nil, 0)) alice2bob.expectMsgType[Error] assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) @@ -116,7 +116,7 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv Error") { f => import f._ - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! Error(ByteVector32.Zeroes, "oops") awaitCond(alice.stateName == CLOSING) assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) @@ -126,7 +126,7 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushMsat)) { f => import f._ - val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx bob ! Error(ByteVector32.Zeroes, "funding double-spent") awaitCond(bob.stateName == CLOSING) assert(bob2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) @@ -138,13 +138,13 @@ class WaitForFundingLockedStateSpec extends TestKitBaseClass with FixtureAnyFunS val sender = TestProbe() val c = CMD_CLOSE(sender.ref, None, None) alice ! c - sender.expectMsg(RES_FAILURE(c, CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_FUNDING_LOCKED))) + sender.expectMsg(RES_FAILURE(c, CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_CHANNEL_READY))) } test("recv CMD_FORCECLOSE") { f => import f._ val sender = TestProbe() - val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx + val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx alice ! CMD_FORCECLOSE(sender.ref) awaitCond(alice.stateName == CLOSING) assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].tx.txid == tx.txid) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 149f4d9255..ecda856942 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITC import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Scripts.multiSig2of2 -import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, ChannelReady, FundingSigned, Init, OpenChannel} import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomKey} import org.scalatest.{Outcome, Tag} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -79,20 +79,20 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF } } - test("recv FundingLocked") { f => + test("recv ChannelReady") { f => import f._ // we create a new listener that registers after alice has published the funding tx val listener = TestProbe() bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished]) bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) - // make bob send a FundingLocked msg + // make bob send a ChannelReady msg val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) val txPublished = listener.expectMsgType[TransactionPublished] assert(txPublished.tx == fundingTx) assert(txPublished.miningFee == 0.sat) // bob is fundee assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) - val msg = bob2alice.expectMsgType[FundingLocked] + val msg = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(msg)) awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) @@ -107,9 +107,9 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get alice ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED) + awaitCond(alice.stateName == ChannelReady) alice2blockchain.expectMsgType[WatchFundingLost] - alice2bob.expectMsgType[FundingLocked] + alice2bob.expectMsgType[ChannelReady] } test("recv WatchFundingConfirmedTriggered (bad funding pubkey script)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index cca34ac7d0..4d4c33f829 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -85,9 +85,9 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob, reestablishA) bob2alice.forward(alice, reestablishB) - // both nodes will send the funding_locked message because all updates have been cancelled - alice2bob.expectMsgType[FundingLocked] - bob2alice.expectMsgType[FundingLocked] + // both nodes will send the channel_ready message because all updates have been cancelled + alice2bob.expectMsgType[ChannelReady] + bob2alice.expectMsgType[ChannelReady] // alice will re-send the update and the sig alice2bob.expectMsg(htlc) @@ -660,7 +660,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[ChannelReestablish] bob2alice.forward(alice) - alice2bob.expectMsgType[FundingLocked] // since the channel's commitment hasn't been updated, we re-send funding_locked + alice2bob.expectMsgType[ChannelReady] // since the channel's commitment hasn't been updated, we re-send channel_ready if (shouldUpdateFee) { alice2bob.expectMsg(UpdateFee(channelId(alice), networkFeeratePerKw)) } else { @@ -734,8 +734,8 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.forward(bob) // at this point the channel isn't deeply buried: channel_update isn't sent again - alice2bob.expectMsgType[FundingLocked] - bob2alice.expectMsgType[FundingLocked] + alice2bob.expectMsgType[ChannelReady] + bob2alice.expectMsgType[ChannelReady] alice2bob.expectNoMessage() bob2alice.expectNoMessage() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index c778b391ab..b71f43e79e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -495,7 +495,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { val channelId = sender.expectMsgType[RES_GET_CHANNEL_DATA[PersistentChannelData]].data.channelId awaitCond({ funder.register ! Register.Forward(sender.ref, channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender)) - sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_FUNDING_LOCKED + sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_CHANNEL_READY }) generateBlocks(6) @@ -506,7 +506,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { // after 8 blocks the funder is still waiting for funding_locked from the fundee funder.register ! Register.Forward(sender.ref, channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender)) - assert(sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_FUNDING_LOCKED) + assert(sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_CHANNEL_READY) // simulate a disconnection sender.send(funder.switchboard, Peer.Disconnect(fundee.nodeParams.nodeId)) @@ -520,7 +520,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { fundeeState == OFFLINE && funderState == OFFLINE }) - // reconnect and check the fundee is waiting for more conf, funder is waiting for fundee to send funding_locked + // reconnect and check the fundee is waiting for more conf, funder is waiting for fundee to send channel_ready awaitCond({ // reconnection sender.send(fundee.switchboard, Peer.Connect( @@ -535,7 +535,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { val fundeeState = sender.expectMsgType[RES_GET_CHANNEL_STATE].state funder.register ! Register.Forward(sender.ref, channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender)) val funderState = sender.expectMsgType[RES_GET_CHANNEL_STATE].state - fundeeState == WAIT_FOR_FUNDING_CONFIRMED && funderState == WAIT_FOR_FUNDING_LOCKED + fundeeState == WAIT_FOR_FUNDING_CONFIRMED && funderState == WAIT_FOR_CHANNEL_READY }, max = 30 seconds, interval = 10 seconds) // 5 extra blocks make it 13, just the amount of confirmations needed diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala index fffe22d180..bb3611f4d9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala @@ -423,7 +423,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi val (_, message) = buildMessage(randomKey(), randomKey(), Nil, Recipient(remoteNodeId, None), Nil) probe.send(peerConnection, message) assert(peerConnection.stateName == PeerConnection.CONNECTED) - probe.send(peerConnection, FundingLocked(ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"), randomKey().publicKey)) + probe.send(peerConnection, ChannelReady(ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"), randomKey().publicKey)) peerConnection.stateData match { case d: PeerConnection.ConnectedData => assert(d.isPersistent) case _ => fail() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 1b3708bd4c..a86d2e6bb3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -202,7 +202,7 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { toLocal = Map(ByteVector32(hex"7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247") -> Btc(0.1))) )) ) - JsonSerializers.serialization.write(gb)(JsonSerializers.formats) shouldBe """{"total":1.0,"onChain":{"confirmed":0.4,"unconfirmed":0.05},"offChain":{"waitForFundingConfirmed":0.0,"waitForFundingLocked":0.0,"normal":{"toLocal":0.2,"htlcs":0.05},"shutdown":{"toLocal":0.0,"htlcs":0.0},"negotiating":0.0,"closing":{"localCloseBalance":{"toLocal":{"4d176ad844c363bed59edf81962b008faa6194c3b3757ffcd26ba60f95716db2":0.1},"htlcs":{"94b70cec5a98d67d17c6e3de5c7697f8a6cab4f698df91e633ce35efa3574d71":0.03,"a844edd41ce8503864f3c95d89d850b177a09d7d35e950a7d27c14abb63adb13":0.06},"htlcsUnpublished":0.01},"remoteCloseBalance":{"toLocal":{},"htlcs":{},"htlcsUnpublished":0.0},"mutualCloseBalance":{"toLocal":{"7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247":0.1}},"unknownCloseBalance":{"toLocal":0.0,"htlcs":0.0}},"waitForPublishFutureCommitment":0.0}}""" + JsonSerializers.serialization.write(gb)(JsonSerializers.formats) shouldBe """{"total":1.0,"onChain":{"confirmed":0.4,"unconfirmed":0.05},"offChain":{"waitForFundingConfirmed":0.0,"waitForChannelReady":0.0,"normal":{"toLocal":0.2,"htlcs":0.05},"shutdown":{"toLocal":0.0,"htlcs":0.0},"negotiating":0.0,"closing":{"localCloseBalance":{"toLocal":{"4d176ad844c363bed59edf81962b008faa6194c3b3757ffcd26ba60f95716db2":0.1},"htlcs":{"94b70cec5a98d67d17c6e3de5c7697f8a6cab4f698df91e633ce35efa3574d71":0.03,"a844edd41ce8503864f3c95d89d850b177a09d7d35e950a7d27c14abb63adb13":0.06},"htlcsUnpublished":0.01},"remoteCloseBalance":{"toLocal":{},"htlcs":{},"htlcsUnpublished":0.0},"mutualCloseBalance":{"toLocal":{"7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247":0.1}},"unknownCloseBalance":{"toLocal":0.0,"htlcs":0.0}},"waitForPublishFutureCommitment":0.0}}""" } test("type hints") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 9893c6b268..44cff49a21 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -347,7 +347,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { AcceptChannel(randomBytes32(), 3 sat, UInt64(4), 5 sat, 6 msat, 7, CltvExpiryDelta(8), 9, publicKey(1), point(2), point(3), point(4), point(5), point(6)), FundingCreated(randomBytes32(), bin32(0), 3, randomBytes64()), FundingSigned(randomBytes32(), randomBytes64()), - FundingLocked(randomBytes32(), point(2)), + ChannelReady(randomBytes32(), point(2)), UpdateFee(randomBytes32(), FeeratePerKw(2 sat)), Shutdown(randomBytes32(), bin(47, 0)), ClosingSigned(randomBytes32(), 2 sat, randomBytes64()), From 244d6dda66a865185b260db9bbacf1d47c9e0e77 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 30 Mar 2022 11:55:02 +0200 Subject: [PATCH 02/55] add new options, reassign trampoline bit --- eclair-core/src/main/resources/reference.conf | 4 +++- .../main/scala/fr/acinq/eclair/Features.scala | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 8b6739d551..8f5d406c2d 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -57,9 +57,11 @@ eclair { option_dual_fund = disabled option_onion_messages = optional option_channel_type = optional + option_scid_alias = optional option_payment_metadata = optional - trampoline_payment_prototype = disabled + option_zeroconf = disabled keysend = disabled + trampoline_payment_prototype = disabled } override-init-features = [ // optional per-node features # { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index bb5e5575c9..9fe4a36da9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -240,11 +240,21 @@ object Features { val mandatory = 44 } + case object ScidAlias extends Feature with InitFeature with NodeFeature { + val rfcName = "option_scid_alias" + val mandatory = 46 + } + case object PaymentMetadata extends Feature with InvoiceFeature { val rfcName = "option_payment_metadata" val mandatory = 48 } + case object ZeroConf extends Feature with InitFeature with NodeFeature { + val rfcName = "option_zeroconf" + val mandatory = 50 + } + case object KeySend extends Feature with NodeFeature { val rfcName = "keysend" val mandatory = 54 @@ -278,9 +288,11 @@ object Features { DualFunding, OnionMessages, ChannelType, + ScidAlias, PaymentMetadata, - TrampolinePaymentPrototype, - KeySend + ZeroConf, + KeySend, + TrampolinePaymentPrototype ) // Features may depend on other features, as specified in Bolt 9. From 838559ecce035668b8b2537ce0777896f48917bc Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 30 Mar 2022 14:44:46 +0200 Subject: [PATCH 03/55] add variations to channel type with alias/zeroconf - only add these variations to the most modern type `AnchorOutputsZeroFeeHtlcTx` - the `scid_alias` is managed by eclair, it is enabled by default for private channels (privacy boost) and disabled for public channels (incompatible as per the spec) --- .../main/scala/fr/acinq/eclair/Eclair.scala | 20 +++--- .../main/scala/fr/acinq/eclair/Features.scala | 4 +- .../eclair/blockchain/fee/FeeEstimator.scala | 2 +- .../fr/acinq/eclair/channel/ChannelData.scala | 4 +- .../eclair/channel/ChannelFeatures.scala | 35 +++++++--- .../fr/acinq/eclair/channel/Helpers.scala | 4 +- .../main/scala/fr/acinq/eclair/io/Peer.scala | 16 +++-- .../fr/acinq/eclair/EclairImplSpec.scala | 22 +++++- .../blockchain/fee/FeeEstimatorSpec.scala | 20 +++--- .../eclair/channel/ChannelFeaturesSpec.scala | 52 ++++++++------ .../publish/ReplaceableTxPublisherSpec.scala | 68 +++++++++---------- .../ChannelStateTestsHelperMethods.scala | 6 +- .../a/WaitForAcceptChannelStateSpec.scala | 13 ++-- .../a/WaitForOpenChannelStateSpec.scala | 11 +-- .../b/WaitForFundingCreatedStateSpec.scala | 5 +- .../b/WaitForFundingInternalStateSpec.scala | 5 +- .../b/WaitForFundingSignedStateSpec.scala | 5 +- .../c/WaitForChannelReadyStateSpec.scala | 5 +- .../c/WaitForFundingConfirmedStateSpec.scala | 7 +- .../channel/states/h/ClosingStateSpec.scala | 5 +- .../integration/ChannelIntegrationSpec.scala | 2 +- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 12 +++- .../protocol/LightningMessageCodecsSpec.scala | 6 +- .../acinq/eclair/api/handlers/Channel.scala | 6 +- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 4 +- 25 files changed, 204 insertions(+), 135 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala index 0d6be5469e..d162fe7890 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala @@ -182,14 +182,18 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging { override def open(nodeId: PublicKey, fundingAmount: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[SupportedChannelType], fundingFeeratePerByte_opt: Option[FeeratePerByte], announceChannel_opt: Option[Boolean], openTimeout_opt: Option[Timeout])(implicit timeout: Timeout): Future[ChannelOpenResponse] = { // we want the open timeout to expire *before* the default ask timeout, otherwise user will get a generic response val openTimeout = openTimeout_opt.getOrElse(Timeout(20 seconds)) - (appKit.switchboard ? Peer.OpenChannel( - remoteNodeId = nodeId, - fundingSatoshis = fundingAmount, - pushMsat = pushAmount_opt.getOrElse(0 msat), - channelType_opt = channelType_opt, - fundingTxFeeratePerKw_opt = fundingFeeratePerByte_opt.map(FeeratePerKw(_)), - channelFlags = announceChannel_opt.map(announceChannel => ChannelFlags(announceChannel = announceChannel)), - timeout_opt = Some(openTimeout))).mapTo[ChannelOpenResponse] + for { + _ <- Future.successful(0) + open = Peer.OpenChannel( + remoteNodeId = nodeId, + fundingSatoshis = fundingAmount, + pushMsat = pushAmount_opt.getOrElse(0 msat), + channelType_opt = channelType_opt, + fundingTxFeeratePerKw_opt = fundingFeeratePerByte_opt.map(FeeratePerKw(_)), + channelFlags = announceChannel_opt.map(announceChannel => ChannelFlags(announceChannel = announceChannel)), + timeout_opt = Some(openTimeout)) + res <- (appKit.switchboard ? open).mapTo[ChannelOpenResponse] + } yield res } override def close(channels: List[ApiTypes.ChannelIdentifier], scriptPubKey_opt: Option[ByteVector], closingFeerates_opt: Option[ClosingFeerates])(implicit timeout: Timeout): Future[Map[ApiTypes.ChannelIdentifier, Either[Throwable, CommandResponse[CMD_CLOSE]]]] = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 9fe4a36da9..f6816f4cbe 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -240,7 +240,7 @@ object Features { val mandatory = 44 } - case object ScidAlias extends Feature with InitFeature with NodeFeature { + case object ScidAlias extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { val rfcName = "option_scid_alias" val mandatory = 46 } @@ -250,7 +250,7 @@ object Features { val mandatory = 48 } - case object ZeroConf extends Feature with InitFeature with NodeFeature { + case object ZeroConf extends Feature with InitFeature with NodeFeature with ChannelTypeFeature { val rfcName = "option_zeroconf" val mandatory = 50 } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala index 07d58d7c12..a01b2fcf75 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/FeeEstimator.scala @@ -49,7 +49,7 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax channelType match { case ChannelTypes.Standard | ChannelTypes.StaticRemoteKey => proposedFeerate < networkFeerate * ratioLow || networkFeerate * ratioHigh < proposedFeerate - case ChannelTypes.AnchorOutputs | ChannelTypes.AnchorOutputsZeroFeeHtlcTx => + case ChannelTypes.AnchorOutputs | _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => // when using anchor outputs, we allow any feerate: fees will be set with CPFP and RBF at broadcast time false } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index a2d6a147da..9e39078799 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -85,7 +85,9 @@ case class INPUT_INIT_FUNDER(temporaryChannelId: ByteVector32, remoteInit: Init, channelFlags: ChannelFlags, channelConfig: ChannelConfig, - channelType: SupportedChannelType) + channelType: SupportedChannelType) { + require(!(channelType.features.contains(Features.ScidAlias) && channelFlags.announceChannel), "option_scid_alias is not compatible with public channels") +} case class INPUT_INIT_FUNDEE(temporaryChannelId: ByteVector32, localParams: LocalParams, remote: ActorRef, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 5ef17a4a11..7bd79bb98e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.channel import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} +import fr.acinq.eclair.{Feature, FeatureSupport, Features, InitFeature} import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature} /** @@ -32,7 +33,7 @@ case class ChannelFeatures(features: Set[PermanentChannelFeature]) { val channelType: SupportedChannelType = { if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) { - ChannelTypes.AnchorOutputsZeroFeeHtlcTx + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = features.contains(Features.ScidAlias), zeroConf = features.contains(Features.ZeroConf)) } else if (hasFeature(Features.AnchorOutputs)) { ChannelTypes.AnchorOutputs } else if (hasFeature(Features.StaticRemoteKey)) { @@ -105,11 +106,16 @@ object ChannelTypes { override def commitmentFormat: CommitmentFormat = UnsafeLegacyAnchorOutputsCommitmentFormat override def toString: String = "anchor_outputs" } - case object AnchorOutputsZeroFeeHtlcTx extends SupportedChannelType { - override def features: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx) + case class AnchorOutputsZeroFeeHtlcTx(scidAlias: Boolean, zeroConf: Boolean) extends SupportedChannelType { + override def features: Set[ChannelTypeFeature] = Set( + if (scidAlias) Some(Features.ScidAlias) else None, + if (zeroConf) Some(Features.ZeroConf) else None, + Some(Features.StaticRemoteKey), + Some(Features.AnchorOutputsZeroFeeHtlcTx) + ).flatten override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - override def toString: String = "anchor_outputs_zero_fee_htlc_tx" + override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType { override def features: Set[InitFeature] = featureBits.activated.keySet @@ -117,7 +123,14 @@ object ChannelTypes { } // @formatter:on - private val features2ChannelType: Map[Features[_ <: InitFeature], SupportedChannelType] = Set(Standard, StaticRemoteKey, AnchorOutputs, AnchorOutputsZeroFeeHtlcTx) + private val features2ChannelType: Map[Features[_ <: InitFeature], SupportedChannelType] = Set( + Standard, + StaticRemoteKey, + AnchorOutputs, + AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), + AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true), + AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false), + AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)) .map(channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType) .toMap @@ -125,12 +138,14 @@ object ChannelTypes { def fromFeatures(features: Features[InitFeature]): ChannelType = features2ChannelType.getOrElse(features, UnsupportedChannelType(features)) /** Pick the channel type based on local and remote feature bits, as defined by the spec. */ - def defaultFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature]): SupportedChannelType = { - if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputsZeroFeeHtlcTx)) { - AnchorOutputsZeroFeeHtlcTx - } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputs)) { + def defaultFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): SupportedChannelType = { + def canUse(feature: InitFeature) = Features.canUseFeature(localFeatures, remoteFeatures, feature) + + if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) { + AnchorOutputsZeroFeeHtlcTx(scidAlias = canUse(Features.ScidAlias) && !announceChannel, zeroConf = canUse(Features.ZeroConf)) // alias feature is incompatible with public channel + } else if (canUse(Features.AnchorOutputs)) { AnchorOutputs - } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.StaticRemoteKey)) { + } else if (canUse(Features.StaticRemoteKey)) { StaticRemoteKey } else { Standard diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 4a97f09160..2f2617b658 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -164,10 +164,10 @@ object Helpers { case None if Features.canUseFeature(localFeatures, remoteFeatures, Features.ChannelType) => // Bolt 2: if `option_channel_type` is negotiated: MUST set `channel_type` return Left(MissingChannelType(open.temporaryChannelId)) - case None if channelType != ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures) => + case None if channelType != ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, open.channelFlags.announceChannel) => // If we have overridden the default channel type, but they didn't support explicit channel type negotiation, // we need to abort because they expect a different channel type than what we offered. - return Left(InvalidChannelType(open.temporaryChannelId, channelType, ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures))) + return Left(InvalidChannelType(open.temporaryChannelId, channelType, ChannelTypes.defaultFromFeatures(localFeatures, remoteFeatures, open.channelFlags.announceChannel))) case _ => // we agree on channel type } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 0fa63ef102..a37270e758 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -146,14 +146,15 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainA } else { val channelConfig = ChannelConfig.standard // If a channel type was provided, we directly use it instead of computing it based on local and remote features. - val channelType = c.channelType_opt.getOrElse(ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures)) + val channelFlags = c.channelFlags.getOrElse(nodeParams.channelConf.channelFlags) + val channelType = c.channelType_opt.getOrElse(ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures, channelFlags.announceChannel)) val (channel, localParams) = createNewChannel(nodeParams, d.localFeatures, channelType, isInitiator = true, c.fundingSatoshis, origin_opt = Some(sender())) c.timeout_opt.map(openTimeout => context.system.scheduler.scheduleOnce(openTimeout.duration, channel, Channel.TickChannelOpenTimeout)(context.dispatcher)) val temporaryChannelId = randomBytes32() val channelFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(remoteNodeId, channelType, c.fundingSatoshis, None) val fundingTxFeeratePerKw = c.fundingTxFeeratePerKw_opt.getOrElse(nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) log.info(s"requesting a new channel with type=$channelType fundingSatoshis=${c.fundingSatoshis}, pushMsat=${c.pushMsat} and fundingFeeratePerByte=${c.fundingTxFeeratePerKw_opt} temporaryChannelId=$temporaryChannelId localParams=$localParams") - channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis, c.pushMsat, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.peerConnection, d.remoteInit, c.channelFlags.getOrElse(nodeParams.channelConf.channelFlags), channelConfig, channelType) + channel ! INPUT_INIT_FUNDER(temporaryChannelId, c.fundingSatoshis, c.pushMsat, channelFeeratePerKw, fundingTxFeeratePerKw, localParams, d.peerConnection, d.remoteInit, channelFlags, channelConfig, channelType) stay() using d.copy(channels = d.channels + (TemporaryChannelId(temporaryChannelId) -> channel)) } @@ -165,12 +166,12 @@ class Peer(val nodeParams: NodeParams, remoteNodeId: PublicKey, wallet: OnChainA // remote explicitly specifies a channel type: we check whether we want to allow it case Some(remoteChannelType) => ChannelTypes.areCompatible(d.localFeatures, remoteChannelType) match { case Some(acceptedChannelType) => Right(acceptedChannelType) - case None => Left(InvalidChannelType(msg.temporaryChannelId, ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures), remoteChannelType)) + case None => Left(InvalidChannelType(msg.temporaryChannelId, ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures, msg.channelFlags.announceChannel), remoteChannelType)) } // Bolt 2: if `option_channel_type` is negotiated: MUST set `channel_type` case None if Features.canUseFeature(d.localFeatures, d.remoteFeatures, Features.ChannelType) => Left(MissingChannelType(msg.temporaryChannelId)) // remote doesn't specify a channel type: we use spec-defined defaults - case None => Right(ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures)) + case None => Right(ChannelTypes.defaultFromFeatures(d.localFeatures, d.remoteFeatures, msg.channelFlags.announceChannel)) } chosenChannelType match { case Right(channelType) => @@ -465,9 +466,10 @@ object Peer { case class Disconnect(nodeId: PublicKey) extends PossiblyHarmful case class OpenChannel(remoteNodeId: PublicKey, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, channelType_opt: Option[SupportedChannelType], fundingTxFeeratePerKw_opt: Option[FeeratePerKw], channelFlags: Option[ChannelFlags], timeout_opt: Option[Timeout]) extends PossiblyHarmful { - require(pushMsat <= fundingSatoshis, s"pushMsat must be less or equal to fundingSatoshis") - require(fundingSatoshis >= 0.sat, s"fundingSatoshis must be positive") - require(pushMsat >= 0.msat, s"pushMsat must be positive") + require(!(channelType_opt.exists(_.features.contains(Features.ScidAlias)) && channelFlags.exists(_.announceChannel)), "option_scid_alias is not compatible with public channels") + require(pushMsat <= fundingSatoshis, "pushMsat must be less or equal to fundingSatoshis") + require(fundingSatoshis >= 0.sat, "fundingSatoshis must be positive") + require(pushMsat >= 0.msat, "pushMsat must be positive") fundingTxFeeratePerKw_opt.foreach(feeratePerKw => require(feeratePerKw >= FeeratePerKw.MinimumFeeratePerKw, s"fee rate $feeratePerKw is below minimum ${FeeratePerKw.MinimumFeeratePerKw} rate/kw")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 9154eb3154..86d649d31a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -16,7 +16,7 @@ package fr.acinq.eclair -import akka.actor.ActorRef +import akka.actor.{ActorRef, Status} import akka.actor.typed.scaladsl.adapter.{ClassicActorRefOps, actorRefAdapter} import akka.pattern.pipe import akka.testkit.TestProbe @@ -29,6 +29,7 @@ import fr.acinq.eclair.blockchain.DummyOnChainWallet import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.channel._ import fr.acinq.eclair.db._ +import fr.acinq.eclair.io.Peer import fr.acinq.eclair.io.Peer.OpenChannel import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment @@ -233,6 +234,25 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I assert(sender.expectMsgType[Iterable[ChannelUpdate]].map(_.shortChannelId).toSet == Set(ShortChannelId(2))) } + test("open with bad arguments") { f => + import f._ + + val eclair = new EclairImpl(kit) + + // option_scid_alias is not compatible with public channels + eclair.open(randomKey().publicKey, 123456 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), None, announceChannel_opt = Some(true), None).pipeTo(sender.ref) + assert(sender.expectMsgType[Status.Failure].cause.getMessage.contains("option_scid_alias is not compatible with public channels")) + + eclair.open(randomKey().publicKey, 123456 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), None, announceChannel_opt = Some(false), None).pipeTo(sender.ref) + switchboard.expectMsgType[Peer.OpenChannel] + + eclair.open(randomKey().publicKey, 123456 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)), None, announceChannel_opt = Some(true), None).pipeTo(sender.ref) + switchboard.expectMsgType[Peer.OpenChannel] + + eclair.open(randomKey().publicKey, 123456 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)), None, announceChannel_opt = Some(false), None).pipeTo(sender.ref) + switchboard.expectMsgType[Peer.OpenChannel] + } + test("close and forceclose should work both with channelId and shortChannelId") { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala index 3e0567eb4a..4211416a7b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FeeEstimatorSpec.scala @@ -57,34 +57,34 @@ class FeeEstimatorSpec extends AnyFunSuite { feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) == defaultMaxCommitFeerate / 2) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, None) == defaultMaxCommitFeerate / 2) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, None) == defaultMaxCommitFeerate / 2) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 2, mempoolMinFee = FeeratePerKw(250 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) == defaultMaxCommitFeerate) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, None) == defaultMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, None) == defaultMaxCommitFeerate) assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) == overrideMaxCommitFeerate) - assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, None) == overrideMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, None) == overrideMaxCommitFeerate) val currentFeerates1 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, Some(currentFeerates1)) == defaultMaxCommitFeerate / 2) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, Some(currentFeerates1)) == defaultMaxCommitFeerate / 2) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, Some(currentFeerates1)) == defaultMaxCommitFeerate / 2) val currentFeerates2 = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate * 1.5, mempoolMinFee = FeeratePerKw(250 sat))) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, Some(currentFeerates2)) == defaultMaxCommitFeerate) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, Some(currentFeerates2)) == defaultMaxCommitFeerate) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, Some(currentFeerates2)) == defaultMaxCommitFeerate) val highFeerates = CurrentFeerates(FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(mempoolMinFee = FeeratePerKw(10000 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs, 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) - assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) + assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, Some(highFeerates)) == FeeratePerKw(10000 sat) * 1.25) feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(25000 sat)).copy(mempoolMinFee = FeeratePerKw(10000 sat))) assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) - assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) + assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) - assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx, 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) + assert(feeConf.getCommitmentFeerate(overrideNodeId, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), 100000 sat, None) == FeeratePerKw(10000 sat) * 1.25) } test("fee difference too high") { @@ -123,7 +123,7 @@ class FeeEstimatorSpec extends AnyFunSuite { ) testCases.foreach { case (networkFeerate, proposedFeerate) => assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputs, networkFeerate, proposedFeerate)) - assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputsZeroFeeHtlcTx, networkFeerate, proposedFeerate)) + assert(!tolerance.isFeeDiffTooHigh(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), networkFeerate, proposedFeerate)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala index ad3a4baeaf..d992176daa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelFeaturesSpec.scala @@ -55,27 +55,34 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha } test("pick channel type based on local and remote features") { - case class TestCase(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], expectedChannelType: ChannelType) + case class TestCase(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean, expectedChannelType: ChannelType) val testCases = Seq( - TestCase(Features.empty, Features.empty, ChannelTypes.Standard), - TestCase(Features(StaticRemoteKey -> Optional), Features.empty, ChannelTypes.Standard), - TestCase(Features.empty, Features(StaticRemoteKey -> Optional), ChannelTypes.Standard), - TestCase(Features.empty, Features(StaticRemoteKey -> Mandatory), ChannelTypes.Standard), - TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Mandatory), Features(Wumbo -> Mandatory), ChannelTypes.Standard), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), ChannelTypes.StaticRemoteKey), - TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), ChannelTypes.StaticRemoteKey), - TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Optional), Features(StaticRemoteKey -> Mandatory, Wumbo -> Mandatory), ChannelTypes.StaticRemoteKey), - TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), ChannelTypes.StaticRemoteKey), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), ChannelTypes.StaticRemoteKey), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), ChannelTypes.AnchorOutputs), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), ChannelTypes.AnchorOutputs), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), ChannelTypes.AnchorOutputsZeroFeeHtlcTx), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), ChannelTypes.AnchorOutputsZeroFeeHtlcTx), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx), + TestCase(Features.empty, Features.empty, announceChannel = true, ChannelTypes.Standard), + TestCase(Features(StaticRemoteKey -> Optional), Features.empty, announceChannel = true, ChannelTypes.Standard), + TestCase(Features.empty, Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.Standard), + TestCase(Features.empty, Features(StaticRemoteKey -> Mandatory), announceChannel = true, ChannelTypes.Standard), + TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Mandatory), Features(Wumbo -> Mandatory), announceChannel = true, ChannelTypes.Standard), + TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey), + TestCase(Features(StaticRemoteKey -> Optional), Features(StaticRemoteKey -> Mandatory), announceChannel = true, ChannelTypes.StaticRemoteKey), + TestCase(Features(StaticRemoteKey -> Optional, Wumbo -> Optional), Features(StaticRemoteKey -> Mandatory, Wumbo -> Mandatory), announceChannel = true, ChannelTypes.StaticRemoteKey), + TestCase(Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), announceChannel = true, ChannelTypes.StaticRemoteKey), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional), announceChannel = true, ChannelTypes.AnchorOutputs), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputs), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Mandatory), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), announceChannel = false, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Mandatory, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional, Features.ZeroConf -> Optional), announceChannel = true, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Mandatory, Features.ZeroConf -> Optional), Features(StaticRemoteKey -> Optional, AnchorOutputs -> Optional, AnchorOutputsZeroFeeHtlcTx -> Optional, Features.ScidAlias -> Optional, Features.ZeroConf -> Optional), announceChannel = false, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), ) for (testCase <- testCases) { - assert(ChannelTypes.defaultFromFeatures(testCase.localFeatures, testCase.remoteFeatures) == testCase.expectedChannelType, s"localFeatures=${testCase.localFeatures} remoteFeatures=${testCase.remoteFeatures}") + assert(ChannelTypes.defaultFromFeatures(testCase.localFeatures, testCase.remoteFeatures, testCase.announceChannel) == testCase.expectedChannelType, s"localFeatures=${testCase.localFeatures} remoteFeatures=${testCase.remoteFeatures}") } } @@ -86,7 +93,10 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha TestCase(Features.empty, ChannelTypes.Standard), TestCase(Features(StaticRemoteKey -> Mandatory), ChannelTypes.StaticRemoteKey), TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputs -> Mandatory), ChannelTypes.AnchorOutputs), - TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory, ScidAlias -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory, ZeroConf -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)), + TestCase(Features(StaticRemoteKey -> Mandatory, AnchorOutputsZeroFeeHtlcTx -> Mandatory, ScidAlias -> Mandatory, ZeroConf -> Mandatory), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), ) for (testCase <- validChannelTypes) { assert(ChannelTypes.fromFeatures(testCase.features) == testCase.expectedChannelType, testCase.features) @@ -121,9 +131,9 @@ class ChannelFeaturesSpec extends TestKitBaseClass with AnyFunSuiteLike with Cha TestCase(ChannelTypes.StaticRemoteKey, Features(Wumbo -> Optional), Features(Wumbo -> Optional), Set(StaticRemoteKey, Wumbo)), TestCase(ChannelTypes.AnchorOutputs, Features.empty, Features(Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputs)), TestCase(ChannelTypes.AnchorOutputs, Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), Set(StaticRemoteKey, AnchorOutputs, Wumbo)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx, Features.empty, Features(Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx, Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo)), - TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx, Features(DualFunding -> Optional, Wumbo -> Optional), Features(DualFunding -> Optional, Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo, DualFunding)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features.empty, Features(Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(Wumbo -> Optional), Features(Wumbo -> Mandatory), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo)), + TestCase(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), Features(DualFunding -> Optional, Wumbo -> Optional), Features(DualFunding -> Optional, Wumbo -> Optional), Set(StaticRemoteKey, AnchorOutputsZeroFeeHtlcTx, Wumbo, DualFunding)), ) testCases.foreach(t => assert(ChannelFeatures(t.channelType, t.localFeatures, t.remoteFeatures).features == t.expected, s"channelType=${t.channelType} localFeatures=${t.localFeatures} remoteFeatures=${t.remoteFeatures}")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala index 3d136c5ae7..1dc458a684 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala @@ -131,7 +131,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w val aliceNodeParams = TestConstants.Alice.nodeParams.copy(blockHeight = blockHeight) val setup = init(aliceNodeParams, TestConstants.Bob.nodeParams.copy(blockHeight = blockHeight), walletClient) val testTags = channelType match { - case ChannelTypes.AnchorOutputsZeroFeeHtlcTx => Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs) + case _: ChannelTypes.AnchorOutputsZeroFeeHtlcTx => Set(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs) case ChannelTypes.AnchorOutputs => Set(ChannelStateTestsTags.AnchorOutputs) case ChannelTypes.StaticRemoteKey => Set(ChannelStateTestsTags.StaticRemoteKey) case _ => Set.empty[String] @@ -168,7 +168,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx feerate high enough, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val commitFeerate = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.commitTxFeerate @@ -183,7 +183,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx confirmed, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 12) @@ -200,7 +200,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx feerate high enough and commit tx confirmed, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val commitFeerate = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.commitTxFeerate @@ -218,7 +218,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("remote commit tx confirmed, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val remoteCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.fullySignedLocalCommitTx(bob.underlyingActor.nodeParams.channelKeyManager) @@ -236,7 +236,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("remote commit tx published, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val remoteCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.fullySignedLocalCommitTx(bob.underlyingActor.nodeParams.channelKeyManager) @@ -255,7 +255,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("remote commit tx replaces local commit tx, not spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val remoteCommit = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.fullySignedLocalCommitTx(bob.underlyingActor.nodeParams.channelKeyManager) @@ -291,7 +291,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("funding tx not found, skipping anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (_, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 12) @@ -307,7 +307,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("not enough funds to increase commit tx feerate") { - withFixture(Seq(10.5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ // close channel and wait for the commit tx to be published, anchor will not be published because we don't have enough funds @@ -326,7 +326,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx feerate too low, spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -356,7 +356,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not published, publishing it and spending anchor output") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 32) @@ -392,7 +392,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w 22000 sat, 15000 sat ) - withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ // NB: we try to get transactions confirmed *before* their confirmation target, so we aim for a more aggressive block target than what's provided. @@ -420,7 +420,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx fees not increased when confirmation target is far and feerate hasn't changed") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -443,7 +443,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not confirming, lowering anchor output amount") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -481,7 +481,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not confirming, adding other wallet inputs") { - withFixture(Seq(10.5 millibtc, 5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.5 millibtc, 5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -520,7 +520,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not confirming, not enough funds to increase fees") { - withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -554,7 +554,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not confirming, cannot use new unconfirmed inputs to increase fees") { - withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -585,7 +585,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("commit tx not confirming, updating confirmation target") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, anchorTx) = closeChannelWithoutHtlcs(f, aliceBlockHeight() + 30) @@ -628,7 +628,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("unlock utxos when anchor tx cannot be published") { - withFixture(Seq(500 millibtc, 200 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc, 200 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val targetFeerate = FeeratePerKw(3000 sat) @@ -660,7 +660,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("unlock anchor utxos when stopped before completion") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val targetFeerate = FeeratePerKw(3000 sat) @@ -679,7 +679,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("remote commit tx confirmed, not publishing htlc tx") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ // Add htlcs in both directions and ensure that preimages are available. @@ -768,7 +768,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("not enough funds to increase htlc tx feerate") { - withFixture(Seq(10.5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, htlcSuccess, _) = closeChannelWithHtlcs(f, aliceBlockHeight()) @@ -866,7 +866,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc tx feerate zero, adding wallet inputs") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val targetFeerate = FeeratePerKw(15_000 sat) @@ -883,7 +883,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc tx feerate zero, high commit feerate, adding wallet inputs") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val commitFeerate = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.commitTxFeerate @@ -915,7 +915,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w 5200 sat, 5100 sat ) - withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(utxos, ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val targetFeerate = FeeratePerKw(8_000 sat) @@ -930,7 +930,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc success tx not confirming, lowering output amount") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val initialFeerate = FeeratePerKw(15_000 sat) @@ -966,7 +966,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc success tx not confirming, adding other wallet inputs") { - withFixture(Seq(10.2 millibtc, 2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(10.2 millibtc, 2 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val initialFeerate = FeeratePerKw(15_000 sat) @@ -1002,7 +1002,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc success tx confirmation target reached, increasing fees") { - withFixture(Seq(50 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(50 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val initialFeerate = FeeratePerKw(10_000 sat) @@ -1034,7 +1034,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("htlc timeout tx not confirming, increasing fees") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val feerate = FeeratePerKw(15_000 sat) @@ -1072,7 +1072,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("utxos count too low, setting short confirmation target") { - withFixture(Seq(15 millibtc, 10 millibtc, 5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(15 millibtc, 10 millibtc, 5 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val (commitTx, htlcSuccess, _) = closeChannelWithHtlcs(f, aliceBlockHeight() + 144) @@ -1094,7 +1094,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("unlock utxos when htlc tx cannot be published") { - withFixture(Seq(500 millibtc, 200 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc, 200 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val targetFeerate = FeeratePerKw(5_000 sat) @@ -1129,7 +1129,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("unlock htlc utxos when stopped before completion") { - withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(500 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ setFeerate(FeeratePerKw(5_000 sat)) @@ -1146,7 +1146,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("local commit tx confirmed, not publishing claim htlc tx") { - withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ // Add htlcs in both directions and ensure that preimages are available. @@ -1283,7 +1283,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w } test("claim htlc tx feerate high enough, not changing output amount") { - withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx) { f => + withFixture(Seq(11 millibtc), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) { f => import f._ val currentFeerate = alice.underlyingActor.nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(2) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 2af37fd6a2..7c2a1c55c0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -146,7 +146,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { SetupFixture(alice, bob, aliceOrigin, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, alice2relayer, bob2relayer, channelUpdateListener, wallet, alicePeer, bobPeer) } - def computeFeatures(setup: SetupFixture, tags: Set[String]): (LocalParams, LocalParams, SupportedChannelType) = { + def computeFeatures(setup: SetupFixture, tags: Set[String], channelFlags: ChannelFlags): (LocalParams, LocalParams, SupportedChannelType) = { import setup._ val aliceInitFeatures = Alice.nodeParams.features @@ -168,7 +168,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) .initFeatures() - val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures) + val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, channelFlags.announceChannel) implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global val aliceParams = Alice.channelParams @@ -193,8 +193,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, channelType) = computeFeatures(setup, tags) val channelFlags = ChannelFlags(announceChannel = tags.contains(ChannelStateTestsTags.ChannelsPublic)) + val (aliceParams, bobParams, channelType) = computeFeatures(setup, tags, channelFlags) val commitTxFeerate = if (tags.contains(ChannelStateTestsTags.AnchorOutputs) || tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw val (fundingSatoshis, pushMsat) = if (tags.contains(ChannelStateTestsTags.NoPushMsat)) { (TestConstants.fundingSatoshis, 0.msat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 040e255f29..d09af51d74 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -56,14 +56,15 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, defaultChannelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, defaultChannelType) = computeFeatures(setup, test.tags, channelFlags) val channelType = if (test.tags.contains("standard-channel-type")) ChannelTypes.Standard else defaultChannelType - val commitTxFeerate = if (channelType == ChannelTypes.AnchorOutputs || channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw + val commitTxFeerate = if (channelType == ChannelTypes.AnchorOutputs || channelType.isInstanceOf[ChannelTypes.AnchorOutputsZeroFeeHtlcTx]) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { val fundingAmount = if (test.tags.contains(ChannelStateTestsTags.Wumbo)) Btc(5).toSatoshi else TestConstants.fundingSatoshis - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingAmount, TestConstants.pushMsat, commitTxFeerate, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingAmount, TestConstants.pushMsat, commitTxFeerate, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) @@ -96,10 +97,10 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv AcceptChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx)) + assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false))) bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) aliceOrigin.expectNoMessage() } @@ -118,7 +119,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv AcceptChannel (channel type not set but feature bit set)", Tag(ChannelStateTestsTags.ChannelType), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx)) + assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false))) bob2alice.forward(alice, accept.copy(tlvStream = TlvStream.empty)) alice2bob.expectMsg(Error(accept.temporaryChannelId, "option_channel_type was negotiated but channel_type is missing")) awaitCond(alice.stateName == CLOSED) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index b76db60e96..a6d3522b8b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -50,13 +50,14 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, defaultChannelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, defaultChannelType) = computeFeatures(setup, test.tags, channelFlags) val channelType = if (test.tags.contains("standard-channel-type")) ChannelTypes.Standard else defaultChannelType - val commitTxFeerate = if (channelType == ChannelTypes.AnchorOutputs || channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw + val commitTxFeerate = if (channelType == ChannelTypes.AnchorOutputs || channelType.isInstanceOf[ChannelTypes.AnchorOutputsZeroFeeHtlcTx]) TestConstants.anchorOutputsFeeratePerKw else TestConstants.feeratePerKw val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, commitTxFeerate, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, commitTxFeerate, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL) withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, bob2blockchain))) @@ -87,10 +88,10 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui test("recv OpenChannel (anchor outputs zero fee htlc txs)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] - assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx)) + assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false))) alice2bob.forward(bob) awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) - assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) } test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag("standard-channel-type")) { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 84bb3b43e1..ff298ee255 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -60,11 +60,12 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala index 7fa5845ec6..23419edfda 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingInternalStateSpec.scala @@ -43,11 +43,12 @@ class WaitForFundingInternalStateSpec extends TestKitBaseClass with FixtureAnyFu val setup = init(wallet = new NoOpOnChainWallet()) import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 9596b3c33b..c25e605975 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -59,11 +59,12 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS import setup._ val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index f3d772e571..13c892037a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -45,14 +45,15 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val setup = init() import setup._ val channelConfig = ChannelConfig.standard + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val pushMsat = if (test.tags.contains(ChannelStateTestsTags.NoPushMsat)) 0.msat else TestConstants.pushMsat - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { alice.underlyingActor.nodeParams.db.peers.addOrUpdateRelayFees(bobParams.nodeId, relayFees) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index ecda856942..440a11a98c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -47,15 +47,16 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF import setup._ val channelConfig = ChannelConfig.standard + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val pushMsat = if (test.tags.contains(ChannelStateTestsTags.NoPushMsat)) 0.msat else TestConstants.pushMsat - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) within(30 seconds) { val listener = TestProbe() alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished]) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) bob2blockchain.expectMsgType[TxPublisher.SetChannelId] @@ -107,7 +108,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get alice ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) - awaitCond(alice.stateName == ChannelReady) + awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) alice2blockchain.expectMsgType[WatchFundingLost] alice2bob.expectMsgType[ChannelReady] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 52052b9e67..556b453386 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -66,10 +66,11 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with if (unconfirmedFundingTx) { within(30 seconds) { val channelConfig = ChannelConfig.standard - val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags) + val channelFlags = ChannelFlags.Private + val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) - alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, ChannelFlags.Private, channelConfig, channelType) + alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) alice2blockchain.expectMsgType[SetChannelId] bob ! INPUT_INIT_FUNDEE(ByteVector32.Zeroes, bobParams, bob2alice.ref, aliceInit, channelConfig, channelType) bob2blockchain.expectMsgType[SetChannelId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index b71f43e79e..ff22a84393 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -829,7 +829,7 @@ class AnchorOutputZeroFeeHtlcTxsChannelIntegrationSpec extends AnchorChannelInte } test("open channel C <-> F, send payments and close (anchor outputs zero fee htlc txs)") { - testOpenPayClose(ChannelTypes.AnchorOutputsZeroFeeHtlcTx) + testOpenPayClose(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) } test("propagate a fulfill upstream when a downstream htlc is redeemed on-chain (local commit, anchor outputs zero fee htlc txs)") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index e557c5b3e0..77d0623c0e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -341,7 +341,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle } // They only support anchor outputs with zero fee htlc txs and we don't. { - val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx))) + val open = createOpenChannelMessage(TlvStream[OpenChannelTlv](ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))) peerConnection.send(peer, open) peerConnection.expectMsg(Error(open.temporaryChannelId, "invalid channel_type=anchor_outputs_zero_fee_htlc_tx, expected channel_type=standard")) } @@ -432,7 +432,7 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle feeEstimator.setFeerate(FeeratesPerKw.single(TestConstants.anchorOutputsFeeratePerKw * 2).copy(mempoolMinFee = FeeratePerKw(250 sat))) probe.send(peer, Peer.OpenChannel(remoteNodeId, 15000 sat, 0 msat, None, None, None, None)) val init = channel.expectMsgType[INPUT_INIT_FUNDER] - assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx) + assert(init.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) assert(init.fundingAmount == 15000.sat) assert(init.commitTxFeerate == TestConstants.anchorOutputsFeeratePerKw) assert(init.fundingTxFeerate == feeEstimator.getFeeratePerKw(nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)) @@ -450,6 +450,14 @@ class PeerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Paralle assert(init.localParams.defaultFinalScriptPubKey == Script.write(Script.pay2wpkh(init.localParams.walletStaticPaymentBasepoint.get))) } + test("do not allow option_scid_alias with public channel") { f => + import f._ + + intercept[IllegalArgumentException] { + Peer.OpenChannel(remoteNodeId, 24000 sat, 0 msat, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)), None, Some(ChannelFlags(announceChannel = true)), None) + } + } + test("set origin_opt when spawning a channel") { f => import f._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 44cff49a21..0a6b2adcf9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -221,7 +221,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { defaultEncoded ++ hex"0000" ++ hex"01021000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.StaticRemoteKey))), // non-empty upfront_shutdown_script + channel type defaultEncoded ++ hex"0004 01abcdef" ++ hex"0103101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs))), - defaultEncoded ++ hex"0002 abcd" ++ hex"0103401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"abcd"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx))), + defaultEncoded ++ hex"0002 abcd" ++ hex"0103401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"abcd"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))), ) for ((encoded, expected) <- testCases) { @@ -255,8 +255,8 @@ class LightningMessageCodecsSpec extends AnyFunSuite { val defaultEncoded = hex"0040 0000000000000000000000000000000000000000000000000000000000000000 0100000000000000000000000000000000000000000000000000000000000000 00001388 00000fa0 000000000003d090 00000000000001f4 000000000000c350 000000000000000f 0090 01e3 0009eb10 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766 02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337 03462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b 0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7 03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a 01" val testCases = Seq( defaultOpen -> defaultEncoded, - defaultOpen.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx))) -> (defaultEncoded ++ hex"0103401000"), - defaultOpen.copy(tlvStream = TlvStream(UpfrontShutdownScriptTlv(hex"00143adb2d0445c4d491cc7568b10323bd6615a91283"), ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx))) -> (defaultEncoded ++ hex"001600143adb2d0445c4d491cc7568b10323bd6615a91283 0103401000"), + defaultOpen.copy(tlvStream = TlvStream(ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(false, false)))) -> (defaultEncoded ++ hex"0103401000"), + defaultOpen.copy(tlvStream = TlvStream(UpfrontShutdownScriptTlv(hex"00143adb2d0445c4d491cc7568b10323bd6615a91283"), ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(false, false)))) -> (defaultEncoded ++ hex"001600143adb2d0445c4d491cc7568b10323bd6615a91283 0103401000"), ) testCases.foreach { case (open, bin) => val decoded = lightningMessageCodec.decode(bin.bits).require.value diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index 61fece11a3..f99a06e2f8 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -33,13 +33,13 @@ trait Channel { import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization} val open: Route = postRequest("open") { implicit t => - formFields(nodeIdFormParam, "fundingSatoshis".as[Satoshi], "pushMsat".as[MilliSatoshi].?, "channelType".?, "fundingFeerateSatByte".as[FeeratePerByte].?, "announceChannel".as[Boolean].?, "openTimeoutSeconds".as[Timeout].?) { - (nodeId, fundingSatoshis, pushMsat, channelType, fundingFeerateSatByte, announceChannel_opt, openTimeout_opt) => + formFields(nodeIdFormParam, "fundingSatoshis".as[Satoshi], "pushMsat".as[MilliSatoshi].?, "channelType".?, "fundingFeerateSatByte".as[FeeratePerByte].?, "announceChannel".as[Boolean].?, "zeroConf".as[Boolean]?false, "openTimeoutSeconds".as[Timeout].?) { + (nodeId, fundingSatoshis, pushMsat, channelType, fundingFeerateSatByte, announceChannel_opt, zeroConf, openTimeout_opt) => val (channelTypeOk, channelType_opt) = channelType match { case Some(str) if str == ChannelTypes.Standard.toString => (true, Some(ChannelTypes.Standard)) case Some(str) if str == ChannelTypes.StaticRemoteKey.toString => (true, Some(ChannelTypes.StaticRemoteKey)) case Some(str) if str == ChannelTypes.AnchorOutputs.toString => (true, Some(ChannelTypes.AnchorOutputs)) - case Some(str) if str == ChannelTypes.AnchorOutputsZeroFeeHtlcTx.toString => (true, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx)) + case Some(str) if str == ChannelTypes.AnchorOutputsZeroFeeHtlcTx.toString => (true, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = announceChannel_opt.contains(true), zeroConf = zeroConf))) // alias feature is incompatible with public channel case Some(_) => (false, None) case None => (true, None) } diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index de7394b551..20baf3637e 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -372,9 +372,9 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM Route.seal(mockService.route) ~> check { assert(handled) - assert(status == OK) + //assert(status == OK) assert(entityAs[String] == "\"created channel 56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e\"") - eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx), None, None, None)(any[Timeout]).wasCalled(once) + eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), None, None, None)(any[Timeout]).wasCalled(once) } } From 1e88cb67a1fd313c81db3fa1b900a9c697b1196b Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 29 Mar 2022 19:13:43 +0200 Subject: [PATCH 04/55] set min_depth to 0 in zeroconf --- .../eclair/channel/fsm/ChannelOpenSingleFunder.scala | 2 +- .../states/ChannelStateTestsHelperMethods.scala | 6 ++++++ .../channel/states/a/WaitForOpenChannelStateSpec.scala | 10 +++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 1995fbd470..e6f088561e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -80,7 +80,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isInitiator = false, open.temporaryChannelId, open.feeratePerKw, None)) val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val minimumDepth = Helpers.minDepthForFunding(nodeParams.channelConf, open.fundingSatoshis) + val minimumDepth = if (channelFeatures.hasFeature(Features.ZeroConf)) 0 else Helpers.minDepthForFunding(nodeParams.channelConf, open.fundingSatoshis) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. val localShutdownScript = if (Features.canUseFeature(localParams.initFeatures, remoteInit.features, Features.UpfrontShutdownScript)) localParams.defaultFinalScriptPubKey else ByteVector.empty diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 7c2a1c55c0..0d573db6bd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -72,6 +72,8 @@ object ChannelStateTestsTags { val HighDustLimitDifferenceBobAlice = "high_dust_limit_difference_bob_alice" /** If set, channels will use option_channel_type. */ val ChannelType = "option_channel_type" + /** If set, channels will use option_zeroconf. */ + val ZeroConf = "zeroconf" } trait ChannelStateTestsBase extends Assertions with Eventually { @@ -157,6 +159,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) .initFeatures() val bobInitFeatures = Bob.nodeParams.features .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.Wumbo))(_.updated(Features.Wumbo, FeatureSupport.Optional)) @@ -166,6 +170,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) .initFeatures() val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, channelFlags.announceChannel) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index a6d3522b8b..7e1e1be587 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, Init, OpenChannel, TlvStream} -import fr.acinq.eclair.{CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits.ByteVector @@ -288,6 +288,14 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui awaitCond(bob.stateName == CLOSED) } + test("recv OpenChannel (zeroconf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + val open = alice2bob.expectMsgType[OpenChannel] + alice2bob.forward(bob, open) + val accept = bob2alice.expectMsgType[AcceptChannel] + assert(accept.minimumDepth == 0) + } + test("recv Error") { f => import f._ bob ! Error(ByteVector32.Zeroes, "oops") From 54b91ea630f58e3d96d744fe4fd574c108e57509 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 29 Mar 2022 19:25:35 +0200 Subject: [PATCH 05/55] add an alias tlv to channel_ready --- .../fr/acinq/eclair/wire/protocol/ChannelTlv.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala index eadd23a458..9ff575183d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala @@ -18,9 +18,10 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.Satoshi import fr.acinq.eclair.channel.{ChannelType, ChannelTypes} +import fr.acinq.eclair.wire.protocol.ChannelTlv.ChannelTypeTlv import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.TlvCodecs.tlvStream -import fr.acinq.eclair.{FeatureSupport, Features, UInt64} +import fr.acinq.eclair.{FeatureSupport, Features, ShortChannelId, UInt64} import scodec.Codec import scodec.bits.ByteVector import scodec.codecs._ @@ -110,7 +111,14 @@ object FundingSignedTlv { sealed trait ChannelReadyTlv extends Tlv object ChannelReadyTlv { - val channelReadyTlvCodec: Codec[TlvStream[ChannelReadyTlv]] = tlvStream(discriminated[ChannelReadyTlv].by(varint)) + + case class ShortChannelIdTlv(alias: ShortChannelId) extends ChannelReadyTlv + + val channelTypeCodec: Codec[ShortChannelIdTlv] = variableSizeBytesLong(varintoverflow, "alias" | shortchannelid).as[ShortChannelIdTlv] + + val channelReadyTlvCodec: Codec[TlvStream[ChannelReadyTlv]] = tlvStream(discriminated[ChannelReadyTlv].by(varint) + .typecase(UInt64(1), channelTypeCodec) + ) } sealed trait ChannelReestablishTlv extends Tlv From 7ae4b1ad5e99dd51ed531ed50e20d53fe8e06523 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 3 May 2022 11:41:16 +0200 Subject: [PATCH 06/55] store local/remote aliases in channel data * We encode `localAlias` as a list of size 1 in case we want to support multiple aliases in the future * Relayers route payments based on real scid or local alias depending on features * This introduces RealShortChannelId/LocalAlias types: - we just decorate `ShortChannelId` with traits, which means `ShortChannelId`/`RealShortChannelId`/`LocalAlias` have the same equality method, in particular they share the same keyspace in maps - had to upgrade scala-maven-plugin due to weird compiler error --- .../scala/fr/acinq/eclair/BlockHeight.scala | 2 + .../fr/acinq/eclair/ShortChannelId.scala | 13 +- .../blockchain/bitcoind/ZmqWatcher.scala | 34 +++- .../watchdogs/BlockchainWatchdog.scala | 4 +- .../fr/acinq/eclair/channel/ChannelData.scala | 13 +- .../acinq/eclair/channel/ChannelEvents.scala | 37 +++- .../fr/acinq/eclair/channel/Helpers.scala | 90 +++++++-- .../fr/acinq/eclair/channel/Register.scala | 11 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 123 ++++++------ .../channel/fsm/ChannelOpenSingleFunder.scala | 51 +++-- .../fr/acinq/eclair/db/DualDatabases.scala | 7 +- .../scala/fr/acinq/eclair/db/NetworkDb.scala | 7 +- .../fr/acinq/eclair/db/pg/PgNetworkDb.scala | 9 +- .../eclair/db/sqlite/SqliteNetworkDb.scala | 9 +- .../main/scala/fr/acinq/eclair/package.scala | 3 +- .../eclair/payment/relay/ChannelRelayer.scala | 24 +-- .../remote/EclairInternalsSerializer.scala | 2 +- .../acinq/eclair/router/Announcements.scala | 7 +- .../scala/fr/acinq/eclair/router/Graph.scala | 3 +- .../acinq/eclair/router/NetworkEvents.scala | 3 +- .../eclair/router/RouteCalculation.scala | 4 +- .../scala/fr/acinq/eclair/router/Router.scala | 30 ++- .../scala/fr/acinq/eclair/router/Sync.scala | 29 +-- .../fr/acinq/eclair/router/Validation.scala | 40 ++-- .../channel/version0/ChannelCodecs0.scala | 23 ++- .../channel/version1/ChannelCodecs1.scala | 10 +- .../channel/version2/ChannelCodecs2.scala | 10 +- .../channel/version3/ChannelCodecs3.scala | 38 +++- .../eclair/wire/protocol/CommonCodecs.scala | 6 +- .../protocol/LightningMessageCodecs.scala | 8 +- .../wire/protocol/LightningMessageTypes.scala | 12 +- .../fr/acinq/eclair/EclairImplSpec.scala | 11 +- .../fr/acinq/eclair/ShortChannelIdSpec.scala | 13 ++ .../scala/fr/acinq/eclair/TestUtils.scala | 2 + .../blockchain/bitcoind/ZmqWatcherSpec.scala | 13 +- .../fr/acinq/eclair/channel/HelpersSpec.scala | 17 +- .../ChannelStateTestsHelperMethods.scala | 24 ++- .../a/WaitForOpenChannelStateSpec.scala | 2 +- .../b/WaitForFundingSignedStateSpec.scala | 16 +- .../c/WaitForChannelReadyStateSpec.scala | 64 +++++- .../c/WaitForFundingConfirmedStateSpec.scala | 4 +- .../channel/states/e/NormalStateSpec.scala | 125 +++++++++--- .../channel/states/e/OfflineStateSpec.scala | 6 +- .../fr/acinq/eclair/db/AuditDbSpec.scala | 2 +- .../fr/acinq/eclair/db/ChannelsDbSpec.scala | 3 +- .../fr/acinq/eclair/db/NetworkDbSpec.scala | 17 +- .../integration/PaymentIntegrationSpec.scala | 2 +- .../acinq/eclair/io/PeerConnectionSpec.scala | 3 +- .../eclair/payment/PaymentLifecycleSpec.scala | 2 +- .../payment/relay/ChannelRelayerSpec.scala | 190 ++++++++++-------- .../eclair/payment/relay/RelayerSpec.scala | 2 +- .../AnnouncementsBatchValidationSpec.scala | 2 +- .../eclair/router/AnnouncementsSpec.scala | 5 +- .../acinq/eclair/router/BaseRouterSpec.scala | 19 +- .../router/ChannelRangeQueriesSpec.scala | 37 ++-- .../eclair/router/RouteCalculationSpec.scala | 9 +- .../fr/acinq/eclair/router/RouterSpec.scala | 37 ++-- .../acinq/eclair/router/RoutingSyncSpec.scala | 15 +- .../internal/channel/ChannelCodecsSpec.scala | 11 +- .../channel/version3/ChannelCodecs3Spec.scala | 2 +- .../protocol/ExtendedQueriesCodecsSpec.scala | 18 +- .../protocol/LightningMessageCodecsSpec.scala | 35 ++-- .../fr/acinq/eclair/router/FrontRouter.scala | 11 +- .../acinq/eclair/router/FrontRouterSpec.scala | 2 +- .../acinq/eclair/api/handlers/Channel.scala | 2 +- 65 files changed, 901 insertions(+), 484 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/BlockHeight.scala b/eclair-core/src/main/scala/fr/acinq/eclair/BlockHeight.scala index f00c70f40b..0a22160df0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/BlockHeight.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/BlockHeight.scala @@ -36,6 +36,8 @@ case class BlockHeight(private val underlying: Long) extends Ordered[BlockHeight def toInt: Int = underlying.toInt def toLong: Long = underlying def toDouble: Double = underlying.toDouble + + override def toString() = underlying.toString // @formatter:on } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index 44fc67ff6c..7d8a0846d9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -16,6 +16,9 @@ package fr.acinq.eclair +sealed trait RealShortChannelId extends ShortChannelId +sealed trait LocalAlias extends ShortChannelId + /** * A short channel id uniquely identifies a channel by the coordinates of its funding tx output in the blockchain. * See BOLT 7: https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#requirements @@ -24,6 +27,12 @@ case class ShortChannelId(private val id: Long) extends Ordered[ShortChannelId] def toLong: Long = id + /** Careful: only call this if you are sure that this scid is actually a real scid */ + def toReal: RealShortChannelId = new ShortChannelId(id) with RealShortChannelId + + /** Careful: only call this if you are sure that this scid is actually a local alias */ + def toAlias: LocalAlias = new ShortChannelId(id) with LocalAlias + def blockHeight = ShortChannelId.blockHeight(this) override def toString: String = { @@ -42,7 +51,7 @@ object ShortChannelId { case _ => throw new IllegalArgumentException(s"Invalid short channel id: $s") } - def apply(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int): ShortChannelId = ShortChannelId(toShortId(blockHeight.toInt, txIndex, outputIndex)) + def apply(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int): RealShortChannelId = ShortChannelId(toShortId(blockHeight.toInt, txIndex, outputIndex)).toReal def toShortId(blockHeight: Int, txIndex: Int, outputIndex: Int): Long = ((blockHeight & 0xFFFFFFL) << 40) | ((txIndex & 0xFFFFFFL) << 16) | (outputIndex & 0xFFFFL) @@ -56,6 +65,8 @@ object ShortChannelId { def outputIndex(shortChannelId: ShortChannelId): Int = (shortChannelId.id & 0xFFFF).toInt def coordinates(shortChannelId: ShortChannelId): TxCoordinates = TxCoordinates(blockHeight(shortChannelId), txIndex(shortChannelId), outputIndex(shortChannelId)) + + def generateLocalAlias(): LocalAlias = new ShortChannelId(System.nanoTime()) with LocalAlias // TODO: fixme (duplicate, etc.) } case class TxCoordinates(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int) \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index 536f205539..f0b8d4f885 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -20,6 +20,7 @@ import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import fr.acinq.bitcoin.scalacompat._ +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.Monitoring.Metrics import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient @@ -30,7 +31,7 @@ import fr.acinq.eclair.{BlockHeight, KamonExt, NodeParams, ShortChannelId, Times import java.util.concurrent.atomic.AtomicLong import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} +import scala.util.{Failure, Random, Success} /** * Created by PM on 21/02/2016. @@ -136,8 +137,8 @@ object ZmqWatcher { /** This event is sent when a [[WatchSpentBasic]] condition is met. */ sealed trait WatchSpentBasicTriggered extends WatchTriggered - case class WatchExternalChannelSpent(replyTo: ActorRef[WatchExternalChannelSpentTriggered], txId: ByteVector32, outputIndex: Int, shortChannelId: ShortChannelId) extends WatchSpentBasic[WatchExternalChannelSpentTriggered] - case class WatchExternalChannelSpentTriggered(shortChannelId: ShortChannelId) extends WatchSpentBasicTriggered + case class WatchExternalChannelSpent(replyTo: ActorRef[WatchExternalChannelSpentTriggered], txId: ByteVector32, outputIndex: Int, shortChannelId: RealShortChannelId) extends WatchSpentBasic[WatchExternalChannelSpentTriggered] + case class WatchExternalChannelSpentTriggered(shortChannelId: RealShortChannelId) extends WatchSpentBasicTriggered case class WatchFundingSpent(replyTo: ActorRef[WatchFundingSpentTriggered], txId: ByteVector32, outputIndex: Int, hints: Set[ByteVector32]) extends WatchSpent[WatchFundingSpentTriggered] case class WatchFundingSpentTriggered(spendingTx: Transaction) extends WatchSpentTriggered @@ -236,6 +237,11 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client case _: WatchConfirmed[_] => // nothing to do case _: WatchFundingLost => // nothing to do } + watches + .collect { + case w: WatchFundingConfirmed if w.minDepth == 0 && w.txId == tx.txid => + checkConfirmed(w) + } Behaviors.same case ProcessNewBlock(blockHash) => @@ -406,13 +412,21 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client client.getTxConfirmations(w.txId).flatMap { case Some(confirmations) if confirmations >= w.minDepth => client.getTransaction(w.txId).flatMap { tx => - client.getTransactionShortId(w.txId).map { - case (height, index) => w match { - case w: WatchFundingConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(height, index, tx)) - case w: WatchFundingDeeplyBuried => context.self ! TriggerEvent(w.replyTo, w, WatchFundingDeeplyBuriedTriggered(height, index, tx)) - case w: WatchTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchTxConfirmedTriggered(height, index, tx)) - case w: WatchParentTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchParentTxConfirmedTriggered(height, index, tx)) - } + w match { + case w: WatchFundingConfirmed if confirmations == 0 && w.minDepth == 0 => + // if the tx doesn't have confirmations but we don't require any, we reply with a fake block index + // otherwise, we get the real short id + context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(BlockHeight(0), 0, tx)) + Future.successful((): Unit) + case _ => + client.getTransactionShortId(w.txId).map { + case (height, index) => w match { + case w: WatchFundingConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(height, index, tx)) + case w: WatchFundingDeeplyBuried => context.self ! TriggerEvent(w.replyTo, w, WatchFundingDeeplyBuriedTriggered(height, index, tx)) + case w: WatchTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchTxConfirmedTriggered(height, index, tx)) + case w: WatchParentTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchParentTxConfirmedTriggered(height, index, tx)) + } + } } } case _ => Future.successful((): Unit) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdog.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdog.scala index 3309037414..0b7825aef4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdog.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/watchdogs/BlockchainWatchdog.scala @@ -104,10 +104,10 @@ object BlockchainWatchdog { case CheckLatestHeaders(blockHeight) => val id = UUID.randomUUID() if (headersOverDnsEnabled) { - context.spawn(HeadersOverDns(nodeParams.chainHash, blockHeight), s"${HeadersOverDns.Source}-${blockHeight.toLong}-$id") ! HeadersOverDns.CheckLatestHeaders(context.self) + context.spawn(HeadersOverDns(nodeParams.chainHash, blockHeight), s"${HeadersOverDns.Source}-${blockHeight}-$id") ! HeadersOverDns.CheckLatestHeaders(context.self) } explorers.foreach { explorer => - context.spawn(ExplorerApi(nodeParams.chainHash, blockHeight, explorer), s"${explorer.name}-${blockHeight.toLong}-$id") ! ExplorerApi.CheckLatestHeaders(context.self) + context.spawn(ExplorerApi(nodeParams.chainHash, blockHeight, explorer), s"${explorer.name}-${blockHeight}-$id") ! ExplorerApi.CheckLatestHeaders(context.self) } Behaviors.same case headers@LatestHeaders(blockHeight, blockHeaders, source) => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 9e39078799..d4687bd306 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -18,13 +18,13 @@ package fr.acinq.eclair.channel import akka.actor.{ActorRef, PossiblyHarmful} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, ShortChannelId, UInt64} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, UInt64} import scodec.bits.ByteVector import java.util.UUID @@ -426,12 +426,17 @@ final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, waitingSince: BlockHeight, // how long have we been waiting for the funding tx to confirm deferred: Option[ChannelReady], lastSent: Either[FundingCreated, FundingSigned]) extends PersistentChannelData -final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, shortChannelId: ShortChannelId, lastSent: ChannelReady) extends PersistentChannelData +final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, + realShortChannelId_opt: Option[RealShortChannelId], + localAlias: LocalAlias, + lastSent: ChannelReady) extends PersistentChannelData final case class DATA_NORMAL(commitments: Commitments, - shortChannelId: ShortChannelId, + realShortChannelId_opt: Option[RealShortChannelId], buried: Boolean, channelAnnouncement: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, + localAlias: LocalAlias, + remoteAlias_opt: Option[ShortChannelId], localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], closingFeerates: Option[ClosingFeerates]) extends PersistentChannelData diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index 236acffead..a4a46ee9fe 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel import akka.actor.ActorRef import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, Transaction} -import fr.acinq.eclair.{BlockHeight, ShortChannelId} +import fr.acinq.eclair.{BlockHeight, Features, LocalAlias, RealShortChannelId, ShortChannelId} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.Helpers.Closing.ClosingType import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} @@ -44,13 +44,36 @@ case class ChannelRestored(channel: ActorRef, channelId: ByteVector32, peer: Act case class ChannelIdAssigned(channel: ActorRef, remoteNodeId: PublicKey, temporaryChannelId: ByteVector32, channelId: ByteVector32) extends ChannelEvent -case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, shortChannelId: ShortChannelId, previousShortChannelId: Option[ShortChannelId]) extends ChannelEvent - -case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent +/** + * This event will be sent whenever a new scid is assigned to the channel, be it a real, local alias or remote alias. + * + * @param realShortChannelId_opt the real scid, it can change in case of a reorg before the channel reaches 6 conf + * @param localAlias we must remember the alias that we sent to our peer because we use it to: + * - identify incoming [[ChannelUpdate]] + * - route outgoing payments to that channel + * @param remoteAlias_opt we only remember the last alias received from our peer, we use this to generate + * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] + */ +case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteAlias_opt: Option[ShortChannelId]) extends ChannelEvent + +case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent { + /** + * We always map the local alias because we must always be able to route based on it + * However we only map the real scid if option_scid_alias (TODO: rename to option_scid_privacy) is disabled + */ + def scidsForRouting: Seq[ShortChannelId] = { + commitments match { + case c: Commitments => + val realScid_opt = if (c.channelFeatures.hasFeature(Features.ScidAlias)) None else realShortChannelId_opt + realScid_opt.toSeq :+ localAlias + case _ => Seq(localAlias) // TODO: ugly + } + } +} -case class ChannelUpdateParametersChanged(channel: ActorRef, channelId: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey, channelUpdate: ChannelUpdate) extends ChannelEvent +case class ChannelUpdateParametersChanged(channel: ActorRef, channelId: ByteVector32, remoteNodeId: PublicKey, channelUpdate: ChannelUpdate) extends ChannelEvent -case class LocalChannelDown(channel: ActorRef, channelId: ByteVector32, shortChannelId: ShortChannelId, remoteNodeId: PublicKey) extends ChannelEvent +case class LocalChannelDown(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteNodeId: PublicKey) extends ChannelEvent case class ChannelStateChanged(channel: ActorRef, channelId: ByteVector32, peer: ActorRef, remoteNodeId: PublicKey, previousState: ChannelState, currentState: ChannelState, commitments_opt: Option[AbstractCommitments]) extends ChannelEvent @@ -66,7 +89,7 @@ case class TransactionPublished(channelId: ByteVector32, remoteNodeId: PublicKey case class TransactionConfirmed(channelId: ByteVector32, remoteNodeId: PublicKey, tx: Transaction) extends ChannelEvent // NB: this event is only sent when the channel is available. -case class AvailableBalanceChanged(channel: ActorRef, channelId: ByteVector32, shortChannelId: ShortChannelId, commitments: AbstractCommitments) extends ChannelEvent +case class AvailableBalanceChanged(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, commitments: AbstractCommitments) extends ChannelEvent case class ChannelPersisted(channel: ActorRef, remoteNodeId: PublicKey, channelId: ByteVector32, data: PersistentChannelData) extends ChannelEvent diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 2f2617b658..7d8eec8493 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat._ -import fr.acinq.eclair._ +import fr.acinq.eclair.{RealShortChannelId, _} import fr.acinq.eclair.blockchain.OnChainAddressGenerator import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratePerKw} import fr.acinq.eclair.channel.fsm.Channel @@ -65,22 +65,6 @@ object Helpers { } } - /** - * Returns the number of confirmations needed to safely handle the funding transaction, - * we make sure the cumulative block reward largely exceeds the channel size. - * - * @param fundingSatoshis funding amount of the channel - * @return number of confirmations needed - */ - def minDepthForFunding(channelConf: ChannelConf, fundingSatoshis: Satoshi): Long = fundingSatoshis match { - case funding if funding <= Channel.MAX_FUNDING => channelConf.minDepthBlocks - case funding => - val blockReward = 6.25 // this is true as of ~May 2020, but will be too large after 2024 - val scalingFactor = 15 - val blocksToReachFunding = (((scalingFactor * funding.toBtc.toDouble) / blockReward).ceil + 1).toInt - channelConf.minDepthBlocks.max(blocksToReachFunding) - } - def extractShutdownScript(channelId: ByteVector32, localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector]): Either[ChannelException, Option[ByteVector]] = { val canUseUpfrontShutdownScript = Features.canUseFeature(localFeatures, remoteFeatures, Features.UpfrontShutdownScript) val canUseAnySegwit = Features.canUseFeature(localFeatures, remoteFeatures, Features.ShutdownAnySegwit) @@ -201,6 +185,43 @@ object Helpers { extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt)) } + /** + * The short channel id used in channel_update depends on multiple factors: + * - is the channel public or private? + * - do we have a real scid? + * - do we have a remote alias? + * + * {{{ + * - received remote_alias from peer + * - public : + * - before 6 blocks : use remote_alias + * - after 6 blocks : use real scid + * - private : use remote_alias + * - no remote_alias from peer + * - min_depth > 0 : use real scid (may change if reorg between min_depth and 6 conf) + * - min_depth = 0 (zero-conf) : unsupported + * }}} + */ + def scidForChannelUpdate(channelFlags: ChannelFlags, realShortChannelId_opt: Option[ShortChannelId], remoteAlias_opt: Option[ShortChannelId])(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { + val scid_opt = if (channelFlags.announceChannel) { + // public channel: we prefer the real scid + realShortChannelId_opt.orElse(remoteAlias_opt) + } else { + // private channel: we prefer the remote alias + remoteAlias_opt.orElse(realShortChannelId_opt) + } + scid_opt.getOrElse { + // TODO A bit hacky! + // Our model requires a channel_update in DATA_NORMAL. But if we are in zero-conf and our peer + // does not send an alias, then we have neither a remote alias, nor a real scid. This should never happen, so + // we default to a ShortChannelId(0) which should never be used. + log.warning("no real scid and no alias!! this should never happen") + ShortChannelId(0) + } + } + + def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.commitments.channelFlags, d.realShortChannelId_opt, d.remoteAlias_opt) + /** * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by * other nodes. @@ -226,7 +247,7 @@ object Helpers { remoteFeeratePerKw < FeeratePerKw.MinimumFeeratePerKw } - def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: ShortChannelId): AnnouncementSignatures = { + def makeAnnouncementSignatures(nodeParams: NodeParams, commitments: Commitments, shortChannelId: RealShortChannelId): AnnouncementSignatures = { val features = Features.empty[Feature] // empty features for now val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(commitments.localParams.fundingKeyPath) val witness = Announcements.generateChannelAnnouncementWitness( @@ -278,6 +299,39 @@ object Helpers { object Funding { + /** + * As funder we trust ourselves to not double spend funding txs: we could always use a zero-confirmation watch, + * but we need a scid to send the initial channel_update and remote may not provide an alias (and we don't want to + * trust the real scid sent by remote in their channel_ready). So we always wait for one conf, except if the channel + * has the zero-conf feature (because presumably the peer will sends an alias in that case). + * + * @return + */ + def minDepthFunder(channelFeatures: ChannelFeatures): Long = { + if (channelFeatures.hasFeature(Features.ZeroConf)) { + 0 + } else { + 1 + } + } + + /** + * Returns the number of confirmations needed to safely handle the funding transaction, + * we make sure the cumulative block reward largely exceeds the channel size. + * + * @param fundingSatoshis funding amount of the channel + * @return number of confirmations needed + */ + def minDepthFundee(channelConf: ChannelConf, channelFeatures: ChannelFeatures, fundingSatoshis: Satoshi): Long = fundingSatoshis match { + case _ if channelFeatures.hasFeature(Features.ZeroConf) => 0 // zero-conf stay zero-conf, whatever the funding amount is + case funding if funding <= Channel.MAX_FUNDING => channelConf.minDepthBlocks + case funding => + val blockReward = 6.25 // this is true as of ~May 2020, but will be too large after 2024 + val scalingFactor = 15 + val blocksToReachFunding = (((scalingFactor * funding.toBtc.toDouble) / blockReward).ceil + 1).toInt + channelConf.minDepthBlocks.max(blocksToReachFunding) + } + def makeFundingInputInfo(fundingTxId: ByteVector32, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = { val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2) val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala index 8e949012dc..57c906a1f6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala @@ -47,13 +47,16 @@ class Register extends Actor with ActorLogging { case ChannelIdAssigned(channel, remoteNodeId, temporaryChannelId, channelId) => context become main(channels + (channelId -> channel) - temporaryChannelId, shortIds, channelsTo + (channelId -> remoteNodeId) - temporaryChannelId) - case ShortChannelIdAssigned(_, channelId, shortChannelId, _) => - context become main(channels, shortIds + (shortChannelId -> channelId), channelsTo) + case scidAssigned: ShortChannelIdAssigned => + // We map all known scids (real or alias) to the channel_id. The relayer is in charge of deciding whether a real + // scid can be used or not for routing (see option_scid_privacy), but the register is neutral. + val m = (scidAssigned.realShortChannelId_opt.toSeq :+ scidAssigned.localAlias).map(_ -> scidAssigned.channelId).toMap + context become main(channels, shortIds ++ m, channelsTo) case Terminated(actor) if channels.values.toSet.contains(actor) => val channelId = channels.find(_._2 == actor).get._1 - val shortChannelId = shortIds.find(_._2 == channelId).map(_._1).getOrElse(ShortChannelId(0L)) - context become main(channels - channelId, shortIds - shortChannelId, channelsTo - channelId) + val shortChannelIds = shortIds.collect { case (key, value) if value == channelId => key } + context become main(channels - channelId, shortIds -- shortChannelIds, channelsTo - channelId) case Symbol("channels") => sender() ! channels 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 faa6c067ec..b9a7231a1e 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 @@ -31,7 +31,7 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient import fr.acinq.eclair.channel.Commitments.PostRevocationAction import fr.acinq.eclair.channel.Helpers.Syncing.SyncResult -import fr.acinq.eclair.channel.Helpers.{Closing, Syncing, getRelayFees} +import fr.acinq.eclair.channel.Helpers.{Closing, Syncing, getRelayFees, scidForChannelUpdate} import fr.acinq.eclair.channel.Monitoring.Metrics.ProcessMessage import fr.acinq.eclair.channel.Monitoring.{Metrics, Tags} import fr.acinq.eclair.channel._ @@ -299,7 +299,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case normal: DATA_NORMAL => watchFundingTx(data.commitments) - context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, normal.channelUpdate.shortChannelId, None)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, realShortChannelId_opt = normal.realShortChannelId_opt, localAlias = normal.localAlias, remoteAlias_opt = normal.remoteAlias_opt)) // we check the configuration because the values for channel_update may have changed while eclair was down val fees = getRelayFees(nodeParams, remoteNodeId, data.commitments) @@ -355,7 +355,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendAdd(d.commitments, c, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf) match { case Right((commitments1, add)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending add case Left(cause) => handleAddHtlcCommandError(c, cause, Some(d.channelUpdate)) } @@ -370,7 +370,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFulfill(d.commitments, c) match { case Right((commitments1, fulfill)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fulfill case Left(cause) => // we acknowledge the command right away in case of failure @@ -390,7 +390,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFail(d.commitments, c, nodeParams.privateKey) match { case Right((commitments1, fail)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fail case Left(cause) => // we acknowledge the command right away in case of failure @@ -401,7 +401,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFailMalformed(d.commitments, c) match { case Right((commitments1, fail)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fail case Left(cause) => // we acknowledge the command right away in case of failure @@ -424,7 +424,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFee(d.commitments, c, nodeParams.onChainFeeConf) match { case Right((commitments1, fee)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fee case Left(cause) => handleCommandError(cause, c) } @@ -481,7 +481,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } if (d.commitments.availableBalanceForSend != commitments1.availableBalanceForSend) { // we send this event only when our balance changes - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortChannelId, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) } context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1)) stay() using d.copy(commitments = commitments1) storing() sending revocation @@ -614,33 +614,39 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(c: CurrentFeerates, d: DATA_NORMAL) => handleCurrentFeerate(c, d) case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, _), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => - val shortChannelId = ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt) - log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=$shortChannelId") - // if final shortChannelId is different from the one we had before, we need to re-announce it - val channelUpdate = if (shortChannelId != d.shortChannelId) { - log.info(s"short channel id changed, probably due to a chain reorg: old=${d.shortChannelId} new=$shortChannelId") - // we need to re-announce this shortChannelId - context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId, Some(d.shortChannelId))) + val realShortChannelId = ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt) + log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=$realShortChannelId") + if (!d.realShortChannelId_opt.contains(realShortChannelId)) { + log.info(s"setting final real scid: old=${d.realShortChannelId_opt.getOrElse("empty")} new=$realShortChannelId") + // we announce the new shortChannelId + context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, realShortChannelId_opt = Some(realShortChannelId), localAlias = d.localAlias, remoteAlias_opt = d.remoteAlias_opt)) + } + val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.commitments.channelFlags, Some(realShortChannelId), d.remoteAlias_opt) + // if the shortChannelId is different from the one we had before, we need to re-announce it + val channelUpdate1 = if (d.channelUpdate.shortChannelId != scidForChannelUpdate) { + log.info(s"using new scid in channel_update: old=${d.channelUpdate.shortChannelId} new=$scidForChannelUpdate") // we re-announce the channelUpdate for the same reason - Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) + Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) } else d.channelUpdate val localAnnSigs_opt = if (d.commitments.announceChannel) { // if channel is public we need to send our announcement_signatures in order to generate the channel_announcement - Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId)) + Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, realShortChannelId)) } else None // we use goto() instead of stay() because we want to fire transitions - goto(NORMAL) using d.copy(shortChannelId = shortChannelId, buried = true, channelUpdate = channelUpdate) storing() sending localAnnSigs_opt.toSeq + goto(NORMAL) using d.copy(realShortChannelId_opt = Some(realShortChannelId), buried = true, channelUpdate = channelUpdate1) storing() sending localAnnSigs_opt.toSeq case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_NORMAL) if d.commitments.announceChannel => // channels are publicly announced if both parties want it (defined as feature bit) if (d.buried) { + // must be defined if tx is buried + val shortChannelId = d.realShortChannelId_opt.get // we are aware that the channel has reached enough confirmations // we already had sent our announcement_signatures but we don't store them so we need to recompute it - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, d.shortChannelId) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId) d.channelAnnouncement match { case None => - require(d.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${d.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") - log.info(s"announcing channelId=${d.channelId} on the network with shortId=${d.shortChannelId}") + require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") + log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}") import d.commitments.{localParams, remoteParams} val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, fundingPubKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) @@ -668,7 +674,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } case Event(c: CMD_UPDATE_RELAY_FEE, d: DATA_NORMAL) => - val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) + val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) log.info(s"updating relay fees: prev={} next={}", d.channelUpdate.toStringShort, channelUpdate1.toStringShort) val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo replyTo ! RES_SUCCESS(c, d.channelId) @@ -677,7 +683,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(BroadcastChannelUpdate(reason), d: DATA_NORMAL) => val age = TimestampSecond.now() - d.channelUpdate.timestamp - 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 = Helpers.aboveReserve(d.commitments)) + val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) reason match { case Reconnected if d.commitments.announceChannel && Announcements.areSame(channelUpdate1, d.channelUpdate) && age < REFRESH_CHANNEL_UPDATE_INTERVAL => // we already sent an identical channel_update not long ago (flapping protection in case we keep being disconnected/reconnected) @@ -701,7 +707,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // 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) { 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) + val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), 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)) @@ -1307,10 +1313,10 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val when(SYNCING)(handleExceptions { case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => val minDepth = if (d.commitments.localParams.isInitiator) { - nodeParams.channelConf.minDepthBlocks + Helpers.Funding.minDepthFunder(d.commitments.channelFeatures) } else { // when we're not the channel initiator we scale the min_depth confirmations depending on the funding amount - Helpers.minDepthForFunding(nodeParams.channelConf, d.commitments.commitInput.txOut.amount) + Helpers.Funding.minDepthFundee(nodeParams.channelConf, d.commitments.channelFeatures, d.commitments.commitInput.txOut.amount) } // we put back the watch (operation is idempotent) because the event may have been fired while we were in OFFLINE blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, minDepth) @@ -1377,7 +1383,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val () case None => // BOLT 7: a node SHOULD retransmit the announcement_signatures message if it has not received an announcement_signatures message - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, d.shortChannelId) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, d.realShortChannelId_opt.get) sendQueue = sendQueue :+ localAnnSigs case Some(_) => // channel was already announced, nothing to do @@ -1593,44 +1599,43 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val cancelTimer(RevocationTimeout.toString) } - // if channel is private, we send the channel_update directly to remote - // they need it "to learn the other end's forwarding parameters" (BOLT 7) - (state, nextState, stateData, nextStateData) match { - case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && !d1.buried && d2.buried => - // for a private channel, when the tx was just buried we need to send the channel_update to our peer (even if it didn't change) - send(d2.channelUpdate) - case (SYNCING, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && d2.buried => - // otherwise if we're coming back online, we rebroadcast the latest channel_update - // this makes sure that if the channel_update was missed, we have a chance to re-send it - send(d2.channelUpdate) - case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if !d1.commitments.announceChannel && d1.channelUpdate != d2.channelUpdate && d2.buried => - // otherwise, we only send it when it is different, and tx is already buried - send(d2.channelUpdate) - case _ => () - } - sealed trait EmitLocalChannelEvent - case class EmitLocalChannelUpdate(d: DATA_NORMAL) extends EmitLocalChannelEvent + /** + * This event is for: + * - the router: so that it knows about this channel to find routes + * - the relayers: so they can learn about the channel scid/alias and route to it + * - the peer: so they can "learn the other end's forwarding parameters" (BOLT 7) + * + * @param sendToPeer indicates whether we should send the channel_update directly to our peer + */ + case class EmitLocalChannelUpdate(reason: String, d: DATA_NORMAL, sendToPeer: Boolean) extends EmitLocalChannelEvent + /** + * When a channel that could previously be used to relay payments starts closing, we advertise the fact that this + * channel can't be used for payments anymore. If the channel is private we don't really need to tell the + * counterparty because it is already aware that the channel is being closed + */ case class EmitLocalChannelDown(d: DATA_NORMAL) extends EmitLocalChannelEvent + // We only send the channel_update directly to the peer if we are connected AND the channel hasn't been announced val emitEvent_opt: Option[EmitLocalChannelEvent] = (state, nextState, stateData, nextStateData) match { - case (WAIT_FOR_INIT_INTERNAL, OFFLINE, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate(d)) - case (WAIT_FOR_CHANNEL_READY, NORMAL, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate(d)) - case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) - case (SYNCING, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) - case (NORMAL, OFFLINE, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) - case (OFFLINE, OFFLINE, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate(d2)) - // When a channel that could previously be used to relay payments starts closing, we advertise the fact that this channel can't be used for payments anymore - // If the channel is private we don't really need to tell the counterparty because it is already aware that the channel is being closed + case (WAIT_FOR_INIT_INTERNAL, OFFLINE, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate("restore", d, sendToPeer = false)) + 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 && d2.buried)) + 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 && d2.buried)) + 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)) case (NORMAL | SYNCING | OFFLINE, SHUTDOWN | NEGOTIATING | CLOSING | CLOSED | ERR_INFORMATION_LEAK | WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, d: DATA_NORMAL, _) => Some(EmitLocalChannelDown(d)) case _ => None } emitEvent_opt.foreach { - case EmitLocalChannelUpdate(d) => - log.info("emitting channel_update={} enabled={} ", d.channelUpdate, d.channelUpdate.channelFlags.isEnabled) - context.system.eventStream.publish(LocalChannelUpdate(self, d.channelId, d.shortChannelId, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments)) + case EmitLocalChannelUpdate(reason, d, sendToPeer) => + log.info(s"emitting channel update: reason={} enabled={} sendToPeer={} realScid=${d.realShortChannelId_opt} channel_update={}", reason, d.channelUpdate.channelFlags.isEnabled, sendToPeer, d.channelUpdate) + context.system.eventStream.publish(LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments)) + if (sendToPeer) { + send(d.channelUpdate) + } case EmitLocalChannelDown(d) => - context.system.eventStream.publish(LocalChannelDown(self, d.channelId, d.shortChannelId, d.commitments.remoteParams.nodeId)) + context.system.eventStream.publish(LocalChannelDown(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId)) } // When we change our channel update parameters (e.g. relay fees), we want to advertise it to other actors. @@ -1784,7 +1789,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val if (d.channelUpdate.channelFlags.isEnabled) { // if the channel isn't disabled we generate a new channel_update log.info("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) + val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = false) // then we update the state and replay the request self forward c // we use goto() to fire transitions @@ -1797,7 +1802,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } private def handleUpdateRelayFeeDisconnected(c: CMD_UPDATE_RELAY_FEE, d: DATA_NORMAL) = { - val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = false) + val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate(d), c.cltvExpiryDelta_opt.getOrElse(d.channelUpdate.cltvExpiryDelta), d.channelUpdate.htlcMinimumMsat, c.feeBase, c.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = false) log.info(s"updating relay fees: prev={} next={}", d.channelUpdate.toStringShort, channelUpdate1.toStringShort) val replyTo = if (c.replyTo == ActorRef.noSender) sender() else c.replyTo replyTo ! RES_SUCCESS(c, d.channelId) @@ -1834,7 +1839,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val private def maybeEmitChannelUpdateChangedEvent(newUpdate: ChannelUpdate, oldUpdate_opt: Option[ChannelUpdate], d: DATA_NORMAL): Unit = { if (oldUpdate_opt.isEmpty || !Announcements.areSameIgnoreFlags(newUpdate, oldUpdate_opt.get)) { - context.system.eventStream.publish(ChannelUpdateParametersChanged(self, d.channelId, newUpdate.shortChannelId, d.commitments.remoteNodeId, newUpdate)) + context.system.eventStream.publish(ChannelUpdateParametersChanged(self, d.channelId, d.commitments.remoteNodeId, newUpdate)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index e6f088561e..c58d3e7b3e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -31,8 +31,8 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.TxOwner import fr.acinq.eclair.transactions.{Scripts, Transactions} -import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelTlv, Error, FundingCreated, ChannelReady, FundingSigned, OpenChannel, TlvStream} -import fr.acinq.eclair.{Features, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelReady, ChannelReadyTlv, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream} +import fr.acinq.eclair.{BlockHeight, Features, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} import scodec.bits.ByteVector import scala.concurrent.duration.DurationInt @@ -80,7 +80,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelCreated(self, peer, remoteNodeId, isInitiator = false, open.temporaryChannelId, open.feeratePerKw, None)) val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val minimumDepth = if (channelFeatures.hasFeature(Features.ZeroConf)) 0 else Helpers.minDepthForFunding(nodeParams.channelConf, open.fundingSatoshis) + val minimumDepth = Funding.minDepthFundee(nodeParams.channelConf, channelFeatures, open.fundingSatoshis) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. val localShutdownScript = if (Features.canUseFeature(localParams.initFeatures, remoteInit.features, Features.UpfrontShutdownScript)) localParams.defaultFinalScriptPubKey else ByteVector.empty @@ -151,6 +151,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { initFeatures = remoteInit.features, shutdownScript = remoteShutdownScript) log.debug("remote params: {}", remoteParams) + log.info("remote indicates they are going to use fundingMinDepth={}", accept.minimumDepth) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey))) wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeerate).pipeTo(self) @@ -254,7 +255,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { // NB: we don't send a ChannelSignatureSent for the first commit log.info(s"waiting for them to publish the funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") watchFundingTx(commitments) - val fundingMinDepth = Helpers.minDepthForFunding(nodeParams.channelConf, fundingAmount) + val fundingMinDepth = Funding.minDepthFundee(nodeParams.channelConf, commitments.channelFeatures, fundingAmount) blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, None, nodeParams.currentBlockHeight, None, Right(fundingSigned)) storing() sending fundingSigned } @@ -294,7 +295,8 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") watchFundingTx(commitments) - blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks) + val fundingMinDepth = Funding.minDepthFunder(commitments.channelFeatures) + blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) log.info(s"committing txid=${fundingTx.txid}") // we will publish the funding tx only after the channel state has been written to disk because we want to @@ -355,13 +357,28 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(TransactionConfirmed(commitments.channelId, remoteNodeId, fundingTx)) val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) - val channelReady = ChannelReady(commitments.channelId, nextPerCommitmentPoint) deferred.foreach(self ! _) - // this is the temporary channel id that we will use in our channel_update message, the goal is to be able to use our channel + // this is the real scid, it might change when the funding tx gets deeply buried (if there was a reorg in the meantime) + val realShortChannelId_opt = if (blockHeight == BlockHeight(0)) { + // If we are using zero-conf then the transaction may not have been confirmed yet, that's why the block + // height is zero. In some cases (e.g. we were down for some time) the tx may actually have confirmed, in + // that case we will have a real scid even if the channel is zero-conf + None + } + else { + Some(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) + } + // the alias will use in our channel_update message, the goal is to be able to use our channel // as soon as it reaches NORMAL state, and before it is announced on the network - // (this id might be updated when the funding tx gets deeply buried, if there was a reorg in the meantime) - val shortChannelId = ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt) - goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortChannelId, channelReady) storing() sending channelReady + val localAlias = ShortChannelId.generateLocalAlias() + // we always send our local alias, even if it isn't explicitly supported, that's an optional TLV anyway + val channelReady = ChannelReady( + channelId = commitments.channelId, + nextPerCommitmentPoint = nextPerCommitmentPoint, + tlvStream = TlvStream(ChannelReadyTlv.ShortChannelIdTlv(localAlias)) + ) + + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, channelReady) storing() sending channelReady case Failure(t) => log.error(t, s"rejecting channel with invalid funding tx: ${fundingTx.bin}") goto(CLOSED) @@ -397,16 +414,20 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { }) when(WAIT_FOR_CHANNEL_READY)(handleExceptions { - case Event(ChannelReady(_, nextPerCommitmentPoint, _), d@DATA_WAIT_FOR_CHANNEL_READY(commitments, shortChannelId, _)) => + case Event(channelReady: ChannelReady, d@DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt, localAlias, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) - context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId, None)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = channelReady.alias_opt)) // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced - val fees = getRelayFees(nodeParams, remoteNodeId, commitments) - val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, fees.feeBase, fees.feeProportionalMillionths, commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) + val remoteAlias_opt = channelReady.alias_opt + remoteAlias_opt.foreach(remoteAlias => log.info("received remoteAlias={}", remoteAlias)) + val scidForChannelUpdate = Helpers.scidForChannelUpdate(commitments.channelFlags, realShortChannelId_opt, remoteAlias_opt) + log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) + val relayFees = getRelayFees(nodeParams, remoteNodeId, commitments) + val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, relayFees.feeBase, relayFees.feeProportionalMillionths, commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) // we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network context.system.scheduler.scheduleWithFixedDelay(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, delay = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh)) - goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None, None) storing() + goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), realShortChannelId_opt = realShortChannelId_opt, buried = false, None, initialChannelUpdate, localAlias = localAlias, remoteAlias_opt = remoteAlias_opt, None, None, None) storing() case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_CHANNEL_READY) if d.commitments.announceChannel => log.debug("received remote announcement signatures, delaying") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala index 9b9d5056e2..1fa137dcf8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/DualDatabases.scala @@ -2,6 +2,7 @@ package fr.acinq.eclair.db import com.google.common.util.concurrent.ThreadFactoryBuilder import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.channel._ import fr.acinq.eclair.db.Databases.{FileBackup, PostgresDatabases, SqliteDatabases} import fr.acinq.eclair.db.DbEventHandler.ChannelEvent @@ -113,17 +114,17 @@ case class DualNetworkDb(primary: NetworkDb, secondary: NetworkDb) extends Netwo primary.removeChannels(shortChannelIds) } - override def listChannels(): SortedMap[ShortChannelId, Router.PublicChannel] = { + override def listChannels(): SortedMap[RealShortChannelId, Router.PublicChannel] = { runAsync(secondary.listChannels()) primary.listChannels() } - override def addToPruned(shortChannelIds: Iterable[ShortChannelId]): Unit = { + override def addToPruned(shortChannelIds: Iterable[RealShortChannelId]): Unit = { runAsync(secondary.addToPruned(shortChannelIds)) primary.addToPruned(shortChannelIds) } - override def removeFromPruned(shortChannelId: ShortChannelId): Unit = { + override def removeFromPruned(shortChannelId: RealShortChannelId): Unit = { runAsync(secondary.removeFromPruned(shortChannelId)) primary.removeFromPruned(shortChannelId) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala index 80973d1c2c..ec3031fd0a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.db import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.router.Router.PublicChannel import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement} @@ -44,11 +45,11 @@ trait NetworkDb { def removeChannels(shortChannelIds: Iterable[ShortChannelId]): Unit - def listChannels(): SortedMap[ShortChannelId, PublicChannel] + def listChannels(): SortedMap[RealShortChannelId, PublicChannel] - def addToPruned(shortChannelIds: Iterable[ShortChannelId]): Unit + def addToPruned(shortChannelIds: Iterable[RealShortChannelId]): Unit - def removeFromPruned(shortChannelId: ShortChannelId): Unit + def removeFromPruned(shortChannelId: RealShortChannelId): Unit def isPruned(shortChannelId: ShortChannelId): Boolean diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala index afe716350e..37273908d1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.db.pg import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi} import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db.NetworkDb @@ -199,11 +200,11 @@ class PgNetworkDb(implicit ds: DataSource) extends NetworkDb with Logging { } } - override def listChannels(): SortedMap[ShortChannelId, PublicChannel] = withMetrics("network/list-channels", DbBackends.Postgres) { + override def listChannels(): SortedMap[RealShortChannelId, PublicChannel] = withMetrics("network/list-channels", DbBackends.Postgres) { inTransaction { pg => using(pg.createStatement()) { statement => statement.executeQuery("SELECT channel_announcement, txid, capacity_sat, channel_update_1, channel_update_2 FROM network.public_channels") - .foldLeft(SortedMap.empty[ShortChannelId, PublicChannel]) { (m, rs) => + .foldLeft(SortedMap.empty[RealShortChannelId, PublicChannel]) { (m, rs) => val ann = channelAnnouncementCodec.decode(rs.getBitVectorOpt("channel_announcement").get).require.value val txId = ByteVector32.fromValidHex(rs.getString("txid")) val capacity = rs.getLong("capacity_sat") @@ -236,7 +237,7 @@ class PgNetworkDb(implicit ds: DataSource) extends NetworkDb with Logging { } } - override def addToPruned(shortChannelIds: Iterable[ShortChannelId]): Unit = withMetrics("network/add-to-pruned", DbBackends.Postgres) { + override def addToPruned(shortChannelIds: Iterable[RealShortChannelId]): Unit = withMetrics("network/add-to-pruned", DbBackends.Postgres) { inTransaction { pg => using(pg.prepareStatement("INSERT INTO network.pruned_channels VALUES (?) ON CONFLICT DO NOTHING")) { statement => @@ -249,7 +250,7 @@ class PgNetworkDb(implicit ds: DataSource) extends NetworkDb with Logging { } } - override def removeFromPruned(shortChannelId: ShortChannelId): Unit = withMetrics("network/remove-from-pruned", DbBackends.Postgres) { + override def removeFromPruned(shortChannelId: RealShortChannelId): Unit = withMetrics("network/remove-from-pruned", DbBackends.Postgres) { inTransaction { pg => using(pg.prepareStatement(s"DELETE FROM network.pruned_channels WHERE short_channel_id=?")) { statement => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala index 844e886aae..6e6ea16089 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.db.sqlite import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi} import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.db.Monitoring.Metrics.withMetrics import fr.acinq.eclair.db.Monitoring.Tags.DbBackends import fr.acinq.eclair.db.NetworkDb @@ -124,10 +125,10 @@ class SqliteNetworkDb(val sqlite: Connection) extends NetworkDb with Logging { } } - override def listChannels(): SortedMap[ShortChannelId, PublicChannel] = withMetrics("network/list-channels", DbBackends.Sqlite) { + override def listChannels(): SortedMap[RealShortChannelId, PublicChannel] = withMetrics("network/list-channels", DbBackends.Sqlite) { using(sqlite.createStatement()) { statement => statement.executeQuery("SELECT channel_announcement, txid, capacity_sat, channel_update_1, channel_update_2 FROM channels") - .foldLeft(SortedMap.empty[ShortChannelId, PublicChannel]) { (m, rs) => + .foldLeft(SortedMap.empty[RealShortChannelId, PublicChannel]) { (m, rs) => val ann = channelAnnouncementCodec.decode(rs.getBitVectorOpt("channel_announcement").get).require.value val txId = ByteVector32.fromValidHex(rs.getString("txid")) val capacity = rs.getLong("capacity_sat") @@ -153,7 +154,7 @@ class SqliteNetworkDb(val sqlite: Connection) extends NetworkDb with Logging { } } - override def addToPruned(shortChannelIds: Iterable[ShortChannelId]): Unit = withMetrics("network/add-to-pruned", DbBackends.Sqlite) { + override def addToPruned(shortChannelIds: Iterable[RealShortChannelId]): Unit = withMetrics("network/add-to-pruned", DbBackends.Sqlite) { using(sqlite.prepareStatement("INSERT OR IGNORE INTO pruned VALUES (?)"), inTransaction = true) { statement => shortChannelIds.foreach(shortChannelId => { statement.setLong(1, shortChannelId.toLong) @@ -163,7 +164,7 @@ class SqliteNetworkDb(val sqlite: Connection) extends NetworkDb with Logging { } } - override def removeFromPruned(shortChannelId: ShortChannelId): Unit = withMetrics("network/remove-from-pruned", DbBackends.Sqlite) { + override def removeFromPruned(shortChannelId: RealShortChannelId): Unit = withMetrics("network/remove-from-pruned", DbBackends.Sqlite) { using(sqlite.prepareStatement(s"DELETE FROM pruned WHERE short_channel_id=?")) { statement => statement.setLong(1, shortChannelId.toLong) statement.executeUpdate() diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala index cf87799532..8f5852e22b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/package.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/package.scala @@ -16,12 +16,11 @@ package fr.acinq -import fr.acinq.bitcoin.{Base58, Base58Check, Bech32} import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat._ +import fr.acinq.bitcoin.{Base58, Base58Check, Bech32} import fr.acinq.eclair.crypto.StrongRandom import fr.acinq.eclair.payment.relay.Relayer.RelayFees -import fr.acinq.eclair.wire.protocol.ChannelUpdate import scodec.Attempt import scodec.bits.{BitVector, ByteVector} diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala index f874479681..2275e8ea56 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala @@ -46,7 +46,6 @@ object ChannelRelayer { private[payment] case class WrappedLocalChannelUpdate(localChannelUpdate: LocalChannelUpdate) extends Command private[payment] case class WrappedLocalChannelDown(localChannelDown: LocalChannelDown) extends Command private[payment] case class WrappedAvailableBalanceChanged(availableBalanceChanged: AvailableBalanceChanged) extends Command - private[payment] case class WrappedShortChannelIdAssigned(shortChannelIdAssigned: ShortChannelIdAssigned) extends Command // @formatter:on def mdc: Command => Map[String, String] = { @@ -63,7 +62,6 @@ object ChannelRelayer { context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[LocalChannelUpdate](WrappedLocalChannelUpdate)) context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[LocalChannelDown](WrappedLocalChannelDown)) context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[AvailableBalanceChanged](WrappedAvailableBalanceChanged)) - context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[ShortChannelIdAssigned](WrappedShortChannelIdAssigned)) context.messageAdapter[IncomingPaymentPacket.ChannelRelayPacket](Relay) Behaviors.withMdc(Logs.mdc(category_opt = Some(Logs.LogCategory.PAYMENT)), mdc) { Behaviors.receiveMessage { @@ -90,35 +88,33 @@ object ChannelRelayer { replyTo ! Relayer.OutgoingChannels(selected.toSeq) Behaviors.same - case WrappedLocalChannelUpdate(LocalChannelUpdate(_, channelId, shortChannelId, remoteNodeId, _, channelUpdate, commitments)) => - context.log.debug(s"updating local channel info for channelId=$channelId shortChannelId=$shortChannelId remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) + case WrappedLocalChannelUpdate(lcu@LocalChannelUpdate(_, channelId, _, localAlias, remoteNodeId, _, channelUpdate, commitments)) => + context.log.debug(s"updating local channel info for channelId=$channelId localAlias=$localAlias remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) val prevChannelUpdate = channels.get(channelId).map(_.channelUpdate) val channel = Relayer.OutgoingChannel(remoteNodeId, channelUpdate, prevChannelUpdate, commitments) val channels1 = channels + (channelId -> channel) - val scid2channels1 = scid2channels + (channelUpdate.shortChannelId -> channelId) + val mappings = lcu.scidsForRouting.map(_ -> channelId).toMap + context.log.debug("adding mappings={} to channelId={}", mappings.keys.mkString(","), channelId) + val scid2channels1 = scid2channels ++ mappings val node2channels1 = node2channels.addOne(remoteNodeId, channelId) apply(nodeParams, register, channels1, scid2channels1, node2channels1) - case WrappedLocalChannelDown(LocalChannelDown(_, channelId, shortChannelId, remoteNodeId)) => - context.log.debug(s"removed local channel info for channelId=$channelId shortChannelId=$shortChannelId") + case WrappedLocalChannelDown(LocalChannelDown(_, channelId, _, localAlias, remoteNodeId)) => + context.log.debug(s"removed local channel info for channelId=$channelId localAlias=$localAlias") val channels1 = channels - channelId - val scid2Channels1 = scid2channels - shortChannelId + val scid2Channels1 = scid2channels - localAlias val node2channels1 = node2channels.subtractOne(remoteNodeId, channelId) apply(nodeParams, register, channels1, scid2Channels1, node2channels1) - case WrappedAvailableBalanceChanged(AvailableBalanceChanged(_, channelId, shortChannelId, commitments)) => + case WrappedAvailableBalanceChanged(AvailableBalanceChanged(_, channelId, _, localAlias, commitments)) => val channels1 = channels.get(channelId) match { case Some(c: Relayer.OutgoingChannel) => - context.log.debug(s"available balance changed for channelId=$channelId shortChannelId=$shortChannelId availableForSend={} availableForReceive={}", commitments.availableBalanceForSend, commitments.availableBalanceForReceive) + context.log.debug(s"available balance changed for channelId=$channelId localAlias=$localAlias availableForSend={} availableForReceive={}", commitments.availableBalanceForSend, commitments.availableBalanceForReceive) channels + (channelId -> c.copy(commitments = commitments)) case None => channels // we only consider the balance if we have the channel_update } apply(nodeParams, register, channels1, scid2channels, node2channels) - case WrappedShortChannelIdAssigned(ShortChannelIdAssigned(_, channelId, shortChannelId, previousShortChannelId_opt)) => - context.log.debug(s"added new mapping shortChannelId=$shortChannelId for channelId=$channelId") - val scid2channels1 = scid2channels + (shortChannelId -> channelId) - apply(nodeParams, register, channels, scid2channels1, node2channels) } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala index d0917fdf4b..d35bb6c37f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/remote/EclairInternalsSerializer.scala @@ -194,7 +194,7 @@ object EclairInternalsSerializer { .typecase(31, lengthPrefixedNodeAnnouncementCodec.as[NodeUpdated]) .typecase(32, publicKey.as[NodeLost]) .typecase(33, iterable(singleChannelDiscoveredCodec).as[ChannelsDiscovered]) - .typecase(34, shortchannelid.as[ChannelLost]) + .typecase(34, realshortchannelid.as[ChannelLost]) .typecase(35, iterable(lengthPrefixedChannelUpdateCodec).as[ChannelUpdatesReceived]) .typecase(36, double.as[SyncProgress]) .typecase(40, lengthPrefixedAnnouncementCodec.as[GossipDecision.Accepted]) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index 83b8930183..da8381840c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256, verifySignature} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, LexicographicalOrdering} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{CltvExpiryDelta, Feature, Features, MilliSatoshi, NodeFeature, ShortChannelId, TimestampSecond, TimestampSecondLong, serializationResult} import scodec.bits.ByteVector @@ -28,7 +29,7 @@ import shapeless.HNil */ object Announcements { - def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: Features[Feature], tlvStream: TlvStream[ChannelAnnouncementTlv]): ByteVector = + def channelAnnouncementWitnessEncode(chainHash: ByteVector32, shortChannelId: RealShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, bitcoinKey2: PublicKey, features: Features[Feature], tlvStream: TlvStream[ChannelAnnouncementTlv]): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: tlvStream :: HNil)))) def nodeAnnouncementWitnessEncode(timestamp: TimestampSecond, nodeId: PublicKey, rgbColor: Color, alias: String, features: Features[Feature], addresses: List[NodeAddress], tlvStream: TlvStream[NodeAnnouncementTlv]): ByteVector = @@ -37,7 +38,7 @@ object Announcements { def channelUpdateWitnessEncode(chainHash: ByteVector32, shortChannelId: ShortChannelId, timestamp: TimestampSecond, channelFlags: ChannelUpdate.ChannelFlags, cltvExpiryDelta: CltvExpiryDelta, htlcMinimumMsat: MilliSatoshi, feeBaseMsat: MilliSatoshi, feeProportionalMillionths: Long, htlcMaximumMsat: Option[MilliSatoshi], tlvStream: TlvStream[ChannelUpdateTlv]): ByteVector = sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: tlvStream :: HNil)))) - def generateChannelAnnouncementWitness(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, features: Features[Feature]): ByteVector = + def generateChannelAnnouncementWitness(chainHash: ByteVector32, shortChannelId: RealShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, features: Features[Feature]): ByteVector = if (isNode1(localNodeId, remoteNodeId)) { channelAnnouncementWitnessEncode(chainHash, shortChannelId, localNodeId, remoteNodeId, localFundingKey, remoteFundingKey, features, TlvStream.empty) } else { @@ -46,7 +47,7 @@ object Announcements { def signChannelAnnouncement(witness: ByteVector, key: PrivateKey): ByteVector64 = Crypto.sign(witness, key) - def makeChannelAnnouncement(chainHash: ByteVector32, shortChannelId: ShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, localNodeSignature: ByteVector64, remoteNodeSignature: ByteVector64, localBitcoinSignature: ByteVector64, remoteBitcoinSignature: ByteVector64): ChannelAnnouncement = { + def makeChannelAnnouncement(chainHash: ByteVector32, shortChannelId: RealShortChannelId, localNodeId: PublicKey, remoteNodeId: PublicKey, localFundingKey: PublicKey, remoteFundingKey: PublicKey, localNodeSignature: ByteVector64, remoteNodeSignature: ByteVector64, localBitcoinSignature: ByteVector64, remoteBitcoinSignature: ByteVector64): ChannelAnnouncement = { val (nodeId1, nodeId2, bitcoinKey1, bitcoinKey2, nodeSignature1, nodeSignature2, bitcoinSignature1, bitcoinSignature2) = if (isNode1(localNodeId, remoteNodeId)) { (localNodeId, remoteNodeId, localFundingKey, remoteFundingKey, localNodeSignature, remoteNodeSignature, localBitcoinSignature, remoteBitcoinSignature) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 2cf45c35e2..5876a1a3a9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Btc, MilliBtc, Satoshi, SatoshiLong} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge} @@ -610,7 +611,7 @@ object Graph { * * @param channels map of all known public channels in the network. */ - def makeGraph(channels: SortedMap[ShortChannelId, PublicChannel]): DirectedGraph = { + def makeGraph(channels: SortedMap[RealShortChannelId, PublicChannel]): DirectedGraph = { // initialize the map with the appropriate size to avoid resizing during the graph initialization val mutableMap = new mutable.HashMap[PublicKey, List[GraphEdge]](initialCapacity = channels.size + 1, mutable.HashMap.defaultLoadFactor) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/NetworkEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/NetworkEvents.scala index 8d781dc7b9..f960bf8d6b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/NetworkEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/NetworkEvents.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Satoshi import fr.acinq.eclair.ShortChannelId +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement} @@ -37,7 +38,7 @@ case class SingleChannelDiscovered(ann: ChannelAnnouncement, capacity: Satoshi, case class ChannelsDiscovered(c: Iterable[SingleChannelDiscovered]) extends NetworkEvent -case class ChannelLost(shortChannelId: ShortChannelId) extends NetworkEvent +case class ChannelLost(shortChannelId: RealShortChannelId) extends NetworkEvent case class ChannelUpdatesReceived(ann: Iterable[ChannelUpdate]) extends NetworkEvent diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala index 46cd566252..5951c26298 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala @@ -71,8 +71,8 @@ object RouteCalculation { case _ => None } case Some(c: PrivateChannel) => currentNode match { - case c.nodeId1 => Some(ChannelDesc(c.shortChannelId, c.nodeId1, c.nodeId2)) - case c.nodeId2 => Some(ChannelDesc(c.shortChannelId, c.nodeId2, c.nodeId1)) + case c.nodeId1 => Some(ChannelDesc(c.localAlias, c.nodeId1, c.nodeId2)) + case c.nodeId2 => Some(ChannelDesc(c.localAlias, c.nodeId2, c.nodeId1)) case _ => None } case None => assistedChannels.get(shortChannelId).flatMap(c => currentNode match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 6580c17831..820f7504c8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -335,7 +335,7 @@ object Router { } def apply(u: ChannelUpdate, pc: PrivateChannel): ChannelDesc = { // the least significant bit tells us if it is node1 or node2 - if (u.channelFlags.isNode1) ChannelDesc(u.shortChannelId, pc.nodeId1, pc.nodeId2) else ChannelDesc(u.shortChannelId, pc.nodeId2, pc.nodeId1) + if (u.channelFlags.isNode1) ChannelDesc(pc.localAlias, pc.nodeId1, pc.nodeId2) else ChannelDesc(pc.localAlias, pc.nodeId2, pc.nodeId1) } } case class ChannelMeta(balance1: MilliSatoshi, balance2: MilliSatoshi) @@ -356,7 +356,7 @@ object Router { val nodeId1: PublicKey = ann.nodeId1 val nodeId2: PublicKey = ann.nodeId2 - def shortChannelId: ShortChannelId = ann.shortChannelId + def shortChannelId: RealShortChannelId = ann.shortChannelId def channelId: ByteVector32 = toLongId(fundingTxid.reverse, outputIndex(ann.shortChannelId)) def getNodeIdSameSideAs(u: ChannelUpdate): PublicKey = if (u.channelFlags.isNode1) ann.nodeId1 else ann.nodeId2 def getChannelUpdateSameSideAs(u: ChannelUpdate): Option[ChannelUpdate] = if (u.channelFlags.isNode1) update_1_opt else update_2_opt @@ -372,7 +372,7 @@ object Router { case Right(rcu) => updateChannelUpdateSameSideAs(rcu.channelUpdate) } } - case class PrivateChannel(shortChannelId: ShortChannelId, channelId: ByteVector32, localNodeId: PublicKey, remoteNodeId: PublicKey, update_1_opt: Option[ChannelUpdate], update_2_opt: Option[ChannelUpdate], meta: ChannelMeta) extends KnownChannel { + case class PrivateChannel(localAlias: LocalAlias, channelId: ByteVector32, localNodeId: PublicKey, remoteNodeId: PublicKey, update_1_opt: Option[ChannelUpdate], update_2_opt: Option[ChannelUpdate], meta: ChannelMeta) extends KnownChannel { val (nodeId1, nodeId2) = if (Announcements.isNode1(localNodeId, remoteNodeId)) (localNodeId, remoteNodeId) else (remoteNodeId, localNodeId) val capacity: Satoshi = (meta.balance1 + meta.balance2).truncateToSatoshi @@ -392,9 +392,15 @@ object Router { /** Create an invoice routing hint from that channel. Note that if the channel is private, the invoice will leak its existence. */ def toIncomingExtraHop: Option[ExtraHop] = { // we want the incoming channel_update - val remoteUpdate_opt = if (localNodeId == nodeId1) update_2_opt else update_1_opt - remoteUpdate_opt.map { remoteUpdate => - ExtraHop(remoteNodeId, remoteUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta) + val (localUpdate_opt, remoteUpdate_opt) = if (localNodeId == nodeId1) (update_1_opt, update_2_opt) else (update_2_opt, update_1_opt) + (localUpdate_opt, remoteUpdate_opt) match { + case (Some(localUpdate), Some(remoteUpdate)) => + // this is tricky: for incoming payments we need the *remote alias*, we can find it in the channel_update that we sent them + Some(ExtraHop(remoteNodeId, localUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) + case (_, Some(remoteUpdate)) if remoteUpdate.shortChannelId != localAlias => + // they are using a real scid (otherwise it would match our local alias, we can use it in the routing hint) + Some(ExtraHop(remoteNodeId, remoteUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) + case _ => None } } } @@ -626,7 +632,7 @@ object Router { case class Rebroadcast(channels: Map[ChannelAnnouncement, Set[GossipOrigin]], updates: Map[ChannelUpdate, Set[GossipOrigin]], nodes: Map[NodeAnnouncement, Set[GossipOrigin]]) // @formatter:on - case class ShortChannelIdAndFlag(shortChannelId: ShortChannelId, flag: Long) + case class ShortChannelIdAndFlag(shortChannelId: RealShortChannelId, flag: Long) /** * @param remainingQueries remaining queries to send, the next one will be popped after we receive a [[ReplyShortChannelIdsEnd]] @@ -637,19 +643,19 @@ object Router { } case class Data(nodes: Map[PublicKey, NodeAnnouncement], - channels: SortedMap[ShortChannelId, PublicChannel], + channels: SortedMap[RealShortChannelId, PublicChannel], stash: Stash, rebroadcast: Rebroadcast, awaiting: Map[ChannelAnnouncement, Seq[GossipOrigin]], // note: this is a seq because we want to preserve order: first actor is the one who we need to send a tcp-ack when validation is done privateChannels: Map[ByteVector32, PrivateChannel], // indexed by channel id - scid2PrivateChannels: Map[ShortChannelId, ByteVector32], // scid to channel_id, only to be used for private channels + scid2PrivateChannels: Map[ShortChannelId, ByteVector32], // real scid or alias to channel_id, only to be used for private channels excludedChannels: Set[ChannelDesc], // those channels are temporarily excluded from route calculation, because their node returned a TemporaryChannelFailure graphWithBalances: GraphWithBalanceEstimates, sync: Map[PublicKey, Syncing] // keep tracks of channel range queries sent to each peer. If there is an entry in the map, it means that there is an ongoing query for which we have not yet received an 'end' message ) { def resolve(scid: ShortChannelId): Option[KnownChannel] = { // let's assume this is a real scid - channels.get(scid) match { + channels.get(scid.toReal) match { case Some(publicChannel) => Some(publicChannel) case None => // maybe it's an alias or a real scid @@ -659,6 +665,10 @@ object Router { } } } + + def resolve(channelId: ByteVector32, realScid_opt: Option[RealShortChannelId]): Option[KnownChannel] = { + privateChannels.get(channelId).orElse(realScid_opt.flatMap(channels.get)) + } } // @formatter:off diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala index 63e65b3dcc..fe5d03cdc3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala @@ -20,6 +20,7 @@ import akka.actor.{ActorContext, ActorRef} import akka.event.LoggingAdapter import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.router.Monitoring.{Metrics, Tags} import fr.acinq.eclair.router.Router._ @@ -69,13 +70,13 @@ object Sync { } } - def handleQueryChannelRange(channels: SortedMap[ShortChannelId, PublicChannel], routerConf: RouterConf, origin: RemoteGossip, q: QueryChannelRange)(implicit ctx: ActorContext, log: LoggingAdapter): Unit = { + def handleQueryChannelRange(channels: SortedMap[RealShortChannelId, PublicChannel], routerConf: RouterConf, origin: RemoteGossip, q: QueryChannelRange)(implicit ctx: ActorContext, log: LoggingAdapter): Unit = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors ctx.sender() ! TransportHandler.ReadAck(q) Metrics.QueryChannelRange.Blocks.withoutTags().record(q.numberOfBlocks) log.info("received query_channel_range with firstBlockNum={} numberOfBlocks={} extendedQueryFlags_opt={}", q.firstBlock, q.numberOfBlocks, q.tlvStream) // keep channel ids that are in [firstBlockNum, firstBlockNum + numberOfBlocks] - val shortChannelIds: SortedSet[ShortChannelId] = channels.keySet.filter(keep(q.firstBlock, q.numberOfBlocks, _)) + val shortChannelIds: SortedSet[RealShortChannelId] = channels.keySet.filter(keep(q.firstBlock, q.numberOfBlocks, _)) log.info("replying with {} items for range=({}, {})", shortChannelIds.size, q.firstBlock, q.numberOfBlocks) val chunks = split(shortChannelIds, q.firstBlock, q.numberOfBlocks, routerConf.channelRangeChunkSize) Metrics.QueryChannelRange.Replies.withoutTags().record(chunks.size) @@ -107,7 +108,7 @@ object Sync { Metrics.ReplyChannelRange.ShortChannelIds.withTag(Tags.Direction, Tags.Directions.Incoming).record(r.shortChannelIds.array.size) @tailrec - def loop(ids: List[ShortChannelId], timestamps: List[ReplyChannelRangeTlv.Timestamps], checksums: List[ReplyChannelRangeTlv.Checksums], acc: List[ShortChannelIdAndFlag] = List.empty[ShortChannelIdAndFlag]): List[ShortChannelIdAndFlag] = { + def loop(ids: List[RealShortChannelId], timestamps: List[ReplyChannelRangeTlv.Timestamps], checksums: List[ReplyChannelRangeTlv.Checksums], acc: List[ShortChannelIdAndFlag] = List.empty[ShortChannelIdAndFlag]): List[ShortChannelIdAndFlag] = { ids match { case Nil => acc.reverse case head :: tail => @@ -158,7 +159,7 @@ object Sync { } } - def handleQueryShortChannelIds(nodes: Map[PublicKey, NodeAnnouncement], channels: SortedMap[ShortChannelId, PublicChannel], origin: RemoteGossip, q: QueryShortChannelIds)(implicit ctx: ActorContext, log: LoggingAdapter): Unit = { + def handleQueryShortChannelIds(nodes: Map[PublicKey, NodeAnnouncement], channels: SortedMap[RealShortChannelId, PublicChannel], origin: RemoteGossip, q: QueryShortChannelIds)(implicit ctx: ActorContext, log: LoggingAdapter): Unit = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors ctx.sender() ! TransportHandler.ReadAck(q) @@ -251,8 +252,8 @@ object Sync { } } - def computeFlag(channels: SortedMap[ShortChannelId, PublicChannel])( - shortChannelId: ShortChannelId, + def computeFlag(channels: SortedMap[RealShortChannelId, PublicChannel])( + shortChannelId: RealShortChannelId, theirTimestamps_opt: Option[ReplyChannelRangeTlv.Timestamps], theirChecksums_opt: Option[ReplyChannelRangeTlv.Checksums], includeNodeAnnouncements: Boolean): Long = { @@ -291,8 +292,8 @@ object Sync { * */ def processChannelQuery(nodes: Map[PublicKey, NodeAnnouncement], - channels: SortedMap[ShortChannelId, PublicChannel])( - ids: List[ShortChannelId], + channels: SortedMap[RealShortChannelId, PublicChannel])( + ids: List[RealShortChannelId], flags: List[Long], onChannel: ChannelAnnouncement => Unit, onUpdate: ChannelUpdate => Unit, @@ -302,7 +303,7 @@ object Sync { // we loop over channel ids and query flag. We track node Ids for node announcement // we've already sent to avoid sending them multiple times, as requested by the BOLTs @tailrec - def loop(ids: List[ShortChannelId], flags: List[Long], numca: Int = 0, numcu: Int = 0, nodesSent: Set[PublicKey] = Set.empty[PublicKey]): (Int, Int, Int) = ids match { + def loop(ids: List[RealShortChannelId], flags: List[Long], numca: Int = 0, numcu: Int = 0, nodesSent: Set[PublicKey] = Set.empty[PublicKey]): (Int, Int, Int) = ids match { case Nil => (numca, numcu, nodesSent.size) case head :: tail if !channels.contains(head) => log.warning("received query for shortChannelId={} that we don't have", head) @@ -369,7 +370,7 @@ object Sync { } } - def getChannelDigestInfo(channels: SortedMap[ShortChannelId, PublicChannel])(shortChannelId: ShortChannelId): (ReplyChannelRangeTlv.Timestamps, ReplyChannelRangeTlv.Checksums) = { + def getChannelDigestInfo(channels: SortedMap[RealShortChannelId, PublicChannel])(shortChannelId: RealShortChannelId): (ReplyChannelRangeTlv.Timestamps, ReplyChannelRangeTlv.Checksums) = { val c = channels(shortChannelId) val timestamp1 = c.update_1_opt.map(_.timestamp).getOrElse(0L unixsec) val timestamp2 = c.update_2_opt.map(_.timestamp).getOrElse(0L unixsec) @@ -390,7 +391,7 @@ object Sync { crc32c(data) } - case class ShortChannelIdsChunk(firstBlock: BlockHeight, numBlocks: Long, shortChannelIds: List[ShortChannelId]) { + case class ShortChannelIdsChunk(firstBlock: BlockHeight, numBlocks: Long, shortChannelIds: List[RealShortChannelId]) { /** * @param maximumSize maximum size of the short channel ids list * @return a chunk with at most `maximumSize` ids @@ -419,7 +420,7 @@ object Sync { * returned chunks may still contain more than `channelRangeChunkSize` elements * @return a list of short channel id chunks */ - def split(shortChannelIds: SortedSet[ShortChannelId], firstBlock: BlockHeight, numberOfBlocks: Long, channelRangeChunkSize: Int): List[ShortChannelIdsChunk] = { + def split(shortChannelIds: SortedSet[RealShortChannelId], firstBlock: BlockHeight, numberOfBlocks: Long, channelRangeChunkSize: Int): List[ShortChannelIdsChunk] = { // see BOLT7: MUST encode a short_channel_id for every open channel it knows in blocks first_blocknum to first_blocknum plus number_of_blocks minus one val it = shortChannelIds.iterator.dropWhile(_.blockHeight < firstBlock).takeWhile(_.blockHeight < firstBlock + numberOfBlocks) if (it.isEmpty) { @@ -429,7 +430,7 @@ object Sync { // ids that have the same block height must be grouped in the same chunk // chunk should contain `channelRangeChunkSize` ids @tailrec - def loop(currentChunk: List[ShortChannelId], acc: List[ShortChannelIdsChunk]): List[ShortChannelIdsChunk] = { + def loop(currentChunk: List[RealShortChannelId], acc: List[ShortChannelIdsChunk]): List[ShortChannelIdsChunk] = { if (it.hasNext) { val id = it.next() val currentHeight = currentChunk.head.blockHeight @@ -481,7 +482,7 @@ object Sync { * @param channels channels map * @return a ReplyChannelRange object */ - def buildReplyChannelRange(chunk: ShortChannelIdsChunk, syncComplete: Boolean, chainHash: ByteVector32, defaultEncoding: EncodingType, queryFlags_opt: Option[QueryChannelRangeTlv.QueryFlags], channels: SortedMap[ShortChannelId, PublicChannel]): ReplyChannelRange = { + def buildReplyChannelRange(chunk: ShortChannelIdsChunk, syncComplete: Boolean, chainHash: ByteVector32, defaultEncoding: EncodingType, queryFlags_opt: Option[QueryChannelRangeTlv.QueryFlags], channels: SortedMap[RealShortChannelId, PublicChannel]): ReplyChannelRange = { val encoding = if (chunk.shortChannelIds.isEmpty) EncodingType.UNCOMPRESSED else defaultEncoding val (timestamps, checksums) = queryFlags_opt match { case Some(extension) if extension.wantChecksums | extension.wantTimestamps => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index a218504a1e..a9296ef513 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -21,6 +21,7 @@ import akka.actor.{ActorContext, ActorRef, typed} import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} import fr.acinq.eclair.channel.{AvailableBalanceChanged, LocalChannelDown, LocalChannelUpdate} @@ -183,7 +184,7 @@ object Validation { } } - def handleChannelSpent(d: Data, db: NetworkDb, shortChannelId: ShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { + def handleChannelSpent(d: Data, db: NetworkDb, shortChannelId: RealShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors val lostChannel = d.channels(shortChannelId).ann log.info("funding tx of channelId={} has been spent", shortChannelId) @@ -265,7 +266,7 @@ object Validation { def handleChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, update: Either[LocalChannelUpdate, RemoteChannelUpdate], wasStashed: Boolean = false)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors val (pc_opt: Option[KnownChannel], u: ChannelUpdate, origins: Set[GossipOrigin]) = update match { - case Left(lcu) => (d.resolve(lcu.shortChannelId), lcu.channelUpdate, Set(LocalGossip)) + case Left(lcu) => (d.resolve(lcu.channelId, lcu.realShortChannelId_opt), lcu.channelUpdate, Set(LocalGossip)) case Right(rcu) => rcu.origins.collect { case RemoteGossip(peerConnection, _) if !wasStashed => // stashed changes have already been acknowledged @@ -382,19 +383,21 @@ object Validation { d.copy(stash = d.stash.copy(updates = d.stash.updates + (u -> origins))) } case None if db.isPruned(u.shortChannelId) && !StaleChannels.isStale(u) => + // only public channels are pruned + val realShortChannelId = u.shortChannelId.toReal // the channel was recently pruned, but if we are here, it means that the update is not stale so this is the case // of a zombie channel coming back from the dead. they probably sent us a channel_announcement right before this update, // but we ignored it because the channel was in the 'pruned' list. Now that we know that the channel is alive again, // let's remove the channel from the zombie list and ask the sender to re-send announcements (channel_announcement + updates) // about that channel. We can ignore this update since we will receive it again - log.info(s"channel shortChannelId=${u.shortChannelId} is back from the dead! requesting announcements about this channel") + log.info(s"channel shortChannelId=${realShortChannelId} is back from the dead! requesting announcements about this channel") sendDecision(origins, GossipDecision.RelatedChannelPruned(u)) - db.removeFromPruned(u.shortChannelId) + db.removeFromPruned(realShortChannelId) // peerConnection_opt will contain a valid peerConnection only when we're handling an update that we received from a peer, not // when we're sending updates to ourselves origins head match { case RemoteGossip(peerConnection, remoteNodeId) => - val query = QueryShortChannelIds(u.chainHash, EncodedShortChannelIds(routerConf.encodingType, List(u.shortChannelId)), TlvStream.empty) + val query = QueryShortChannelIds(u.chainHash, EncodedShortChannelIds(routerConf.encodingType, List(realShortChannelId)), TlvStream.empty) d.sync.get(remoteNodeId) match { case Some(sync) if sync.started => // we already have a pending request to that node, let's add this channel to the list and we'll get it later @@ -420,7 +423,7 @@ object Validation { def handleLocalChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, localNodeId: PublicKey, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - d.channels.get(lcu.shortChannelId) match { + lcu.realShortChannelId_opt.flatMap(d.channels.get) match { case Some(_) => // channel has already been announced and router knows about it, we can process the channel_update handleChannelUpdate(d, db, routerConf, Left(lcu)) @@ -442,11 +445,15 @@ object Validation { case None => // channel isn't announced and we never heard of it (maybe it is a private channel or maybe it is a public channel that doesn't yet have 6 confirmations) // let's create a corresponding private channel and process the channel_update - log.debug("adding unannounced local channel to remote={} shortChannelId={}", lcu.remoteNodeId, lcu.shortChannelId) - val pc = PrivateChannel(lcu.shortChannelId, lcu.channelId, localNodeId, lcu.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)).updateBalances(lcu.commitments) + log.warning("unknown local channel update to remote={} channelId={}, localAlias={}", lcu.remoteNodeId, lcu.channelId, lcu.localAlias) + val pc = PrivateChannel(lcu.localAlias, lcu.channelId, localNodeId, lcu.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)).updateBalances(lcu.commitments) + val mappings = lcu.realShortChannelId_opt match { + case Some(realScid) => Map(realScid -> lcu.channelId, lcu.localAlias -> lcu.channelId) + case None => Map(lcu.localAlias -> lcu.channelId) + } val d1 = d.copy( privateChannels = d.privateChannels + (lcu.channelId -> pc), - scid2PrivateChannels = d.scid2PrivateChannels + (lcu.shortChannelId -> lcu.channelId) + scid2PrivateChannels = d.scid2PrivateChannels + mappings ) handleChannelUpdate(d1, db, routerConf, Left(lcu)) } @@ -454,17 +461,18 @@ object Validation { } def handleLocalChannelDown(d: Data, localNodeId: PublicKey, lcd: LocalChannelDown)(implicit log: LoggingAdapter): Data = { - import lcd.{channelId, remoteNodeId, shortChannelId} + import lcd.{channelId, remoteNodeId} // a local channel has permanently gone down - if (d.channels.contains(shortChannelId)) { + if (lcd.realShortChannelId_opt.exists(d.channels.contains)) { // the channel was public, we will receive (or have already received) a WatchEventSpentBasic event, that will trigger a clean up of the channel // so let's not do anything here d - } else if (d.privateChannels.contains(channelId)) { + } else if (d.privateChannels.contains(lcd.channelId)) { // the channel was private or public-but-not-yet-announced, let's do the clean up - log.info("removing private local channel and channel_update for channelId={} shortChannelId={}", channelId, shortChannelId) - val desc1 = ChannelDesc(shortChannelId, localNodeId, remoteNodeId) - val desc2 = ChannelDesc(shortChannelId, remoteNodeId, localNodeId) + val localAlias = d.privateChannels(channelId).localAlias + log.info("removing private local channel and channel_update for channelId={} localAlias={}", channelId, localAlias) + val desc1 = ChannelDesc(localAlias, localNodeId, remoteNodeId) + val desc2 = ChannelDesc(localAlias, remoteNodeId, localNodeId) // we remove the corresponding updates from the graph val graphWithBalances1 = d.graphWithBalances .removeEdge(desc1) @@ -477,7 +485,7 @@ object Validation { } def handleAvailableBalanceChanged(d: Data, e: AvailableBalanceChanged)(implicit log: LoggingAdapter): Data = { - val (publicChannels1, graphWithBalances1) = d.channels.get(e.shortChannelId) match { + val (publicChannels1, graphWithBalances1) = e.realShortChannelId_opt.flatMap(d.channels.get) match { case Some(pc) => val pc1 = pc.updateBalances(e.commitments) log.debug("public channel balance updated: {}", pc1) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 976b02dad2..793c2f2aee 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -18,7 +18,6 @@ package fr.acinq.eclair.wire.internal.channel.version0 import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, Transaction, TxOut} -import fr.acinq.eclair.{BlockHeight, TimestampSecond} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ @@ -27,6 +26,7 @@ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSi import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, combinedFeaturesCodec} import fr.acinq.eclair.wire.protocol._ +import fr.acinq.eclair.{BlockHeight, TimestampSecond} import scodec.Codec import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -371,8 +371,11 @@ private[channel] object ChannelCodecs0 { val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: - ("lastSent" | channelReadyCodec)).as[DATA_WAIT_FOR_CHANNEL_READY].decodeOnly + ("shortChannelId" | realshortchannelid) :: + ("lastSent" | channelReadyCodec)).map { + case commitments :: shortChannelId :: lastSent :: HNil => + DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId), localAlias = shortChannelId.toAlias, lastSent = lastSent) + }.decodeOnly val shutdownCodec: Codec[Shutdown] = ( ("channelId" | bytes32) :: @@ -382,23 +385,29 @@ private[channel] object ChannelCodecs0 { // this is a decode-only codec compatible with versions 9afb26e and below val DATA_NORMAL_COMPAT_03_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, variableSizeBytes(noUnknownFieldsChannelAnnouncementSizeCodec, channelAnnouncementCodec))) :: ("channelUpdate" | variableSizeBytes(noUnknownFieldsChannelUpdateSizeCodec, channelUpdateCodec)) :: ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec)) :: - ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).as[DATA_NORMAL].decodeOnly + ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool) :: ("channelAnnouncement" | optional(bool, variableSizeBytes(uint16, channelAnnouncementCodec))) :: ("channelUpdate" | variableSizeBytes(uint16, channelUpdateCodec)) :: ("localShutdown" | optional(bool, shutdownCodec)) :: ("remoteShutdown" | optional(bool, shutdownCodec)) :: - ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).as[DATA_NORMAL].decodeOnly + ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodec) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index 7dbca5ba58..02882eeb0a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -242,7 +242,10 @@ private[channel] object ChannelCodecs1 { val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] + ("lastSent" | lengthDelimited(channelReadyCodec))).map { + case commitments :: shortChannelId :: lastSent :: HNil => + DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: @@ -252,7 +255,10 @@ private[channel] object ChannelCodecs1 { ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).as[DATA_NORMAL] + ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodec) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index 1033445807..c41d9f26be 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -277,7 +277,10 @@ private[channel] object ChannelCodecs2 { val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: - ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] + ("lastSent" | lengthDelimited(channelReadyCodec))).map { + case commitments :: shortChannelId :: lastSent :: HNil => + DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: @@ -287,7 +290,10 @@ private[channel] object ChannelCodecs2 { ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).as[DATA_NORMAL] + ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( ("commitments" | commitmentsCodec) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index 8a4cb224d8..98023ef261 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage -import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, PermanentChannelFeature} +import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, LocalAlias, PermanentChannelFeature} import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -323,9 +323,18 @@ private[channel] object ChannelCodecs3 { ("deferred" | optional(bool8, lengthDelimited(channelReadyCodec))) :: ("lastSent" | either(bool8, lengthDelimited(fundingCreatedCodec), lengthDelimited(fundingSignedCodec)))).as[DATA_WAIT_FOR_FUNDING_CONFIRMED] - val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( + val DATA_WAIT_FOR_CHANNEL_READY_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: + ("lastSent" | lengthDelimited(channelReadyCodec))).map { + case commitments :: shortChannelId :: lastSent :: HNil => + DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + }.decodeOnly + + val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( + ("commitments" | commitmentsCodec) :: + ("realShortChannelId_opt" | optional(bool8, realshortchannelid)) :: + ("localAlias" | localalias) :: ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] val DATA_NORMAL_COMPAT_02_Codec: Codec[DATA_NORMAL] = ( @@ -336,9 +345,12 @@ private[channel] object ChannelCodecs3 { ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: - ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).as[DATA_NORMAL] + ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly - val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( + val DATA_NORMAL_COMPAT_07_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: ("shortChannelId" | shortchannelid) :: ("buried" | bool8) :: @@ -346,6 +358,21 @@ private[channel] object ChannelCodecs3 { ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("closingFeerates" | optional(bool8, closingFeeratesCodec))).map { + case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => + DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + }.decodeOnly + + val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( + ("commitments" | commitmentsCodec) :: + ("realShortChannelId_opt" | optional(bool8, realshortchannelid)) :: + ("buried" | bool8) :: + ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: + ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: + ("localAlias" | discriminated[LocalAlias].by(uint16).typecase(1, localalias)) :: // forward-compatible with listOfN(uint16, localalias) in case we want to store a list of local aliases later + ("remoteAlias" | optional(bool8, shortchannelid)) :: + ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: + ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | optional(bool8, closingFeeratesCodec))).as[DATA_NORMAL] val DATA_SHUTDOWN_COMPAT_03_Codec: Codec[DATA_SHUTDOWN] = ( @@ -387,8 +414,9 @@ private[channel] object ChannelCodecs3 { // Order matters! val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) + .typecase(0x09, Codecs.DATA_NORMAL_Codec) .typecase(0x08, Codecs.DATA_SHUTDOWN_Codec) - .typecase(0x07, Codecs.DATA_NORMAL_Codec) + .typecase(0x07, Codecs.DATA_NORMAL_COMPAT_07_Codec) .typecase(0x06, Codecs.DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT_Codec) .typecase(0x05, Codecs.DATA_CLOSING_Codec) .typecase(0x04, Codecs.DATA_NEGOTIATING_Codec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 78ad89f00a..2efa06c9cb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -18,10 +18,10 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transaction} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.ChannelFlags import fr.acinq.eclair.crypto.Mac32 -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -132,6 +132,10 @@ object CommonCodecs { val shortchannelid: Codec[ShortChannelId] = int64.xmap(l => ShortChannelId(l), s => s.toLong) + val realshortchannelid: Codec[RealShortChannelId] = shortchannelid.narrow[RealShortChannelId](scid => Attempt.successful(scid.toReal), scid => scid) + + val localalias: Codec[LocalAlias] = shortchannelid.narrow[LocalAlias](scid => Attempt.successful(scid.toAlias), scid => scid) + val privateKey: Codec[PrivateKey] = Codec[PrivateKey]( (priv: PrivateKey) => bytes(32).encode(priv.value), (wire: BitVector) => bytes(32).decode(wire).map(_.map(b => PrivateKey(b))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 0315ea9a1d..4ffa28885a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -281,7 +281,7 @@ object LightningMessageCodecs { val announcementSignaturesCodec: Codec[AnnouncementSignatures] = ( ("channelId" | bytes32) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("nodeSignature" | bytes64) :: ("bitcoinSignature" | bytes64) :: ("tlvStream" | AnnouncementSignaturesTlv.announcementSignaturesTlvCodec)).as[AnnouncementSignatures] @@ -289,7 +289,7 @@ object LightningMessageCodecs { val channelAnnouncementWitnessCodec = ("features" | featuresCodec) :: ("chainHash" | bytes32) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("nodeId1" | publicKey) :: ("nodeId2" | publicKey) :: ("bitcoinKey1" | publicKey) :: @@ -368,10 +368,10 @@ object LightningMessageCodecs { .\(0) { case a@EncodedShortChannelIds(_, Nil) => a // empty list is always encoded with encoding type 'uncompressed' for compatibility with other implementations case a@EncodedShortChannelIds(EncodingType.UNCOMPRESSED, _) => a - }((provide[EncodingType](EncodingType.UNCOMPRESSED) :: list(shortchannelid)).as[EncodedShortChannelIds]) + }((provide[EncodingType](EncodingType.UNCOMPRESSED) :: list(realshortchannelid)).as[EncodedShortChannelIds]) .\(1) { case a@EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, _) => a - }((provide[EncodingType](EncodingType.COMPRESSED_ZLIB) :: zlib(list(shortchannelid))).as[EncodedShortChannelIds]) + }((provide[EncodingType](EncodingType.COMPRESSED_ZLIB) :: zlib(list(realshortchannelid))).as[EncodedShortChannelIds]) val queryShortChannelIdsCodec: Codec[QueryShortChannelIds] = ( ("chainHash" | bytes32) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 7cc27910d8..4da90219f8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -20,9 +20,11 @@ import com.google.common.base.Charsets import com.google.common.net.InetAddresses import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, ScriptWitness, Transaction} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelType} import fr.acinq.eclair.payment.relay.Relayer +import fr.acinq.eclair.wire.protocol.ChannelReadyTlv.ShortChannelIdTlv import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import scodec.bits.ByteVector @@ -227,7 +229,9 @@ case class FundingSigned(channelId: ByteVector32, case class ChannelReady(channelId: ByteVector32, nextPerCommitmentPoint: PublicKey, - tlvStream: TlvStream[ChannelReadyTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId + tlvStream: TlvStream[ChannelReadyTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId { + val alias_opt: Option[ShortChannelId] = tlvStream.get[ShortChannelIdTlv].map(_.alias) +} case class Shutdown(channelId: ByteVector32, scriptPubKey: ByteVector, @@ -279,7 +283,7 @@ case class UpdateFee(channelId: ByteVector32, tlvStream: TlvStream[UpdateFeeTlv] = TlvStream.empty) extends ChannelMessage with UpdateMessage with HasChannelId case class AnnouncementSignatures(channelId: ByteVector32, - shortChannelId: ShortChannelId, + shortChannelId: RealShortChannelId, nodeSignature: ByteVector64, bitcoinSignature: ByteVector64, tlvStream: TlvStream[AnnouncementSignaturesTlv] = TlvStream.empty) extends RoutingMessage with HasChannelId @@ -290,7 +294,7 @@ case class ChannelAnnouncement(nodeSignature1: ByteVector64, bitcoinSignature2: ByteVector64, features: Features[Feature], chainHash: ByteVector32, - shortChannelId: ShortChannelId, + shortChannelId: RealShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, bitcoinKey1: PublicKey, @@ -394,7 +398,7 @@ object EncodingType { } // @formatter:on -case class EncodedShortChannelIds(encoding: EncodingType, array: List[ShortChannelId]) { +case class EncodedShortChannelIds(encoding: EncodingType, array: List[RealShortChannelId]) { /** custom toString because it can get huge in logs */ override def toString: String = s"EncodedShortChannelIds($encoding,${array.headOption.getOrElse("")}->${array.lastOption.getOrElse("")} size=${array.size})" } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 86d649d31a..3fda4e6d99 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -25,6 +25,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, SatoshiLong} import fr.acinq.eclair.ApiTypes.{ChannelIdentifier, ChannelNotFound} import fr.acinq.eclair.TestConstants._ +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.DummyOnChainWallet import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.channel._ @@ -208,11 +209,11 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I ) val (a, b, c, d, e) = (a_priv.publicKey, b_priv.publicKey, c_priv.publicKey, d_priv.publicKey, e_priv.publicKey) - val ann_ab = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(1), a, b, a, b, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_ae = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(4), a, e, a, e, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_bc = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(2), b, c, b, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_cd = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(3), c, d, c, d, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_ec = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(7), e, c, e, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ab = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(1), a, b, a, b, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ae = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(4), a, e, a, e, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_bc = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(2), b, c, b, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_cd = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(3), c, d, c, d, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ec = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(7), e, c, e, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) assert(Announcements.isNode1(a, b)) assert(Announcements.isNode1(b, c)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala index e26254dddf..136839af93 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala @@ -57,4 +57,17 @@ class ShortChannelIdSpec extends AnyFunSuite { assert(Try(ShortChannelId("42000x27")).isFailure) assert(Try(ShortChannelId("42000x")).isFailure) } + + test("scids key space") { + + val alias = ShortChannelId.generateLocalAlias() + val realScid = alias.toReal + val scid = ShortChannelId(realScid.toLong) + + val m = Map(alias -> "alias", realScid -> "real", scid -> "unknown") + + // all scids are in the same key space + assert(m.size == 1) + + } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala index 4979d559af..de8c278355 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala @@ -89,4 +89,6 @@ object TestUtils { seedFile } + def realScid(l: Long): RealShortChannelId = ShortChannelId(l).toReal + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala index b521692e20..6b53677fcf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala @@ -22,6 +22,7 @@ import akka.actor.{ActorRef, Props, typed} import akka.pattern.pipe import akka.testkit.TestProbe import fr.acinq.bitcoin.scalacompat.{Block, Btc, MilliBtcDouble, OutPoint, SatoshiLong, Script, Transaction, TxOut} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.WatcherSpec._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient @@ -108,8 +109,8 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind val w1 = WatchFundingSpent(TestProbe().ref, txid, outputIndex, hints = Set.empty) val w2 = WatchFundingSpent(TestProbe().ref, txid, outputIndex, hints = Set.empty) - val w3 = WatchExternalChannelSpent(TestProbe().ref, txid, outputIndex, ShortChannelId(1)) - val w4 = WatchExternalChannelSpent(TestProbe().ref, randomBytes32(), 5, ShortChannelId(1)) + val w3 = WatchExternalChannelSpent(TestProbe().ref, txid, outputIndex, realScid(1)) + val w4 = WatchExternalChannelSpent(TestProbe().ref, randomBytes32(), 5, realScid(1)) val w5 = WatchFundingConfirmed(TestProbe().ref, txid, 3) // we test as if the collection was immutable @@ -210,7 +211,7 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind val (tx1, tx2) = createUnspentTxChain(tx, priv) val listener = TestProbe() - watcher ! WatchExternalChannelSpent(listener.ref, tx.txid, outputIndex, ShortChannelId(5)) + watcher ! WatchExternalChannelSpent(listener.ref, tx.txid, outputIndex, realScid(5)) watcher ! WatchFundingSpent(listener.ref, tx.txid, outputIndex, Set.empty) listener.expectNoMessage(1 second) @@ -221,7 +222,7 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind probe.expectMsg(tx1.txid) // tx and tx1 aren't confirmed yet, but we trigger the WatchEventSpent when we see tx1 in the mempool. listener.expectMsgAllOf( - WatchExternalChannelSpentTriggered(ShortChannelId(5)), + WatchExternalChannelSpentTriggered(realScid(5)), WatchFundingSpentTriggered(tx1) ) // Let's confirm tx and tx1: seeing tx1 in a block should trigger WatchEventSpent again, but not WatchEventSpentBasic @@ -267,8 +268,8 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind listener.expectMsg(WatchOutputSpentTriggered(tx1)) watcher ! StopWatching(listener.ref) - watcher ! WatchExternalChannelSpent(listener.ref, tx1.txid, 0, ShortChannelId(1)) - listener.expectMsg(WatchExternalChannelSpentTriggered(ShortChannelId(1))) + watcher ! WatchExternalChannelSpent(listener.ref, tx1.txid, 0, realScid(1)) + listener.expectMsg(WatchExternalChannelSpentTriggered(realScid(1))) watcher ! WatchFundingSpent(listener.ref, tx1.txid, 0, Set.empty) listener.expectMsg(WatchFundingSpentTriggered(tx2)) }) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 47de53b8d3..1f01194324 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.UpdateAddHtlc -import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong} +import fr.acinq.eclair.{BlockHeight, Features, MilliSatoshiLong, TestKitBaseClass, TimestampSecond, TimestampSecondLong} import org.scalatest.Tag import org.scalatest.funsuite.AnyFunSuiteLike import scodec.bits.HexStringSyntax @@ -40,13 +40,14 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging test("compute the funding tx min depth according to funding amount") { - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(1)) == 4) - assert(Helpers.minDepthForFunding(nodeParams.channelConf.copy(minDepthBlocks = 6), Btc(1)) == 6) // 4 conf would be enough but we use min-depth=6 - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(6.25)) == 16) // we use scaling_factor=15 and a fixed block reward of 6.25BTC - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(12.50)) == 31) - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(12.60)) == 32) - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(30)) == 73) - assert(Helpers.minDepthForFunding(nodeParams.channelConf, Btc(50)) == 121) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(1)) == 4) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf.copy(minDepthBlocks = 6), ChannelFeatures(), Btc(1)) == 6) // 4 conf would be enough but we use min-depth=6 + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(6.25)) == 16) // we use scaling_factor=15 and a fixed block reward of 6.25BTC + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.50)) == 31) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.60)) == 32) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(30)) == 73) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(50)) == 121) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(Features.ZeroConf), Btc(50)) == 0) } test("compute refresh delay") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 0d573db6bd..181ed4ae08 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -159,8 +159,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional).updated(Features.ZeroConf, FeatureSupport.Optional)) .initFeatures() val bobInitFeatures = Bob.nodeParams.features .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.Wumbo))(_.updated(Features.Wumbo, FeatureSupport.Optional)) @@ -170,8 +169,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional).updated(Features.ZeroConf, FeatureSupport.Optional)) .initFeatures() val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, channelFlags.announceChannel) @@ -224,21 +222,24 @@ trait ChannelStateTestsBase extends Assertions with Eventually { bob2alice.forward(alice) assert(alice2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId != ByteVector32.Zeroes) alice2blockchain.expectMsgType[WatchFundingSpent] - alice2blockchain.expectMsgType[WatchFundingConfirmed] + val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] assert(bob2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId != ByteVector32.Zeroes) bob2blockchain.expectMsgType[WatchFundingSpent] bob2blockchain.expectMsgType[WatchFundingConfirmed] - + val bobWatchFundingConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] eventually(assert(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) - bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + alice ! fundingConfirmedEvent(aliceWatchFundingConfirmed, fundingTx) + bob ! fundingConfirmedEvent(bobWatchFundingConfirmed, fundingTx) alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] alice2bob.expectMsgType[ChannelReady] alice2bob.forward(bob) bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) + // we don't forward the channel updates, in reality they would be processed by the router + alice2bob.expectMsgType[ChannelUpdate] + bob2alice.expectMsgType[ChannelUpdate] alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] bob2blockchain.expectMsgType[WatchFundingDeeplyBuried] eventually(assert(alice.stateName == NORMAL)) @@ -250,6 +251,13 @@ trait ChannelStateTestsBase extends Assertions with Eventually { fundingTx } + /** This simulates the behavior of our watcher: it replies to zero-conf watches with a zero block height. */ + def fundingConfirmedEvent(watch: WatchFundingConfirmed, fundingTx: Transaction) = if (watch.minDepth == 0) { + WatchFundingConfirmedTriggered(BlockHeight(0), 0, fundingTx) + } else { + WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + } + def localOrigin(replyTo: ActorRef): Origin.LocalHot = Origin.LocalHot(replyTo, UUID.randomUUID()) def makeCmdAdd(amount: MilliSatoshi, destination: PublicKey, currentBlockHeight: BlockHeight): (ByteVector32, CMD_ADD_HTLC) = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 7e1e1be587..d776ccecce 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -288,7 +288,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui awaitCond(bob.stateName == CLOSED) } - test("recv OpenChannel (zeroconf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv OpenChannel (zeroconf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] alice2bob.forward(bob, open) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index c25e605975..5f0007e698 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -92,7 +92,18 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS assert(txPublished.tx.txid == fundingTxId) assert(txPublished.miningFee > 0.sat) val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] - assert(watchConfirmed.minDepth == Alice.nodeParams.channelConf.minDepthBlocks) + assert(watchConfirmed.minDepth == 1) // when funder we trust ourselves so we never wait more than 1 block + aliceOrigin.expectMsgType[ChannelOpenResponse.ChannelOpened] + } + + test("recv FundingSigned with valid signature (zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + bob2alice.expectMsgType[FundingSigned] + bob2alice.forward(alice) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) + alice2blockchain.expectMsgType[WatchFundingSpent] + val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] + assert(watchConfirmed.minDepth == 0) // zeroconf aliceOrigin.expectMsgType[ChannelOpenResponse.ChannelOpened] } @@ -103,8 +114,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) alice2blockchain.expectMsgType[WatchFundingSpent] val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] - // when we are funder, we keep our regular min depth even for wumbo channels - assert(watchConfirmed.minDepth == Alice.nodeParams.channelConf.minDepthBlocks) + assert(watchConfirmed.minDepth == 1) // when funder we trust ourselves so we never wait more than 1 block aliceOrigin.expectMsgType[ChannelOpenResponse.ChannelOpened] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 13c892037a..9af8ce60d5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.channel.states.c import akka.testkit.{TestFSMRef, TestProbe} +import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.{ByteVector32, Transaction} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ @@ -25,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, ShortChannelId, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -67,14 +68,14 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2blockchain.expectMsgType[WatchFundingSpent] - alice2blockchain.expectMsgType[WatchFundingConfirmed] + val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] bob2blockchain.expectMsgType[TxPublisher.SetChannelId] bob2blockchain.expectMsgType[WatchFundingSpent] - bob2blockchain.expectMsgType[WatchFundingConfirmed] + val bobWatchFundingConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) - bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + alice ! fundingConfirmedEvent(aliceWatchFundingConfirmed, fundingTx) + bob ! fundingConfirmedEvent(bobWatchFundingConfirmed, fundingTx) alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] alice2bob.expectMsgType[ChannelReady] @@ -86,13 +87,64 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady") { f => import f._ - bob2alice.expectMsgType[ChannelReady] + // we have a real scid at this stage, because this isn't a zero-conf channel + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.nonEmpty) + val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) + val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // we have a real scid, but this is a private channel so alice uses bob's alias + assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) + } + + test("recv ChannelReady (no alias)") { f => + import f._ + // we have a real scid at this stage, because this isn't a zero-conf channel + val realScid = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.get + val channelReady = bob2alice.expectMsgType[ChannelReady] + val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) + bob2alice.forward(alice, channelReadyNoAlias) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // this is a private channel but bob didn't send an alias so we use the real scid + assert(initialChannelUpdate.shortChannelId == realScid) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) + awaitCond(alice.stateName == NORMAL) + } + + test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + // zero-conf channel: we don't have a real scid + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + val channelReady = bob2alice.expectMsgType[ChannelReady] + bob2alice.forward(alice) + val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // this is a private channel so alice uses bob's alias + assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + bob2alice.expectNoMessage(200 millis) + awaitCond(alice.stateName == NORMAL) + } + + test("recv ChannelReady (zero-conf, no alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + // zero-conf channel: we don't have a real scid + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + val channelReady = bob2alice.expectMsgType[ChannelReady] + val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) + bob2alice.forward(alice, channelReadyNoAlias) + val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // edge case: we have neither a real scid nor an alias, we use a fake scid + assert(initialChannelUpdate.shortChannelId === ShortChannelId(0)) + assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + bob2alice.expectNoMessage(200 millis) + awaitCond(alice.stateName == NORMAL) } test("recv WatchFundingSpentTriggered (remote commit)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 440a11a98c..e0e578b2d2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -110,7 +110,9 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) alice2blockchain.expectMsgType[WatchFundingLost] - alice2bob.expectMsgType[ChannelReady] + val channelReady = alice2bob.expectMsgType[ChannelReady] + // we always send an alias + assert(channelReady.alias_opt.isDefined) } test("recv WatchFundingConfirmedTriggered (bad funding pubkey script)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 2930c973ba..d73a80c198 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -18,9 +18,9 @@ package fr.acinq.eclair.channel.states.e import akka.actor.ActorRef import akka.testkit.TestProbe +import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, OutPoint, SatoshiLong, Script, Transaction} -import fr.acinq.bitcoin.ScriptFlags import fr.acinq.eclair.Features.StaticRemoteKey import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.UInt64.Conversions._ @@ -3400,55 +3400,116 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(addSettled.htlc == htlc1) } - test("recv WatchFundingDeeplyBuriedTriggered", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + test("recv WatchFundingDeeplyBuriedTriggered (public channel)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] - // public channel: we don't send the channel_update directly to the peer + assert(annSigs.shortChannelId == realShortChannelId) + // alice updates her internal state + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(annSigs.shortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // public channel: we prefer the real scid alias and it hasn't changed, so we don't send a new channel_update alice2bob.expectNoMessage(1 second) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried) // we don't re-publish the same channel_update if there was no change channelUpdateListener.expectNoMessage(1 second) } - test("recv WatchFundingDeeplyBuriedTriggered (short channel id changed)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + test("recv WatchFundingDeeplyBuriedTriggered (public channel, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400001), 22, null) + // in zero-conf channel we don't have a real short channel id when going to NORMAL state + assert(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + // funding tx coordinates (unknown before) + val (blockHeight, txIndex) = (BlockHeight(400000), 42) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) + val realShortChannelId = ShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] - // public channel: we don't send the channel_update directly to the peer - alice2bob.expectNoMessage(1 second) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried) - assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) + assert(annSigs.shortChannelId == realShortChannelId) + // alice updates her internal state + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(annSigs.shortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // public channel: we prefer the real scid alias so we switched from remote alias to real scid : we send a new channel_update + val channelUpdate1 = alice2bob.expectMsgType[ChannelUpdate] + assert(channelUpdate1.shortChannelId == realShortChannelId) + assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate == channelUpdate1) + channelUpdateListener.expectNoMessage(1 second) + } + + test("recv WatchFundingDeeplyBuriedTriggered (public channel, short channel id changed)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + import f._ + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + // new funding tx coordinates (there was a reorg) + val (blockHeight1, txIndex1) = (blockHeight + 10, txIndex + 10) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight1, txIndex1, null) + val newRealShortChannelId = ShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) + val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] + assert(annSigs.shortChannelId == newRealShortChannelId) + // update data with real short channel id + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(newRealShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // public channel: we prefer the real scid alias and the real scid alias changed, so we send a new channel_update + val channelUpdate1 = alice2bob.expectMsgType[ChannelUpdate] + assert(channelUpdate1.shortChannelId == newRealShortChannelId) + assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate == channelUpdate1) channelUpdateListener.expectNoMessage(1 second) } test("recv WatchFundingDeeplyBuriedTriggered (private channel)") { f => import f._ - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - // private channel: we send the channel_update directly to the peer - val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) + // update data with real short channel id + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(realShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one + alice2bob.expectNoMessage() + // we don't re-publish the same channel_update if there was no change + channelUpdateListener.expectNoMessage(1 second) + } + + test("recv WatchFundingDeeplyBuriedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) + // update data with real short channel id + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(realShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one + alice2bob.expectNoMessage() // we don't re-publish the same channel_update if there was no change channelUpdateListener.expectNoMessage(1 second) } test("recv WatchFundingDeeplyBuriedTriggered (private channel, short channel id changed)") { f => import f._ - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400001), 22, null) - // private channel: we send the channel_update directly to the peer - val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried) - // LocalChannelUpdate should not be published - assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + // new funding tx coordinates (there was a reorg) + val (blockHeight1, txIndex1) = (blockHeight + 10, txIndex + 10) + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight1, txIndex1, null) + val newRealShortChannelId = ShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) + // update data with real short channel id + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(newRealShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one + alice2bob.expectNoMessage() + // we don't re-publish the same channel_update if there was no change channelUpdateListener.expectNoMessage(1 second) } test("recv AnnouncementSignatures", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + val realShortChannelId = initialState.realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] - bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + bob ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) @@ -3456,7 +3517,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.forward(alice) awaitCond({ val normal = alice.stateData.asInstanceOf[DATA_NORMAL] - normal.shortChannelId == annSigsA.shortChannelId && normal.buried && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId + normal.realShortChannelId_opt.contains(annSigsA.shortChannelId) && normal.buried && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId }) assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelAnnouncement_opt == Some(channelAnn)) } @@ -3464,9 +3525,13 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv AnnouncementSignatures (re-send)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42), 10, null) + val realShortChannelId = initialState.realShortChannelId_opt.get + // existing funding tx coordinates + val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) + + alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] - bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42), 10, null) + bob ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.{localParams, remoteParams} val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) @@ -3527,9 +3592,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_DISCONNECTED") { f => import f._ - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - val update1a = alice2bob.expectMsgType[ChannelUpdate] - assert(update1a.channelFlags.isEnabled) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.channelFlags.isEnabled) // actual test starts here alice ! INPUT_DISCONNECTED @@ -3540,10 +3603,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv INPUT_DISCONNECTED (with pending unsigned htlcs)") { f => import f._ + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.channelFlags.isEnabled) val sender = TestProbe() - alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - val update1a = alice2bob.expectMsgType[ChannelUpdate] - assert(update1a.channelFlags.isEnabled) val (_, htlc1) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice, sender.ref) sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]] val (_, htlc2) = addHtlc(10000 msat, alice, bob, alice2bob, bob2alice, sender.ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 4d4c33f829..6a4620f372 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -755,11 +755,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice2bob.expectNoMessage() bob2alice.expectNoMessage() - // funding tx gets 6 confirmations, channel is private so there is no announcement sigs + // funding tx gets 6 confirmations alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - alice2bob.expectMsgType[ChannelUpdate] - bob2alice.expectMsgType[ChannelUpdate] + // channel is private so there is no announcement sigs + // we use aliases so there is no need to resend a channel_update // we get disconnected again disconnect(alice, bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala index 39506f983f..9ef2ca1d01 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/AuditDbSpec.scala @@ -845,7 +845,7 @@ class AuditDbSpec extends AnyFunSuite { val scid = ShortChannelId(123) val remoteNodeId = randomKey().publicKey val u = Announcements.makeChannelUpdate(randomBytes32(), randomKey(), remoteNodeId, scid, CltvExpiryDelta(56), 2000 msat, 1000 msat, 999, 1000000000 msat) - dbs.audit.addChannelUpdate(ChannelUpdateParametersChanged(null, channelId, scid, remoteNodeId, u)) + dbs.audit.addChannelUpdate(ChannelUpdateParametersChanged(null, channelId, remoteNodeId, u)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala index 5d4c5dede7..801988a4a1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.db import com.softwaremill.quicklens._ import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.eclair.TestDatabases.{TestPgDatabases, TestSqliteDatabases, migrationCheck} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.db.ChannelsDbSpec.{getPgTimestamp, getTimestamp, testCases} import fr.acinq.eclair.db.DbEventHandler.ChannelEvent import fr.acinq.eclair.db.jdbc.JdbcUtils.using @@ -60,7 +61,7 @@ class ChannelsDbSpec extends AnyFunSuite { val channel1 = ChannelCodecsSpec.normal val channel2a = ChannelCodecsSpec.normal.modify(_.commitments.channelId).setTo(randomBytes32()) - val channel2b = channel2a.modify(_.shortChannelId).setTo(ShortChannelId(189371)) + val channel2b = channel2a.modify(_.realShortChannelId_opt).setTo(Some(realScid(189371))) val commitNumber = 42 val paymentHash1 = ByteVector32.Zeroes diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala index a1e39298f9..02cfa815e6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, import fr.acinq.eclair.FeatureSupport.Optional import fr.acinq.eclair.Features.VariableLengthOnion import fr.acinq.eclair.TestDatabases._ +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.db.jdbc.JdbcUtils.using import fr.acinq.eclair.db.pg.PgNetworkDb import fr.acinq.eclair.db.sqlite.SqliteNetworkDb @@ -81,7 +82,7 @@ class NetworkDbSpec extends AnyFunSuite { forAllDbs { dbs => val db = dbs.network val sig = ByteVector64.Zeroes - val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) val txid = ByteVector32.fromValidHex("0001" * 16) db.addChannel(c, txid, Satoshi(42)) assert(db.listChannels() == SortedMap(c.shortChannelId -> PublicChannel(c, txid, Satoshi(42), None, None, None))) @@ -104,9 +105,9 @@ class NetworkDbSpec extends AnyFunSuite { val b = generatePubkeyHigherThan(a) val c = generatePubkeyHigherThan(b) - val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), a.publicKey, b.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) - val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(43), a.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) - val channel_3 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(44), b.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), a.publicKey, b.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(43), a.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_3 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(44), b.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) val txid_1 = randomBytes32() val txid_2 = randomBytes32() @@ -204,7 +205,7 @@ class NetworkDbSpec extends AnyFunSuite { val pub = priv.publicKey val capacity = 10000 sat - val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id, pub, pub, pub, pub, sig, sig, sig, sig)) + val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id.toReal, pub, pub, pub, pub, sig, sig, sig, sig)) val template = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv, pub, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true) val updates = shortChannelIds.map(id => template.copy(shortChannelId = id)) val txid = randomBytes32() @@ -222,9 +223,9 @@ class NetworkDbSpec extends AnyFunSuite { forAllDbs { dbs => val db = dbs.network - db.addToPruned(shortChannelIds) + db.addToPruned(shortChannelIds.map(_.toReal)) shortChannelIds.foreach { id => assert(db.isPruned(id)) } - db.removeFromPruned(ShortChannelId(5)) + db.removeFromPruned(realScid(5)) assert(!db.isPruned(ShortChannelId(5))) } } @@ -381,7 +382,7 @@ object NetworkDbSpec { val channelTestCases: Seq[ChannelTestCase] = for (_ <- 0 until 10) yield { val a = randomKey() val b = generatePubkeyHigherThan(a) - val channel = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(Random.nextInt(1_000_000)), a.publicKey, a.publicKey, randomKey().publicKey, randomKey().publicKey, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val channel = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(Random.nextInt(1_000_000)).toReal, a.publicKey, a.publicKey, randomKey().publicKey, randomKey().publicKey, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) val channel_update_1_opt = if (Random.nextBoolean()) { Some(Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, a, b.publicKey, channel.shortChannelId, CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, Random.nextBoolean())) } else None diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 976dfcbbef..4db2f68f79 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -187,7 +187,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { // we then forge a new channel_update for B-C... val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.channelConf.expiryDelta + 1, nodes("C").nodeParams.channelConf.htlcMinimum, nodes("B").nodeParams.relayParams.publicChannelFees.feeBase, nodes("B").nodeParams.relayParams.publicChannelFees.feeProportionalMillionths, 500000000 msat) // ...and notify B's relayer - nodes("B").system.eventStream.publish(LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, shortIdBC, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC)) + nodes("B").system.eventStream.publish(LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, Some(shortIdBC), null, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC)) // we retrieve a payment hash from D val amountMsat = 4200000.msat sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), Left("1 coffee"))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala index bb3611f4d9..1391164755 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala @@ -23,6 +23,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, PaymentSecret, VariableLengthOnion} import fr.acinq.eclair.TestConstants._ +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.message.OnionMessages.{Recipient, buildMessage} @@ -333,7 +334,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi val query = QueryShortChannelIds( Alice.nodeParams.chainHash, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(42000))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(42000))), TlvStream.empty) // make sure that routing messages go through diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index c7c010a55c..b7d83a9d36 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -714,7 +714,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // | | and b -> h has fees = 0 // +---(5)--> g ---(6)--> h // and e --(4)--> f (we are a) - val channelId_bh = ShortChannelId(BlockHeight(420000), 100, 0) + val channelId_bh = ShortChannelId(BlockHeight(420000), 100, 0).toReal val chan_bh = channelAnnouncement(channelId_bh, priv_b, priv_h, priv_funding_b, priv_funding_h) val channelUpdate_bh = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, h, channelId_bh, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 0 msat, feeProportionalMillionths = 0, htlcMaximumMsat = 500000000 msat) val channelUpdate_hb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_h, b, channelId_bh, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = 500000000 msat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index b3096820c1..4aca396be2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -24,6 +24,7 @@ import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong} import fr.acinq.eclair.TestConstants.emptyOnionPacket +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ import fr.acinq.eclair.payment.IncomingPaymentPacket.ChannelRelayPacket @@ -32,7 +33,7 @@ import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, Pa import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire.protocol.PaymentOnion.{ChannelRelayPayload, ChannelRelayTlvPayload, RelayLegacyPayload} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, NodeParams, TestConstants, randomBytes32, _} +import fr.acinq.eclair.{CltvExpiry, NodeParams, RealShortChannelId, TestConstants, randomBytes32, _} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike import scodec.bits.HexStringSyntax @@ -64,6 +65,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a def expectFwdAdd(register: TestProbe[Any], channelId: ByteVector32, outAmount: MilliSatoshi, outExpiry: CltvExpiry): Register.Forward[CMD_ADD_HTLC] = { val fwd = register.expectMessageType[Register.Forward[CMD_ADD_HTLC]] + assert(fwd.message.isInstanceOf[CMD_ADD_HTLC]) // the line above doesn't check the type due to type erasure assert(fwd.channelId == channelId) assert(fwd.message.amount == outAmount) assert(fwd.message.cltvExpiry == outExpiry) @@ -73,58 +75,71 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a fwd } - test("relay htlc-add") { f => + test("relay htlc-add with the real scid") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) + val u = createLocalUpdate(realScid1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) + } + + test("relay htlc-add with the local alias") { f => + import f._ + + val payload = RelayLegacyPayload(localAlias1, outgoingAmount, outgoingExpiry) + val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) + val u = createLocalUpdate(localAlias1) + + channelRelayer ! WrappedLocalChannelUpdate(u) + channelRelayer ! Relay(r) + + expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) } test("relay an htlc-add with onion tlv payload") { f => import f._ import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv._ - val payload = ChannelRelayTlvPayload(TlvStream[OnionPaymentPayloadTlv](AmountToForward(outgoingAmount), OutgoingCltv(outgoingExpiry), OutgoingChannelId(shortId1))) + val payload = ChannelRelayTlvPayload(TlvStream[OnionPaymentPayloadTlv](AmountToForward(outgoingAmount), OutgoingCltv(outgoingExpiry), OutgoingChannelId(realScid1))) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) + val u = createLocalUpdate(realScid1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) } test("relay an htlc-add with retries") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) // we tell the relayer about the first channel - val u1 = createLocalUpdate(shortId1) + val u1 = createLocalUpdate(realScid1) channelRelayer ! WrappedLocalChannelUpdate(u1) // this is another channel, with less balance (it will be preferred) - val u2 = createLocalUpdate(shortId2, 8000000 msat) + val u2 = createLocalUpdate(realScId2, 8000000 msat) channelRelayer ! WrappedLocalChannelUpdate(u2) channelRelayer ! Relay(r) // first try - val fwd1 = expectFwdAdd(register, channelIds(shortId2), outgoingAmount, outgoingExpiry) + val fwd1 = expectFwdAdd(register, channelIds(realScId2), outgoingAmount, outgoingExpiry) // channel returns an error - fwd1.message.replyTo ! RES_ADD_FAILED(fwd1.message, HtlcValueTooHighInFlight(channelIds(shortId2), UInt64(1000000000L), 1516977616L msat), Some(u2.channelUpdate)) + fwd1.message.replyTo ! RES_ADD_FAILED(fwd1.message, HtlcValueTooHighInFlight(channelIds(realScId2), UInt64(1000000000L), 1516977616L msat), Some(u2.channelUpdate)) // second try - val fwd2 = expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + val fwd2 = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) // failure again - fwd1.message.replyTo ! RES_ADD_FAILED(fwd2.message, HtlcValueTooHighInFlight(channelIds(shortId1), UInt64(1000000000L), 1516977616L msat), Some(u1.channelUpdate)) + fwd1.message.replyTo ! RES_ADD_FAILED(fwd2.message, HtlcValueTooHighInFlight(channelIds(realScid1), UInt64(1000000000L), 1516977616L msat), Some(u1.channelUpdate)) // the relayer should give up expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryNodeFailure), commit = true)) @@ -133,7 +148,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add when we have no channel_update for the next channel") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) channelRelayer ! Relay(r) @@ -144,14 +159,14 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add when register returns an error") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) + val u = createLocalUpdate(realScid1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - val fwd = expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) fwd.replyTo ! Register.ForwardFailure(fwd) expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) @@ -160,10 +175,10 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add when the channel is advertised as unusable (down)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) - val d = LocalChannelDown(null, channelId = channelIds(shortId1), shortId1, outgoingNodeId) + val u = createLocalUpdate(realScid1) + val d = LocalChannelDown(null, channelId = channelIds(realScid1), Some(realScid1.toReal), null, outgoingNodeId) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! WrappedLocalChannelDown(d) @@ -175,9 +190,9 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add (channel disabled)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1, enabled = false) + val u = createLocalUpdate(realScid1, enabled = false) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -188,9 +203,9 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add (amount below minimum)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1, htlcMinimum = outgoingAmount + 1.msat) + val u = createLocalUpdate(realScid1, htlcMinimum = outgoingAmount + 1.msat) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -201,21 +216,21 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("relay an htlc-add (expiry larger than our requirements)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) - val u = createLocalUpdate(shortId1) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) + val u = createLocalUpdate(realScid1) val r = createValidIncomingPacket(1100000 msat, outgoingExpiry + u.channelUpdate.cltvExpiryDelta + CltvExpiryDelta(1), payload) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdAdd(register, channelIds(shortId1), payload.amountToForward, payload.outgoingCltv).message + expectFwdAdd(register, channelIds(realScid1), payload.amountToForward, payload.outgoingCltv).message } test("fail to relay an htlc-add (expiry too small)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) - val u = createLocalUpdate(shortId1) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) + val u = createLocalUpdate(realScid1) val r = createValidIncomingPacket(1100000 msat, outgoingExpiry + u.channelUpdate.cltvExpiryDelta - CltvExpiryDelta(1), payload) channelRelayer ! WrappedLocalChannelUpdate(u) @@ -227,9 +242,9 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add (fee insufficient)") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(outgoingAmount + 1.msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) + val u = createLocalUpdate(realScid1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -240,25 +255,25 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("relay an htlc-add that would fail (fee insufficient) with a recent channel update but succeed with the previous update") { f => import f._ - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(outgoingAmount + 1.msat, CltvExpiry(400100), payload) - val u1 = createLocalUpdate(shortId1, timestamp = TimestampSecond.now(), feeBaseMsat = 1 msat, feeProportionalMillionths = 0) + val u1 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now(), feeBaseMsat = 1 msat, feeProportionalMillionths = 0) channelRelayer ! WrappedLocalChannelUpdate(u1) channelRelayer ! Relay(r) // relay succeeds with current channel update (u1) with lower fees - expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) - val u2 = createLocalUpdate(shortId1, timestamp = TimestampSecond.now() - 530) + val u2 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now() - 530) channelRelayer ! WrappedLocalChannelUpdate(u2) channelRelayer ! Relay(r) // relay succeeds because the current update (u2) with higher fees occurred less than 10 minutes ago - expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) - val u3 = createLocalUpdate(shortId1, timestamp = TimestampSecond.now() - 601) + val u3 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now() - 601) channelRelayer ! WrappedLocalChannelUpdate(u1) channelRelayer ! WrappedLocalChannelUpdate(u3) @@ -271,11 +286,11 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("fail to relay an htlc-add (local error)") { f => import f._ - val channelId1 = channelIds(shortId1) - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val channelId1 = channelIds(realScid1) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) - val u_disabled = createLocalUpdate(shortId1, enabled = false) + val u = createLocalUpdate(realScid1) + val u_disabled = createLocalUpdate(realScid1, enabled = false) case class TestCase(exc: ChannelException, update: ChannelUpdate, failure: FailureMessage) @@ -290,7 +305,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a testCases.foreach { testCase => channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - val fwd = expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) fwd.message.replyTo ! RES_ADD_FAILED(fwd.message, testCase.exc, Some(testCase.update)) expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(testCase.failure), commit = true)) } @@ -300,22 +315,22 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a import f._ /** This is just a simplified helper function with random values for fields we are not using here */ - def dummyLocalUpdate(shortChannelId: ShortChannelId, remoteNodeId: PublicKey, availableBalanceForSend: MilliSatoshi, capacity: Satoshi) = { + def dummyLocalUpdate(shortChannelId: RealShortChannelId, remoteNodeId: PublicKey, availableBalanceForSend: MilliSatoshi, capacity: Satoshi) = { val channelId = randomBytes32() val update = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey(), remoteNodeId, shortChannelId, CltvExpiryDelta(10), 100 msat, 1000 msat, 100, capacity.toMilliSatoshi) val commitments = PaymentPacketSpec.makeCommitments(channelId, availableBalanceForSend, testCapacity = capacity) - LocalChannelUpdate(null, channelId, shortChannelId, remoteNodeId, None, update, commitments) + LocalChannelUpdate(null, channelId, Some(shortChannelId), null, remoteNodeId, None, update, commitments) } val (a, b) = (randomKey().publicKey, randomKey().publicKey) val channelUpdates = Map( - ShortChannelId(11111) -> dummyLocalUpdate(ShortChannelId(11111), a, 100000000 msat, 200000 sat), - ShortChannelId(12345) -> dummyLocalUpdate(ShortChannelId(12345), a, 10000000 msat, 200000 sat), - ShortChannelId(22222) -> dummyLocalUpdate(ShortChannelId(22222), a, 10000000 msat, 100000 sat), - ShortChannelId(22223) -> dummyLocalUpdate(ShortChannelId(22223), a, 9000000 msat, 50000 sat), - ShortChannelId(33333) -> dummyLocalUpdate(ShortChannelId(33333), a, 100000 msat, 50000 sat), - ShortChannelId(44444) -> dummyLocalUpdate(ShortChannelId(44444), b, 1000000 msat, 10000 sat), + ShortChannelId(11111) -> dummyLocalUpdate(realScid(11111), a, 100000000 msat, 200000 sat), + ShortChannelId(12345) -> dummyLocalUpdate(realScid(12345), a, 10000000 msat, 200000 sat), + ShortChannelId(22222) -> dummyLocalUpdate(realScid(22222), a, 10000000 msat, 100000 sat), + ShortChannelId(22223) -> dummyLocalUpdate(realScid(22223), a, 9000000 msat, 50000 sat), + ShortChannelId(33333) -> dummyLocalUpdate(realScid(33333), a, 100000 msat, 50000 sat), + ShortChannelId(44444) -> dummyLocalUpdate(realScid(44444), b, 1000000 msat, 10000 sat), ) channelUpdates.values.foreach(u => channelRelayer ! WrappedLocalChannelUpdate(u)) @@ -379,11 +394,11 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("settlement failure") { f => import f._ - val channelId1 = channelIds(shortId1) - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val channelId1 = channelIds(realScid1) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) - val u_disabled = createLocalUpdate(shortId1, enabled = false) + val u = createLocalUpdate(realScid1) + val u_disabled = createLocalUpdate(realScid1, enabled = false) val downstream_htlc = UpdateAddHtlc(channelId1, 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket) case class TestCase(result: HtlcResult, cmd: channel.HtlcSettlementCommand) @@ -399,7 +414,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a testCases.foreach { testCase => channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - val fwd = expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) fwd.message.replyTo ! RES_SUCCESS(fwd.message, channelId1) fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, downstream_htlc, testCase.result) expectFwdFail(register, r.add.channelId, testCase.cmd) @@ -411,10 +426,10 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val eventListener = TestProbe[ChannelPaymentRelayed]() system.eventStream ! EventStream.Subscribe(eventListener.ref) - val channelId1 = channelIds(shortId1) - val payload = RelayLegacyPayload(shortId1, outgoingAmount, outgoingExpiry) + val channelId1 = channelIds(realScid1) + val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(shortId1) + val u = createLocalUpdate(realScid1) val downstream_htlc = UpdateAddHtlc(channelId1, 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket) case class TestCase(result: HtlcResult) @@ -428,7 +443,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - val fwd1 = expectFwdAdd(register, channelIds(shortId1), outgoingAmount, outgoingExpiry) + val fwd1 = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) fwd1.message.replyTo ! RES_SUCCESS(fwd1.message, channelId1) fwd1.message.origin.replyTo ! RES_ADD_SETTLED(fwd1.message.origin, downstream_htlc, testCase.result) @@ -447,6 +462,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a import f._ val channelId_ab = randomBytes32() val channelId_bc = randomBytes32() + val alias_ab = ShortChannelId.generateLocalAlias() val a = PaymentPacketSpec.a val sender = TestProbe[Relayer.OutgoingChannels]() @@ -457,8 +473,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channels } - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, channelUpdate_ab.shortChannelId, a, None, channelUpdate_ab, makeCommitments(channelId_ab, -2000 msat, 300000 msat))) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, -2000 msat, 300000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) val channels1 = getOutgoingChannels(true) assert(channels1.size == 2) @@ -467,33 +483,24 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a assert(channels1.last.channelUpdate == channelUpdate_bc) assert(channels1.last.toChannelBalance == Relayer.ChannelBalance(c, channelUpdate_bc.shortChannelId, 400000 msat, 0 msat, isPublic = false, isEnabled = true)) - channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_bc, channelUpdate_bc.shortChannelId, makeCommitments(channelId_bc, 200000 msat, 500000 msat))) + channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, makeCommitments(channelId_bc, 200000 msat, 500000 msat))) val channels2 = getOutgoingChannels(true) assert(channels2.last.commitments.availableBalanceForReceive == 500000.msat && channels2.last.commitments.availableBalanceForSend == 200000.msat) - channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_ab, channelUpdate_ab.shortChannelId, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) - channelRelayer ! WrappedLocalChannelDown(LocalChannelDown(null, channelId_bc, channelUpdate_bc.shortChannelId, c)) + channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelDown(LocalChannelDown(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, c)) val channels3 = getOutgoingChannels(true) assert(channels3.size == 1 && channels3.head.commitments.availableBalanceForSend == 100000.msat) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, channelUpdate_ab.shortChannelId, a, None, channelUpdate_ab.copy(channelFlags = ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = true)), makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab.copy(channelFlags = ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = true)), makeCommitments(channelId_ab, 100000 msat, 200000 msat))) val channels4 = getOutgoingChannels(true) assert(channels4.isEmpty) val channels5 = getOutgoingChannels(false) assert(channels5.size == 1) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, channelUpdate_ab.shortChannelId, a, None, channelUpdate_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) val channels6 = getOutgoingChannels(true) assert(channels6.size == 1) - - // Simulate a chain re-org that changes the shortChannelId: - channelRelayer ! WrappedShortChannelIdAssigned(ShortChannelIdAssigned(null, channelId_ab, ShortChannelId(42), Some(channelUpdate_ab.shortChannelId))) - - // We should receive the updated channel update containing the new shortChannelId: - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, ShortChannelId(42), a, None, channelUpdate_ab.copy(shortChannelId = ShortChannelId(42)), makeCommitments(channelId_ab, 100000 msat, 200000 msat))) - val channels7 = getOutgoingChannels(true) - assert(channels7.size == 1) - assert(channels7.head.channelUpdate.shortChannelId == ShortChannelId(42)) } } @@ -506,12 +513,20 @@ object ChannelRelayerSpec { val outgoingExpiry: CltvExpiry = CltvExpiry(400000) val outgoingNodeId: PublicKey = randomKey().publicKey - val shortId1: ShortChannelId = ShortChannelId(111111) - val shortId2: ShortChannelId = ShortChannelId(222222) + val realScid1: RealShortChannelId = ShortChannelId(111111).toReal + val realScId2: RealShortChannelId = ShortChannelId(222222).toReal + + val localAlias1: LocalAlias = ShortChannelId(111000).toAlias + val localAlias2: LocalAlias = ShortChannelId(222000).toAlias + + val channelId1: ByteVector32 = randomBytes32() + val channelId2: ByteVector32 = randomBytes32() val channelIds = Map( - shortId1 -> randomBytes32(), - shortId2 -> randomBytes32() + realScid1 -> channelId1, + realScId2 -> channelId2, + localAlias1 -> channelId1, + localAlias2 -> channelId2, ) def createValidIncomingPacket(amountIn: MilliSatoshi, expiryIn: CltvExpiry, payload: ChannelRelayPayload): IncomingPaymentPacket.ChannelRelayPacket = { @@ -519,10 +534,15 @@ object ChannelRelayerSpec { ChannelRelayPacket(add_ab, payload, emptyOnionPacket) } - def createLocalUpdate(shortChannelId: ShortChannelId, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100): LocalChannelUpdate = { - val channelId = channelIds(shortChannelId) - val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, shortChannelId, timestamp, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, feeBaseMsat, feeProportionalMillionths, Some(capacity.toMilliSatoshi)) + /** + * @param realScidOrAlias will be used as id for the channel update + */ + def createLocalUpdate(realScidOrAlias: ShortChannelId, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100): LocalChannelUpdate = { + val channelId = channelIds(realScidOrAlias) + val realScid = channelIds.collectFirst { case (realScid: RealShortChannelId, cid) if cid == channelId => realScid }.get + val localAlias = channelIds.collectFirst { case (localAlias: LocalAlias, cid) if cid == channelId => localAlias }.get + val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, realScidOrAlias, timestamp, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, feeBaseMsat, feeProportionalMillionths, Some(capacity.toMilliSatoshi)) val commitments = PaymentPacketSpec.makeCommitments(channelId, testAvailableBalanceForSend = balance, testCapacity = capacity) - LocalChannelUpdate(null, channelId, shortChannelId, outgoingNodeId, None, update, commitments) + LocalChannelUpdate(null, channelId, Some(realScid), localAlias, outgoingNodeId, None, update, commitments) } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala index 0df90707cb..e6fc5e212c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala @@ -78,7 +78,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat assert(sender.expectMessageType[Relayer.OutgoingChannels].channels.isEmpty) // We publish a channel update, that should be picked up by the channel relayer - system.eventStream ! EventStream.Publish(LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc, makeCommitments(channelId_bc))) + system.eventStream ! EventStream.Publish(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), null, c, None, channelUpdate_bc, makeCommitments(channelId_bc))) eventually(PatienceConfiguration.Timeout(30 seconds), PatienceConfiguration.Interval(1 second)) { childActors.channelRelayer ! ChannelRelayer.GetOutgoingChannels(sender.ref.toClassic, GetOutgoingChannels()) val channels = sender.expectMessageType[Relayer.OutgoingChannels].channels diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala index b87f78b3f6..55aa7e6641 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala @@ -64,7 +64,7 @@ class AnnouncementsBatchValidationSpec extends AnyFunSuite { bitcoinClient.validate(announcements(0)).pipeTo(sender.ref) sender.expectMsgType[ValidateResult].fundingTx.isRight - bitcoinClient.validate(announcements(1).copy(shortChannelId = ShortChannelId(Long.MaxValue))).pipeTo(sender.ref) // invalid block height + bitcoinClient.validate(announcements(1).copy(shortChannelId = ShortChannelId(Long.MaxValue).toReal)).pipeTo(sender.ref) // invalid block height sender.expectMsgType[ValidateResult].fundingTx.isRight bitcoinClient.validate(announcements(2).copy(shortChannelId = ShortChannelId(BlockHeight(500), 1000, 0))).pipeTo(sender.ref) // invalid tx index diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index b7e4b52f35..8f9145a4f1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Block import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.TestConstants.Alice +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair._ import fr.acinq.eclair.router.Announcements._ import fr.acinq.eclair.wire.protocol.ChannelUpdate.ChannelFlags @@ -43,12 +44,12 @@ class AnnouncementsSpec extends AnyFunSuite { test("create valid signed channel announcement") { val (node_a, node_b, bitcoin_a, bitcoin_b) = (randomKey(), randomKey(), randomKey(), randomKey()) - val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, ShortChannelId(42L), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, Features.empty) + val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, realScid(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, Features.empty) val node_a_sig = Announcements.signChannelAnnouncement(witness, node_a) val bitcoin_a_sig = Announcements.signChannelAnnouncement(witness, bitcoin_a) val node_b_sig = Announcements.signChannelAnnouncement(witness, node_b) val bitcoin_b_sig = Announcements.signChannelAnnouncement(witness, bitcoin_b) - val ann = makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42L), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig) + val ann = makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig) assert(checkSigs(ann)) assert(checkSigs(ann.copy(nodeId1 = randomKey().publicKey)) == false) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index 625f814d44..afeaf971e4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -82,6 +82,9 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val channelId_ag_private = randomBytes32() + val alias_ab = ShortChannelId.generateLocalAlias() + val alias_ag_private = ShortChannelId.generateLocalAlias() + val chan_ab = channelAnnouncement(scid_ab, priv_a, priv_b, priv_funding_a, priv_funding_b) val chan_bc = channelAnnouncement(scid_bc, priv_b, priv_c, priv_funding_b, priv_funding_c) val chan_cd = channelAnnouncement(scid_cd, priv_c, priv_d, priv_funding_c, priv_funding_d) @@ -96,8 +99,8 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val update_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, scid_cd, CltvExpiryDelta(3), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 4, htlcMaximumMsat = htlcMaximum) val update_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, scid_ef, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = htlcMaximum) val update_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, scid_ef, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = htlcMaximum) - val update_ag_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, scid_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) - val update_ga_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, a, scid_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) + val update_ag_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, alias_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) + val update_ga_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, a, alias_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) val update_gh = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, h, scid_gh, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) val update_hg = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_h, g, scid_gh, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) @@ -112,8 +115,8 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi assert(ChannelDesc(update_bc, chan_bc) == ChannelDesc(chan_bc.shortChannelId, b, c)) assert(ChannelDesc(update_cd, chan_cd) == ChannelDesc(chan_cd.shortChannelId, c, d)) assert(ChannelDesc(update_ef, chan_ef) == ChannelDesc(chan_ef.shortChannelId, e, f)) - assert(ChannelDesc(update_ag_private, PrivateChannel(scid_ag_private, channelId_ag_private, a, g, None, None, ChannelMeta(1000 msat, 2000 msat))) == ChannelDesc(scid_ag_private, a, g)) - assert(ChannelDesc(update_ag_private, PrivateChannel(scid_ag_private, channelId_ag_private, g, a, None, None, ChannelMeta(2000 msat, 1000 msat))) == ChannelDesc(scid_ag_private, a, g)) + assert(ChannelDesc(update_ag_private, PrivateChannel(alias_ag_private, channelId_ag_private, a, g, None, None, ChannelMeta(1000 msat, 2000 msat))) == ChannelDesc(alias_ag_private, a, g)) + assert(ChannelDesc(update_ag_private, PrivateChannel(alias_ag_private, channelId_ag_private, g, a, None, None, ChannelMeta(2000 msat, 1000 msat))) == ChannelDesc(alias_ag_private, a, g)) assert(ChannelDesc(update_gh, chan_gh) == ChannelDesc(chan_gh.shortChannelId, g, h)) // let's set up the router @@ -151,7 +154,7 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_gh)) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_hg)) // then private channels - sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, scid_ag_private, g, None, update_ag_private, CommitmentsSpec.makeCommitments(30000000 msat, 8000000 msat, a, g, announceChannel = false))) + sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, g, None, update_ag_private, CommitmentsSpec.makeCommitments(30000000 msat, 8000000 msat, a, g, announceChannel = false))) // watcher receives the get tx requests assert(watcher.expectMsgType[ValidateRequest].ann == chan_ab) assert(watcher.expectMsgType[ValidateRequest].ann == chan_bc) @@ -214,13 +217,13 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi } object BaseRouterSpec { - def channelAnnouncement(channelId: ShortChannelId, node1_priv: PrivateKey, node2_priv: PrivateKey, funding1_priv: PrivateKey, funding2_priv: PrivateKey) = { - val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, channelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, Features.empty) + def channelAnnouncement(shortChannelId: RealShortChannelId, node1_priv: PrivateKey, node2_priv: PrivateKey, funding1_priv: PrivateKey, funding2_priv: PrivateKey) = { + val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, shortChannelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, Features.empty) val node1_sig = Announcements.signChannelAnnouncement(witness, node1_priv) val funding1_sig = Announcements.signChannelAnnouncement(witness, funding1_priv) val node2_sig = Announcements.signChannelAnnouncement(witness, node2_priv) val funding2_sig = Announcements.signChannelAnnouncement(witness, funding2_priv) - makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, channelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, node1_sig, node2_sig, funding1_sig, funding2_sig) + makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, shortChannelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, node1_sig, node2_sig, funding1_sig, funding2_sig) } def channelHopFromUpdate(nodeId: PublicKey, nextNodeId: PublicKey, channelUpdate: ChannelUpdate): ChannelHop = diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala index 15cbbc832f..15322ef18a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala @@ -17,6 +17,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, SatoshiLong} +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.router.Router.{ChannelMeta, PublicChannel} import fr.acinq.eclair.router.Sync._ import fr.acinq.eclair.wire.protocol.QueryChannelRangeTlv.QueryFlags @@ -130,8 +131,8 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { assert(computeFlag(channels)(ef.shortChannelId, None, None, includeNodeAnnouncements = true) == (INCLUDE_CHANNEL_ANNOUNCEMENT | INCLUDE_CHANNEL_UPDATE_1 | INCLUDE_CHANNEL_UPDATE_2 | INCLUDE_NODE_ANNOUNCEMENT_1 | INCLUDE_NODE_ANNOUNCEMENT_2)) } - def makeShortChannelIds(height: BlockHeight, count: Int): List[ShortChannelId] = { - val output = ArrayBuffer.empty[ShortChannelId] + def makeShortChannelIds(height: BlockHeight, count: Int): List[RealShortChannelId] = { + val output = ArrayBuffer.empty[RealShortChannelId] var txIndex = 0 var outputIndex = 0 while (output.size < count) { @@ -141,7 +142,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } else { outputIndex = outputIndex + 1 } - output += ShortChannelId(height, txIndex, outputIndex) + output += ShortChannelId(height, txIndex, outputIndex).toReal } output.toList } @@ -152,7 +153,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { // check that chunks contain exactly the ids they were built from are are consistent i.e each chunk covers a range that immediately follows // the previous one even if there are gaps in block heights - def validate(ids: SortedSet[ShortChannelId], firstBlock: BlockHeight, numberOfBlocks: Long, chunks: List[ShortChannelIdsChunk]): Unit = { + def validate(ids: SortedSet[RealShortChannelId], firstBlock: BlockHeight, numberOfBlocks: Long, chunks: List[ShortChannelIdsChunk]): Unit = { @tailrec def noOverlap(chunks: List[ShortChannelIdsChunk]): Boolean = chunks match { @@ -189,14 +190,14 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { test("split short channel ids correctly (basic tests") { - def id(blockHeight: Int, txIndex: Int = 0, outputIndex: Int = 0) = ShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex) + def id(blockHeight: Int, txIndex: Int = 0, outputIndex: Int = 0) = ShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex).toReal // no ids to split { val ids = Nil val firstBlock = BlockHeight(10) val numberOfBlocks = 100 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, Nil) :: Nil) } @@ -205,7 +206,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1001), id(1002), id(1003), id(1004), id(1005)) val firstBlock = BlockHeight(10) val numberOfBlocks = 100 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, Nil) :: Nil) } @@ -214,7 +215,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1001), id(1002), id(1003), id(1004), id(1005)) val firstBlock = BlockHeight(1100) val numberOfBlocks = 100 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, Nil) :: Nil) } @@ -223,7 +224,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1001), id(1002), id(1003), id(1004), id(1005)) val firstBlock = BlockHeight(900) val numberOfBlocks = 200 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, ids.size) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, ids) :: Nil) } @@ -233,7 +234,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000, 0), id(1000, 1), id(1000, 2), id(1000, 3), id(1000, 4), id(1000, 5)) val firstBlock = BlockHeight(900) val numberOfBlocks = 200 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, ids) :: Nil) } @@ -242,7 +243,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1005), id(1012), id(1013), id(1040), id(1050)) val firstBlock = BlockHeight(900) val numberOfBlocks = 200 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) assert(chunks == List( ShortChannelIdsChunk(firstBlock, 100 + 6, List(ids(0), ids(1))), ShortChannelIdsChunk(BlockHeight(1006), 8, List(ids(2), ids(3))), @@ -255,7 +256,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1005), id(1012), id(1013), id(1040), id(1050)) val firstBlock = BlockHeight(1001) val numberOfBlocks = 200 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) assert(chunks == List( ShortChannelIdsChunk(firstBlock, 12, List(ids(1), ids(2))), ShortChannelIdsChunk(BlockHeight(1013), 1040 - 1013 + 1, List(ids(3), ids(4))), @@ -268,7 +269,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1001), id(1002), id(1003), id(1004), id(1005)) val firstBlock = BlockHeight(900) val numberOfBlocks = 105 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) assert(chunks == List( ShortChannelIdsChunk(firstBlock, 100 + 2, List(ids(0), ids(1))), ShortChannelIdsChunk(BlockHeight(1002), 2, List(ids(2), ids(3))), @@ -281,7 +282,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = List(id(1000), id(1001), id(1002), id(1003), id(1004), id(1005)) val firstBlock = BlockHeight(1001) val numberOfBlocks = 4 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 2) assert(chunks == List( ShortChannelIdsChunk(firstBlock, 2, List(ids(1), ids(2))), ShortChannelIdsChunk(BlockHeight(1003), 2, List(ids(3), ids(4))) @@ -293,13 +294,13 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { val ids = makeShortChannelIds(BlockHeight(1000), 100) val firstBlock = BlockHeight(900) val numberOfBlocks = 200 - val chunks = split(SortedSet.empty[ShortChannelId] ++ ids, firstBlock, numberOfBlocks, 10) + val chunks = split(SortedSet.empty[RealShortChannelId] ++ ids, firstBlock, numberOfBlocks, 10) assert(chunks == ShortChannelIdsChunk(firstBlock, numberOfBlocks, ids) :: Nil) } } test("split short channel ids correctly") { - val ids = SortedSet.empty[ShortChannelId] ++ makeShortChannelIds(BlockHeight(42), 100) ++ makeShortChannelIds(BlockHeight(43), 70) ++ makeShortChannelIds(BlockHeight(44), 50) ++ makeShortChannelIds(BlockHeight(45), 30) ++ makeShortChannelIds(BlockHeight(50), 120) + val ids = SortedSet.empty[RealShortChannelId] ++ makeShortChannelIds(BlockHeight(42), 100) ++ makeShortChannelIds(BlockHeight(43), 70) ++ makeShortChannelIds(BlockHeight(44), 50) ++ makeShortChannelIds(BlockHeight(45), 30) ++ makeShortChannelIds(BlockHeight(50), 120) val firstBlock = BlockHeight(0) val numberOfBlocks = 1000 @@ -311,7 +312,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } test("split short channel ids correctly (comprehensive tests)") { - val ids = SortedSet.empty[ShortChannelId] ++ makeShortChannelIds(BlockHeight(42), 100) ++ makeShortChannelIds(BlockHeight(43), 70) ++ makeShortChannelIds(BlockHeight(45), 50) ++ makeShortChannelIds(BlockHeight(47), 30) ++ makeShortChannelIds(BlockHeight(50), 120) + val ids = SortedSet.empty[RealShortChannelId] ++ makeShortChannelIds(BlockHeight(42), 100) ++ makeShortChannelIds(BlockHeight(43), 70) ++ makeShortChannelIds(BlockHeight(45), 50) ++ makeShortChannelIds(BlockHeight(47), 30) ++ makeShortChannelIds(BlockHeight(50), 120) for (firstBlock <- 0 to 60) { for (numberOfBlocks <- 1 to 60) { for (chunkSize <- 1 :: 2 :: 20 :: 50 :: 100 :: 1000 :: Nil) { @@ -357,7 +358,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } test("encode maximum size reply_channel_range") { - val scids = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => ShortChannelId(i)).toList + val scids = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => ShortChannelId(i).toReal).toList val timestamps = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => Timestamps(i.unixsec, (i + 1).unixsec)).toList val checksums = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => Checksums(i, i + 1)).toList val reply = ReplyChannelRange(Block.RegtestGenesisBlock.hash, BlockHeight(0), 100, 0, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, scids), Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, timestamps)), Some(EncodedChecksums(checksums))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index a44a9afb14..2920c97ed7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -19,6 +19,7 @@ package fr.acinq.eclair.router import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Satoshi, SatoshiLong} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate @@ -915,7 +916,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target, // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop. val updates = SortedMap( - ShortChannelId("565643x1216x0") -> PublicChannel( + ShortChannelId("565643x1216x0").toReal -> PublicChannel( ann = makeChannel(ShortChannelId("565643x1216x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -923,7 +924,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, feeBaseMsat = 1000 msat, 100, Some(15000000000L msat))), meta_opt = None ), - ShortChannelId("542280x2156x0") -> PublicChannel( + ShortChannelId("542280x2156x0").toReal -> PublicChannel( ann = makeChannel(ShortChannelId("542280x2156x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -931,7 +932,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 667 msat, 1, Some(16777000000L msat))), meta_opt = None ), - ShortChannelId("565779x2711x0") -> PublicChannel( + ShortChannelId("565779x2711x0").toReal -> PublicChannel( ann = makeChannel(ShortChannelId("565779x2711x0").toLong, PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -1948,7 +1949,7 @@ object RouteCalculationSpec { def makeChannel(shortChannelId: Long, nodeIdA: PublicKey, nodeIdB: PublicKey): ChannelAnnouncement = { val (nodeId1, nodeId2) = if (Announcements.isNode1(nodeIdA, nodeIdB)) (nodeIdA, nodeIdB) else (nodeIdB, nodeIdA) - ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, Features.empty, Block.RegtestGenesisBlock.hash, ShortChannelId(shortChannelId), nodeId1, nodeId2, randomKey().publicKey, randomKey().publicKey) + ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, Features.empty, Block.RegtestGenesisBlock.hash, realScid(shortChannelId), nodeId1, nodeId2, randomKey().publicKey, randomKey().publicKey) } def makeEdge(shortChannelId: Long, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 4c5b9d0eb8..7eac225155 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -22,6 +22,7 @@ import akka.testkit.TestProbe import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Block, SatoshiLong, Transaction, TxOut} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel.{AvailableBalanceChanged, CommitmentsSpec, LocalChannelUpdate} import fr.acinq.eclair.crypto.TransportHandler @@ -190,7 +191,7 @@ class RouterSpec extends BaseRouterSpec { // invalid announcement + reject stashed val priv_y = randomKey() val priv_funding_y = randomKey() // a-y will have an invalid script - val chan_ay = channelAnnouncement(ShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) + val chan_ay = channelAnnouncement(realScid(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ay)) @@ -212,7 +213,7 @@ class RouterSpec extends BaseRouterSpec { { // validation failure val priv_x = randomKey() - val chan_ax = channelAnnouncement(ShortChannelId(42001), priv_a, priv_x, priv_funding_a, randomKey()) + val chan_ax = channelAnnouncement(realScid(42001), priv_a, priv_x, priv_funding_a, randomKey()) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ax)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_ax) watcher.send(router, ValidateResult(chan_ax, Left(new RuntimeException("funding tx not found")))) @@ -227,7 +228,7 @@ class RouterSpec extends BaseRouterSpec { // funding tx spent (funding tx not confirmed) val priv_z = randomKey() val priv_funding_z = randomKey() - val chan_az = channelAnnouncement(ShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) + val chan_az = channelAnnouncement(realScid(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_az)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_az) watcher.send(router, ValidateResult(chan_az, Right(Transaction(version = 0, txIn = Nil, txOut = TxOut(1000000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_z.publicKey)))) :: Nil, lockTime = 0), UtxoStatus.Spent(spendingTxConfirmed = false)))) @@ -242,7 +243,7 @@ class RouterSpec extends BaseRouterSpec { // funding tx spent (funding tx confirmed) val priv_z = randomKey() val priv_funding_z = randomKey() - val chan_az = channelAnnouncement(ShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) + val chan_az = channelAnnouncement(realScid(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_az)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_az) watcher.send(router, ValidateResult(chan_az, Right(Transaction(version = 0, txIn = Nil, txOut = TxOut(1000000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_z.publicKey)))) :: Nil, lockTime = 0), UtxoStatus.Spent(spendingTxConfirmed = true)))) @@ -388,8 +389,8 @@ class RouterSpec extends BaseRouterSpec { assert(res.routes.head.hops.map(_.nodeId).toList == a :: g :: Nil) assert(res.routes.head.hops.last.nextNodeId == h) - val channelUpdate_ag1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, scid_ag_private, CltvExpiryDelta(7), 0 msat, 10 msat, 10, htlcMaximum, enable = false) - sender.send(router, LocalChannelUpdate(sender.ref, null, scid_ag_private, g, None, channelUpdate_ag1, CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, g, announceChannel = false))) + val channelUpdate_ag1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, alias_ag_private, CltvExpiryDelta(7), 0 msat, 10 msat, 10, htlcMaximum, enable = false) + sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, g, None, channelUpdate_ag1, CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, g, announceChannel = false))) sender.send(router, RouteRequest(a, h, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsg(Failure(RouteNotFound)) } @@ -410,7 +411,7 @@ class RouterSpec extends BaseRouterSpec { sender.send(router, RouteRequest(a, b, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsgType[RouteResponse] val commitments1 = CommitmentsSpec.makeCommitments(10000000 msat, 20000000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, scid_ab, b, Some(chan_ab), update_ab, commitments1)) + sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments1)) sender.send(router, RouteRequest(a, b, 12000000 msat, Long.MaxValue.msat, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsg(Failure(BalanceTooLow)) sender.send(router, RouteRequest(a, b, 12000000 msat, Long.MaxValue.msat, allowMultiPart = true, routeParams = DEFAULT_ROUTE_PARAMS)) @@ -484,6 +485,18 @@ class RouterSpec extends BaseRouterSpec { val sender = TestProbe() { + // using the channel alias + val preComputedRoute = PredefinedChannelRoute(g, Seq(alias_ag_private)) + sender.send(router, FinalizeRoute(10000 msat, preComputedRoute)) + val response = sender.expectMsgType[RouteResponse] + assert(response.routes.length === 1) + val route = response.routes.head + assert(route.hops.map(_.params) === Seq(ChannelRelayParams.FromAnnouncement(update_ag_private))) + assert(route.hops.head.nodeId === a) + assert(route.hops.head.nextNodeId === g) + } + { + // using the real scid val preComputedRoute = PredefinedChannelRoute(g, Seq(scid_ag_private)) sender.send(router, FinalizeRoute(10000 msat, preComputedRoute)) val response = sender.expectMsgType[RouteResponse] @@ -611,7 +624,7 @@ class RouterSpec extends BaseRouterSpec { // When the local channel comes back online, it will send a LocalChannelUpdate to the router. val balances = Set[Option[MilliSatoshi]](Some(10000 msat), Some(15000 msat)) val commitments = CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, scid_ab, b, Some(chan_ab), update_ab, commitments)) + sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -634,7 +647,7 @@ class RouterSpec extends BaseRouterSpec { // Then we update the balance without changing the contents of the channel update; the graph should still be updated. val balances = Set[Option[MilliSatoshi]](Some(11000 msat), Some(14000 msat)) val commitments = CommitmentsSpec.makeCommitments(11000 msat, 14000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, scid_ab, b, Some(chan_ab), update_ab, commitments)) + sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -652,7 +665,7 @@ class RouterSpec extends BaseRouterSpec { // When HTLCs are relayed through the channel, balance changes are sent to the router. val balances = Set[Option[MilliSatoshi]](Some(12000 msat), Some(13000 msat)) val commitments = CommitmentsSpec.makeCommitments(12000 msat, 13000 msat, a, b, announceChannel = true) - sender.send(router, AvailableBalanceChanged(sender.ref, null, scid_ab, commitments)) + sender.send(router, AvailableBalanceChanged(sender.ref, null, Some(scid_ab), alias_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -670,13 +683,13 @@ class RouterSpec extends BaseRouterSpec { // Private channels should also update the graph when HTLCs are relayed through them. val balances = Set(33000000 msat, 5000000 msat) val commitments = CommitmentsSpec.makeCommitments(33000000 msat, 5000000 msat, a, g, announceChannel = false) - sender.send(router, AvailableBalanceChanged(sender.ref, channelId_ag_private, scid_ag_private, commitments)) + sender.send(router, AvailableBalanceChanged(sender.ref, channelId_ag_private, None, alias_ag_private, commitments)) sender.send(router, Router.GetRouterData) val data = sender.expectMsgType[Data] val channel_ag = data.privateChannels(channelId_ag_private) assert(Set(channel_ag.meta.balance1, channel_ag.meta.balance2) == balances) // And the graph should be updated too. - val edge_ag = data.graphWithBalances.graph.getEdge(ChannelDesc(scid_ag_private, a, g)).get + val edge_ag = data.graphWithBalances.graph.getEdge(ChannelDesc(alias_ag_private, a, g)).get assert(edge_ag.capacity == channel_ag.capacity) assert(edge_ag.balance_opt == Some(33000000 msat)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index 5abee91aba..1e33b4ee0d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -22,6 +22,7 @@ import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, Script, Transaction, TxIn, TxOut} import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult} import fr.acinq.eclair.crypto.TransportHandler @@ -35,7 +36,7 @@ import fr.acinq.eclair.wire.protocol._ import org.scalatest.ParallelTestExecution import org.scalatest.funsuite.AnyFunSuiteLike -import scala.collection.immutable.{TreeMap, SortedSet} +import scala.collection.immutable.{SortedSet, TreeMap} import scala.collection.mutable import scala.concurrent.duration._ @@ -45,11 +46,11 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle // this map will store private keys so that we can sign new announcements at will val pub2priv: mutable.Map[PublicKey, PrivateKey] = mutable.HashMap.empty - val fakeRoutingInfo: TreeMap[ShortChannelId, (PublicChannel, NodeAnnouncement, NodeAnnouncement)] = RoutingSyncSpec + val fakeRoutingInfo: TreeMap[RealShortChannelId, (PublicChannel, NodeAnnouncement, NodeAnnouncement)] = RoutingSyncSpec .shortChannelIds .take(60) - .foldLeft(TreeMap.empty[ShortChannelId, (PublicChannel, NodeAnnouncement, NodeAnnouncement)]) { - case (m, shortChannelId) => m + (shortChannelId -> makeFakeRoutingInfo(pub2priv)(shortChannelId)) + .foldLeft(TreeMap.empty[RealShortChannelId, (PublicChannel, NodeAnnouncement, NodeAnnouncement)]) { + case (m, shortChannelId) => m + (shortChannelId.toReal -> makeFakeRoutingInfo(pub2priv)(shortChannelId)) } class YesWatcher extends Actor { @@ -127,7 +128,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle SyncResult(rcrs, queries, channels, updates, nodes) } - def countUpdates(channels: Map[ShortChannelId, PublicChannel]): Int = channels.values.foldLeft(0) { + def countUpdates(channels: Map[RealShortChannelId, PublicChannel]): Int = channels.values.foldLeft(0) { case (count, pc) => count + pc.update_1_opt.map(_ => 1).getOrElse(0) + pc.update_2_opt.map(_ => 1).getOrElse(0) } @@ -310,7 +311,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle test("sync progress") { - def req = QueryShortChannelIds(Block.RegtestGenesisBlock.hash, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(42))), TlvStream.empty) + def req = QueryShortChannelIds(Block.RegtestGenesisBlock.hash, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(42))), TlvStream.empty) val nodeIdA = randomKey().publicKey val nodeIdB = randomKey().publicKey @@ -349,7 +350,7 @@ object RoutingSyncSpec { val priv_funding2 = unused pub2priv += (priv1.publicKey -> priv1) pub2priv += (priv2.publicKey -> priv2) - val channelAnn_12 = channelAnnouncement(shortChannelId, priv1, priv2, priv_funding1, priv_funding2) + val channelAnn_12 = channelAnnouncement(shortChannelId.toReal, priv1, priv2, priv_funding1, priv_funding2) val channelUpdate_12 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv1, priv2.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) val channelUpdate_21 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv2, priv1.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) val nodeAnnouncement_1 = makeNodeAnnouncement(priv1, "a", Color(0, 0, 0), List(), TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index fe3e7663f7..02a74574f2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -18,6 +18,7 @@ package fr.acinq.eclair.wire.internal.channel import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet, Satoshi, SatoshiLong, Transaction, TxIn} +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.Helpers.Funding @@ -124,7 +125,7 @@ class ChannelCodecsSpec extends AnyFunSuite { // and we encode with new codec val newbin = channelDataCodec.encode(oldnormal).require.bytes // make sure that encoding used the new codec - assert(newbin.startsWith(hex"030007")) + assert(newbin.startsWith(hex"030009")) // make sure that round-trip yields the same data val newnormal = channelDataCodec.decode(newbin.bits).require.value assert(newnormal == oldnormal) @@ -135,11 +136,11 @@ class ChannelCodecsSpec extends AnyFunSuite { // this test makes sure that we actually produce the same objects than previous versions of eclair val refs = Map( hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B134000456E4167E3C0EB8C856C79CA31C97C0AA0000000000000222000000012A05F2000000000000028F5C000000000000000102D0001E000BD48A2402E80B723C42EE3E42938866EC6686ABB7ABF64380000000C501A7F2974C5074E9E10DBB3F0D9B8C40932EC63ABC610FAD7EB6B21C6D081A459B000000000000011E80000001EEFFFE5C00000000000147AE00000000000001F403F000F18146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB20131AD64F76FAF90CD7DE26892F1BDAB82FB9E02EF6538D82FF4204B5348F02AE081A5388E9474769D69C4F60A763AE0CCDB5228A06281DE64408871A927297FDFD8818B6383985ABD4F0AC22E73791CF3A4D63C592FA2648242D34B8334B1539E823381BB1F1404C37D9C2318F5FC6B1BF7ECF5E6835B779E3BE09BADCF6DF1F51DCFBC80000000C0808000000000000EFD80000000007F00000000061A0A4880000001EDE5F3C3801203B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E808000000015FFFFFF800000000011001029DFB814F6502A68D6F83B6049E3D2948A2080084083750626532FDB437169C20023A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A95700AD0100000000008083B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E80800000001961B4C001618F8180000000001100102E648BA30998A28C02C2DFD9DDCD0E0BA064DA199C55186485AFAB296B94E704426FFE00000000000B000A67D9B9FAADB91650E0146B1F742E5C16006708890200239822011026A6925C659D006FEB42D639F1E42DD13224EE49AA34E71B612CF96DB66A8CD4011032C22F653C54CC5E41098227427650644266D80DED45B7387AE0FFC10E529C4680A418228110807CB47D9C1A14CB832FB361C398EA672C9542F34A90BAD4288FA6AC5FC9E9845C01101CF71CAE9252D389135D8C606225DCF1E0333CCDF1FAE84B74FC5D3D440C25F880A3A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A9573D7C531000000000000000000F3180000000007F00000001EDE5F3C380000000061A0A48D64CA627B243AD5915A2E5D0BAD026762028DDF3304992B83A26D6C11735FC5F01ED56D769BDE7F6A068AF1A4BCFDF950321F3A4744B01B1DDC7498677F112AE1A80000000000000000000000000000000000000658000000000000819800040D37301C10C9419287E9A3B704EB6D7F45CC145DD77DCE8A63B0A47C8AB67467D800901DCE3C8B05A891E56F2BAF1B82405ABD8640B759AEEBD939B976D42C311758F40400000000AFFFFFFC00000000008800814EFDC0A7B2815346B7C1DB024F1E94A451040042041BA83132997EDA1B8B4E10011D48840A33BCFBC0833F6825A4ABF0A78E2B11D5B2981CD958EA4C881204247273416D90840D9834A03892A6C59DCA9B990600A5C65882972A8A7AF7E0CE7975C031846AE78D4AB8002000EC0003FFFFFFFF86801076D98A575A4CDFD0E3F44D1BB3CD3BBAF3BD04C38FED439ED90D88DF932A9296801A80007FFFFFFFF4008136A9D5896669E8724C5120FB6B36C241EF3CEF68AE0316161F04A9EE3EAFF36000FC0003FFFFFFFF86780106E4B5CC4155733A2427082907338051A5DA1E7CA6432840A5528ECAFFA3FB628801B80007FFFFFFFF10020CA4E125E9126107745D4354D4187ABCDE323117857A1DCEB7CCF60B2AAFA80C6003A0000FFFFFFFFE1C0080981575FD981A73A848CC0243CB467BF451F6811DAF4D71CAD8CE8B1E96DB190C01000003FFFFFFFF867400814C747E0FD8290BE8A3B8B3F73015A261479A71780CD3A0A9270234E4B394409C00D80003FFFFFFFF90020E1B9C9B10A97F15F5E1BB27FC8AC670DF8DADEAE4EDFAFB23BDD0AC705FDF51600340000FFFFFFFFF0020AD2581F3494A17B0BE3F63516D53F028A204FD3156D8B21AA4E57A8738D2062080007FFFFFFFF0CE83B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E0B8C1E00000B8000FA46CC2C7E9AB4A37C64216CD65C944E6D73998419D1A1AD2827AB6BC85B32280230764E374064EC82A3751E789607E23BEAE93FB0EDDD5E7FA803767079662E80EAEF384E2AFCB68049D9DC246119E77BD2ED4112330760CAB6CD3671CFCE006C584B9C95E0B554261E00154D40806EA694F44751B328A9291BAD124EFD5664280936EC92D27B242737E7E3E83B4704BA367B7DA5108F2F6EDFB1C38EE721A369E77EED71B12090BAEAAAC322C1457E31AB0C4DE5D9351943F10FD747742616A1AABD09F680B37D4105A8872695EE9B97FAB8985FAA9D747D45046229BF265CEEB300A40FE23040C5F335E0515496C58EE47418B72331FCC6F47A31A9B33B8E000008692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002069FCA5D3141D3A78436ECFC366E31024CBB18EAF1843EB5FADAC871B42069166C0726710955E3AD621072FCBDFCB90D79E5B1951A5EE01DB533B72429F84E2562680519DE7DE0419FB412D255F853C71588EAD94C0E6CAC7526440902123939A0B6C806CC1A501C495362CEE54DCC830052E32C414B95453D7BF0673CBAE018C23573C69C694A8F88483050257A7366B838489731E5776B6FA0F02573401176D3E7FAEEF11E95A671420586631255F51A0EC2CF4D4D9F69D587712070FE1FB9316B71868692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002BA11BBBA0202012000000000000007D0000007D0000000C800000007CFFFF83000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortChannelId":"1513532x23x1","buried":true,"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"1513532x23x1","buried":true,"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"1513532x23x1"}""", hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B1340004D443ECE9D9C43A11A19B554BAAA6AD150000000000000222000000003B9ACA0000000000000249F000000000000000010090001E800BD48A22F4C80A42CC8BB29A764DBAEFC95674931FBE9A4380000000C50134D4A745996002F219B5FDBA1E045374DF589ECA06ABE23CECAE47343E65EDCF800000000000011E80000001BA90824000000000000124F800000000000001F4038500F1810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E2266201E8BFEEEEED725775B8116F6F82CF8E87835A5B45B184E56F272AD70D6078118601E06212B8C8F2E25B73EE7974FDCDF007E389B437BBFE238CCC3F3BF7121B6C5E81AA8589D21E9584B24A11F3ABBA5DAD48D121DD63C57A69CD767119C05DA159CB81A649D8CC0E136EB8DFBD2268B69DCA86F8CE4A604235A03D9D37AE7B07FC563F80000000C080800000000000271C000000000177000000002808B14600000001970039BA00123767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB08800000000015E070F20000000000110010584241B5FB364208F6E64A80D1166DAD866186B10C015ED0283FF1C308C2105A0023A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA95700AD81000000000080B767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB0880000000003E7AEDC0011ABE8A00000000001100101A9CE4B6AEF469590BC7BCC51DCEEAE9C86084055A63CC01E443C733FBE400B9B5B16800000000000B000A5E5700106D1A7097E4DE87EBAF1F8F2773842FA482002418228110805E84989A81F51ABD9D11889AE43E68FAD93659DEC019F1B8C0ADBF15A57B118B81101DCC1256F9306439AD3962C043FC47A5179CAAA001CCB23342BE0E8D92E4022780A4182281108074F306DA3751B84EC5FFB155BDCA7B8E02208BBDBC8D4F3327ABA557BF27CD1701102EF4AC8CC92F469DA9642D4D4162BC545F8B34ADE15B7D6F99808AA22B086B0180A3A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA9576F8099900000000000000000271C00000000017700000001970039BA000000002808B14648CE00AE97051EE10A3C361263F81A98165CE4AA7BA076933D4266E533585F24815C15DEACF0691332B38ECF23EC39982C5C978C748374A01BA9B30D501EE4F26E8000000000000000000000000000000000001224000000000000004B800040A911C460F1467952E3B99BED072F81BFB4454FF389636DCB399FE6A78113C28580091BB3F87A7806AF4FEF920BBF794391A1ECFC7D7632E98245D2BAF3870050558440000000000AF0387900000000000880082C2120DAFD9B21047B732540688B36D6C330C3588600AF68141FF8E18461082D0011D488408570D7C50EB7AB7C042AF13382F8C8DD83E6A7121A5E2DD8B4C73F2C407113310840EF456FD0886E454A6C5CF4F7B0B5D742CC143E47C157EF87E03434BEAB81337ED4AB8001C00F40003FFFFFFFEC7200403248A1D44DFA3AC9EC237D452C936400CAA86E9517CCCF2A8F77B7493CD70B6A00780001FFFFFFFF63A0041826829646B907A97FBD1455EA8673A12B8E7AA6EA790F7802E955CE3B69DE57E006E0001FFFFFFFF640081E51EB1F91218821E680B50E4B22DF8B094385BD33ACAE36BFC9E8C2F5AD2DA5400EC0003FFFFFFFEC7801047C26AD5435658D063EBCF73A5D0EEFE73ED6B73426246E8DFB3A21D1C4C7465001900007FFFFFFFE0040B115AC58BAAA900195893EA3B2AB408D2AD348AD047E3B6CB15E599625E38608006A0001FFFFFFFF7002033C39A21A38BB61F6FB33623771A9356D8885B7C12C939C770C939EF826286C200360000FFFFFFFFB4008104EF4271064A0973B053727C3E67352D00E25CAEED944F50782449CEAE8F50960001FFFFFFFF6390DD9FC3D3C0357A7F7C905DFBCA1C8D0F67E3EBB1974C122E95D79C380282AC222B21FA0007920001295AA1FB77029F7620A90EF7AE6A6CD31E4588B93264A7ADB76152D535C52E90B9E1B7C2376DABA316A6290F1A9730D4E5E44D0B1CB0EE6A795702E6A6BCDFCDA1A4BFEBFC134AB8847A5187ECE761D75D3CCB904274875680F51984800000000AC87E8001E480002E884D2A8080804800000000000001F4000001F40000003200000001BF08EB000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"1413373x969x0"}""", hex"0200020000000303933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400098c4b989bbdced820a77a7186c2320e7d176a5c8b5c16d6ac2af3889d6bc8bf8080000001000000000000022200000004a817c80000000000000249f0000000000000000102d0001eff1600148061b7fbd2d84ed1884177ea785faecb2080b10302e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b300000004080aa982027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8000000000000023d000000037521048000000000000249f00000000000000001070a01e302eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b7503c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a5700000004808a52a1010000000000000004000000001046000000037e11d6000000000000000000245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aefd013b020000000001015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61040047304402207f8c1936d0a50671c993890f887c78c6019abc2a2e8018899dcdc0e891fd2b090220046b56afa2cb7e9470073c238654ecf584bcf5c00b96b91e38335a70e2739ec901483045022100871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c0220119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b01475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aed7782c20000000000000000000040000000010460000000000000000000000037e11d600b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d802e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a000000000000000000000000000000000000000000000000000000000000ff03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52ae0001003e0000fffffffffffc0080474b8cf7bb98217dd8dc475cb7c057a3465d466728978bbb909d0a05d4ae7bbe0001fffffffffff85986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b1eedce0000010000fffffd01ae98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be54920134196992f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef09bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce0000010000027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b803933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13402eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d88710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce000001000060e6eb14010100900000000000000001000003e800000064000000037e11d6000000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortChannelId":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"2026958x1x0"}""" ) refs.foreach { case (oldbin, refjson) => @@ -328,7 +329,7 @@ object ChannelCodecsSpec { commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init) - DATA_NORMAL(commitments, ShortChannelId(42), buried = true, None, channelUpdate, None, None, None) + DATA_NORMAL(commitments, Some(realScid(42)), buried = true, None, channelUpdate, ShortChannelId.generateLocalAlias(), None, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index 18f8a86de7..a024b15da8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -160,7 +160,7 @@ class ChannelCodecs3Spec extends AnyFunSuite { assert(decoded1.asInstanceOf[DATA_NORMAL].closingFeerates == None) val newBin = channelDataCodec.encode(decoded1).require.bytes // make sure that encoding used the new codec - assert(newBin.startsWith(hex"0007")) + assert(newBin.startsWith(hex"0009")) val decoded2 = channelDataCodec.decode(newBin.bits).require.value assert(decoded1 == decoded2) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala index b405869f1e..94d8db2b21 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala @@ -17,6 +17,8 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64} +import fr.acinq.eclair.RealShortChannelId +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.router.Sync import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.ReplyChannelRangeTlv._ @@ -29,7 +31,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode a list of short channel ids") { { // encode/decode with encoding 'uncompressed' - val ids = EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))) + val ids = EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))) val encoded = encodedShortChannelIdsCodec.encode(ids).require val decoded = encodedShortChannelIdsCodec.decode(encoded).require.value assert(decoded == ids) @@ -37,7 +39,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { { // encode/decode with encoding 'zlib' - val ids = EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))) + val ids = EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))) val encoded = encodedShortChannelIdsCodec.encode(ids).require val decoded = encodedShortChannelIdsCodec.decode(encoded).require.value assert(decoded == ids) @@ -65,7 +67,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (no optional data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty) val encoded = queryShortChannelIdsCodec.encode(query_short_channel_id).require @@ -76,7 +78,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (with optional data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte)))) val encoded = queryShortChannelIdsCodec.encode(query_short_channel_id).require @@ -87,7 +89,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (with optional data including unknown data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream( QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte)) :: Nil, GenericTlv(UInt64(43), ByteVector.fromValidHex("deadbeef")) :: Nil @@ -104,7 +106,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), None, None) val encoded = replyChannelRangeCodec.encode(replyChannelRange).require @@ -117,7 +119,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec)))), None) @@ -131,7 +133,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream( List( EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 0a6b2adcf9..a5e8cb54c3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -21,6 +21,8 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, SatoshiLong, ScriptWitness, Transaction} import fr.acinq.eclair.FeatureSupport.Optional import fr.acinq.eclair.Features.DataLossProtect +import fr.acinq.eclair.RealShortChannelId +import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes} @@ -357,19 +359,19 @@ class LightningMessageCodecsSpec extends AnyFunSuite { UpdateFailMalformedHtlc(randomBytes32(), 2, randomBytes32(), 1111), CommitSig(randomBytes32(), randomBytes64(), randomBytes64() :: randomBytes64() :: randomBytes64() :: Nil), RevokeAndAck(randomBytes32(), scalar(0), point(1)), - ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey), + ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, realScid(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey), NodeAnnouncement(randomBytes64(), Features(DataLossProtect -> Optional), 1 unixsec, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil), ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(3), 4 msat, 5 msat, 6, None), - AnnouncementSignatures(randomBytes32(), ShortChannelId(42), randomBytes64(), randomBytes64()), + AnnouncementSignatures(randomBytes32(), realScid(42), randomBytes64(), randomBytes64()), GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000 unixsec, 1500), - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty), + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), QueryChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(100000), 1500, TlvStream(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL) :: Nil, unknownTlv :: Nil)), ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(100000), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream( EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))) :: EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) :: Nil, unknownTlv :: Nil) @@ -402,13 +404,13 @@ class LightningMessageCodecsSpec extends AnyFunSuite { test("non-reg encoding type") { val refs = Map( hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001601789c636000833e08659309a65c971d0100126e02e3" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4010400010204" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1, 2, 4)))), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1, 2, 4)))), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001601789c636000833e08659309a65c971d0100126e02e3010c01789c6364620100000e0008" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) ) refs.forall { @@ -430,28 +432,28 @@ class LightningMessageCodecsSpec extends AnyFunSuite { TlvStream(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL))) -> hex"01070f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000088b800000064010103", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(756230), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), None, None) -> + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), None, None) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000b8a06000005dc01001900000000000000008e0000000000003c69000000000045a6c4", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(1600), 110, 1, - EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(265462))), None, None) -> + EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(265462))), None, None) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000006400000006e01001601789c636000833e08659309a65878be010010a9023a", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(122334), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(12355), ShortChannelId(489686), ShortChannelId(4645313))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(12355), realScid(489686), realScid(4645313))), Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060001ddde000005dc01001900000000000000304300000000000778d6000000000046e1c1011900000282c1000e77c5000778ad00490ab00000b57800955bff031800000457000008ae00000d050000115c000015b300001a0a", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(122334), 1500, 1, - EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(12355), ShortChannelId(489686), ShortChannelId(4645313))), + EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(12355), realScid(489686), realScid(4645313))), Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060001ddde000005dc01001801789c63600001036730c55e710d4cbb3d3c080017c303b1012201789c63606a3ac8c0577e9481bd622d8327d7060686ad150c53a3ff0300554707db031800000457000008ae00000d050000115c000015b300001a0a", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(142), ShortChannelId(15465), ShortChannelId(4564676))), TlvStream.empty) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(4564), ShortChannelId(178622), ShortChannelId(4564676))), TlvStream.empty) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(4564), realScid(178622), realScid(4564676))), TlvStream.empty) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001801789c63600001c12b608a69e73e30edbaec0800203b040e", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(ShortChannelId(12232), ShortChannelId(15556), ShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(12232), realScid(15556), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060019000000000000002fc80000000000003cc4000000000045a6c4010c01789c6364620100000e0008", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(ShortChannelId(14200), ShortChannelId(46645), ShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(14200), realScid(46645), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001801789c63600001f30a30c5b0cd144cb92e3b020017c6034a010c01789c6364620100000e0008" ) @@ -566,4 +568,5 @@ class LightningMessageCodecsSpec extends AnyFunSuite { } } + } diff --git a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala index aed2186f54..045603b6e7 100644 --- a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala +++ b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala @@ -23,6 +23,7 @@ import akka.event.LoggingAdapter import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.Logs.LogCategory +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer.PeerRoutingMessage import fr.acinq.eclair.router.Router._ @@ -110,7 +111,7 @@ class FrontRouter(routerConf: RouterConf, remoteRouter: ActorRef, initialized: O origin.peerConnection ! TransportHandler.ReadAck(ann) Metrics.gossipDropped(ann).increment() d - case u: ChannelUpdate if d.channels.contains(u.shortChannelId) && d.channels(u.shortChannelId).getChannelUpdateSameSideAs(u).contains(u) => + case u: ChannelUpdate if d.channels.get(u.shortChannelId.toReal).exists(_.getChannelUpdateSameSideAs(u).contains(u)) => origin.peerConnection ! TransportHandler.ReadAck(ann) Metrics.gossipDropped(ann).increment() d @@ -213,7 +214,7 @@ object FrontRouter { // @formatter:on case class Data(nodes: Map[PublicKey, NodeAnnouncement], - channels: SortedMap[ShortChannelId, PublicChannel], + channels: SortedMap[RealShortChannelId, PublicChannel], processing: Map[AnnouncementMessage, Set[RemoteGossip]], accepted: Map[AnnouncementMessage, Set[RemoteGossip]], rebroadcast: Rebroadcast) @@ -263,7 +264,7 @@ object FrontRouter { case ChannelsDiscovered(channels) => log.debug("adding {} channels", channels.size) - val channels1 = channels.foldLeft(SortedMap.empty[ShortChannelId, PublicChannel]) { + val channels1 = channels.foldLeft(SortedMap.empty[RealShortChannelId, PublicChannel]) { case (channels, sc) => channels + (sc.ann.shortChannelId -> PublicChannel(sc.ann, ByteVector32.Zeroes, sc.capacity, sc.u1_opt, sc.u2_opt, None)) } val d1 = d.copy(channels = d.channels ++ channels1) @@ -280,7 +281,7 @@ object FrontRouter { case ChannelUpdatesReceived(updates) => log.debug("adding/updating {} channel_updates", updates.size) val channels1 = updates.foldLeft(d.channels) { - case (channels, u) => channels.get(u.shortChannelId) match { + case (channels, u) => channels.get(u.shortChannelId.toReal) match { case Some(c) => channels + (c.ann.shortChannelId -> c.updateChannelUpdateSameSideAs(u)) case None => channels } @@ -309,7 +310,7 @@ object FrontRouter { case n: NodeAnnouncement => d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins)) case c: ChannelAnnouncement => d.rebroadcast.copy(channels = d.rebroadcast.channels + (c -> origins)) case u: ChannelUpdate => - if (d.channels.contains(u.shortChannelId)) { + if (d.channels.contains(u.shortChannelId.toReal)) { d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins)) } else { d.rebroadcast // private channel, we don't rebroadcast the channel_update diff --git a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala index a72d82c11b..ebbc249ca8 100644 --- a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala +++ b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala @@ -345,7 +345,7 @@ object FrontRouterSpec { val channelId_cd = ShortChannelId(BlockHeight(420000), 3, 0) val channelId_ef = ShortChannelId(BlockHeight(420000), 4, 0) - def channelAnnouncement(shortChannelId: ShortChannelId, node1_priv: PrivateKey, node2_priv: PrivateKey, funding1_priv: PrivateKey, funding2_priv: PrivateKey) = { + def channelAnnouncement(shortChannelId: RealShortChannelId, node1_priv: PrivateKey, node2_priv: PrivateKey, funding1_priv: PrivateKey, funding2_priv: PrivateKey) = { val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, shortChannelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, Features.empty) val node1_sig = Announcements.signChannelAnnouncement(witness, node1_priv) val funding1_sig = Announcements.signChannelAnnouncement(witness, funding1_priv) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index f99a06e2f8..dcd59d84f8 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -39,7 +39,7 @@ trait Channel { case Some(str) if str == ChannelTypes.Standard.toString => (true, Some(ChannelTypes.Standard)) case Some(str) if str == ChannelTypes.StaticRemoteKey.toString => (true, Some(ChannelTypes.StaticRemoteKey)) case Some(str) if str == ChannelTypes.AnchorOutputs.toString => (true, Some(ChannelTypes.AnchorOutputs)) - case Some(str) if str == ChannelTypes.AnchorOutputsZeroFeeHtlcTx.toString => (true, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = announceChannel_opt.contains(true), zeroConf = zeroConf))) // alias feature is incompatible with public channel + case Some(str) if str == "anchor_outputs_zero_fee_htlc_tx" => (true, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = announceChannel_opt.contains(true), zeroConf = zeroConf))) // alias feature is incompatible with public channel case Some(_) => (false, None) case None => (true, None) } From 2be569944c47845fd640a1e06654331dbca3dad5 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 17 May 2022 16:07:09 +0200 Subject: [PATCH 07/55] fix race condition There is a potential race condition in the router between the incoming `channel_update` from our peer and the `LocalChannelUpdate` event. If remote's `channel_update` wins the race, the router will not be able to find a related channel and will ignore it. --- .../acinq/eclair/channel/ChannelEvents.scala | 2 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 4 +- .../channel/fsm/ChannelOpenSingleFunder.scala | 11 ++- .../scala/fr/acinq/eclair/router/Router.scala | 12 ++-- .../fr/acinq/eclair/router/Validation.scala | 67 +++++++++++-------- .../acinq/eclair/router/BaseRouterSpec.scala | 3 +- 6 files changed, 60 insertions(+), 39 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index a4a46ee9fe..c1bd4531f8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -54,7 +54,7 @@ case class ChannelIdAssigned(channel: ActorRef, remoteNodeId: PublicKey, tempora * @param remoteAlias_opt we only remember the last alias received from our peer, we use this to generate * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] */ -case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteAlias_opt: Option[ShortChannelId]) extends ChannelEvent +case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteAlias_opt: Option[ShortChannelId], remoteNodeId: PublicKey) extends ChannelEvent case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent { /** 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 b9a7231a1e..3de9adaeb0 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 @@ -299,7 +299,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case normal: DATA_NORMAL => watchFundingTx(data.commitments) - context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, realShortChannelId_opt = normal.realShortChannelId_opt, localAlias = normal.localAlias, remoteAlias_opt = normal.remoteAlias_opt)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, realShortChannelId_opt = normal.realShortChannelId_opt, localAlias = normal.localAlias, remoteAlias_opt = normal.remoteAlias_opt, remoteNodeId = remoteNodeId)) // we check the configuration because the values for channel_update may have changed while eclair was down val fees = getRelayFees(nodeParams, remoteNodeId, data.commitments) @@ -619,7 +619,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val if (!d.realShortChannelId_opt.contains(realShortChannelId)) { log.info(s"setting final real scid: old=${d.realShortChannelId_opt.getOrElse("empty")} new=$realShortChannelId") // we announce the new shortChannelId - context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, realShortChannelId_opt = Some(realShortChannelId), localAlias = d.localAlias, remoteAlias_opt = d.remoteAlias_opt)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, realShortChannelId_opt = Some(realShortChannelId), localAlias = d.localAlias, remoteAlias_opt = d.remoteAlias_opt, remoteNodeId = remoteNodeId)) } val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.commitments.channelFlags, Some(realShortChannelId), d.remoteAlias_opt) // if the shortChannelId is different from the one we had before, we need to re-announce it diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index c58d3e7b3e..27c1a024e5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -378,6 +378,9 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { tlvStream = TlvStream(ChannelReadyTlv.ShortChannelIdTlv(localAlias)) ) + // we announce our identifiers as early as we can + context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = None, remoteNodeId = remoteNodeId)) + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, channelReady) storing() sending channelReady case Failure(t) => log.error(t, s"rejecting channel with invalid funding tx: ${fundingTx.bin}") @@ -417,10 +420,12 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { case Event(channelReady: ChannelReady, d@DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt, localAlias, _)) => // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) - context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = channelReady.alias_opt)) - // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val remoteAlias_opt = channelReady.alias_opt - remoteAlias_opt.foreach(remoteAlias => log.info("received remoteAlias={}", remoteAlias)) + remoteAlias_opt.foreach { remoteAlias => + log.info("received remoteAlias={}", remoteAlias) + context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = Some(remoteAlias), remoteNodeId = remoteNodeId)) + } + // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val scidForChannelUpdate = Helpers.scidForChannelUpdate(commitments.channelFlags, realShortChannelId_opt, remoteAlias_opt) log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) val relayFees = getRelayFees(nodeParams, remoteNodeId, commitments) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 820f7504c8..72e65345b0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -60,6 +60,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm // we pass these to helpers classes so that they have the logging context implicit def implicitLog: DiagnosticLoggingAdapter = diagLog + context.system.eventStream.subscribe(self, classOf[ShortChannelIdAssigned]) context.system.eventStream.subscribe(self, classOf[LocalChannelUpdate]) context.system.eventStream.subscribe(self, classOf[LocalChannelDown]) context.system.eventStream.subscribe(self, classOf[AvailableBalanceChanged]) @@ -229,14 +230,17 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm case Event(PeerRoutingMessage(peerConnection, remoteNodeId, n: NodeAnnouncement), d: Data) => stay() using Validation.handleNodeAnnouncement(d, nodeParams.db.network, Set(RemoteGossip(peerConnection, remoteNodeId)), n) - case Event(u: ChannelUpdate, d: Data) => + case Event(scia: ShortChannelIdAssigned, d) => + stay() using Validation.handleShortChannelIdAssigned(d, nodeParams.nodeId, scia) + + case Event(u: ChannelUpdate, d: Data) => // from payment lifecycle stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, Set(LocalGossip)))) - case Event(PeerRoutingMessage(peerConnection, remoteNodeId, u: ChannelUpdate), d) => + case Event(PeerRoutingMessage(peerConnection, remoteNodeId, u: ChannelUpdate), d) => // from network (gossip or peer) stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, Set(RemoteGossip(peerConnection, remoteNodeId))))) - case Event(lcu: LocalChannelUpdate, d: Data) => - stay() using Validation.handleLocalChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, nodeParams.nodeId, watcher, lcu) + case Event(lcu: LocalChannelUpdate, d: Data) => // from local channel + stay() using Validation.handleLocalChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, watcher, lcu) case Event(lcd: LocalChannelDown, d: Data) => stay() using Validation.handleLocalChannelDown(d, nodeParams.nodeId, lcd) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index a9296ef513..01e1117e07 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -21,10 +21,9 @@ import akka.actor.{ActorContext, ActorRef, typed} import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} -import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} -import fr.acinq.eclair.channel.{AvailableBalanceChanged, LocalChannelDown, LocalChannelUpdate} +import fr.acinq.eclair.channel.{AvailableBalanceChanged, LocalChannelDown, LocalChannelUpdate, ShortChannelIdAssigned} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.db.NetworkDb import fr.acinq.eclair.router.Graph.GraphStructure.GraphEdge @@ -32,7 +31,7 @@ import fr.acinq.eclair.router.Monitoring.Metrics import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, ShortChannelId, TxCoordinates, toLongId} +import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates, toLongId} object Validation { @@ -390,14 +389,14 @@ object Validation { // but we ignored it because the channel was in the 'pruned' list. Now that we know that the channel is alive again, // let's remove the channel from the zombie list and ask the sender to re-send announcements (channel_announcement + updates) // about that channel. We can ignore this update since we will receive it again - log.info(s"channel shortChannelId=${realShortChannelId} is back from the dead! requesting announcements about this channel") + log.info(s"channel shortChannelId=${realShortChannelId} is back from the dead! requesting announcements about this channel") sendDecision(origins, GossipDecision.RelatedChannelPruned(u)) - db.removeFromPruned(realShortChannelId) + db.removeFromPruned(realShortChannelId) // peerConnection_opt will contain a valid peerConnection only when we're handling an update that we received from a peer, not // when we're sending updates to ourselves origins head match { case RemoteGossip(peerConnection, remoteNodeId) => - val query = QueryShortChannelIds(u.chainHash, EncodedShortChannelIds(routerConf.encodingType, List(realShortChannelId)), TlvStream.empty) + val query = QueryShortChannelIds(u.chainHash, EncodedShortChannelIds(routerConf.encodingType, List(realShortChannelId)), TlvStream.empty) d.sync.get(remoteNodeId) match { case Some(sync) if sync.started => // we already have a pending request to that node, let's add this channel to the list and we'll get it later @@ -421,41 +420,53 @@ object Validation { } } - def handleLocalChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, localNodeId: PublicKey, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { + /** + * We will receive this event before [[LocalChannelUpdate]] or [[ChannelUpdate]] + */ + def handleShortChannelIdAssigned(d: Data, localNodeId: PublicKey, scia: ShortChannelIdAssigned)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - lcu.realShortChannelId_opt.flatMap(d.channels.get) match { + // NB: we don't map remote aliases because they are decided our peer and could overlap with ours + val mappings = scia.realShortChannelId_opt match { + case Some(realScid) => Map(realScid -> scia.channelId, scia.localAlias -> scia.channelId) + case None => Map(scia.localAlias -> scia.channelId) + } + val d1 = d.copy(scid2PrivateChannels = d.scid2PrivateChannels ++ mappings) + d1.resolve(scia.channelId, scia.realShortChannelId_opt) match { case Some(_) => - // channel has already been announced and router knows about it, we can process the channel_update - handleChannelUpdate(d, db, routerConf, Left(lcu)) + // channel is known, nothing more to do + d1 case None => + // this is a local channel that hasn't yet been announced (maybe it is a private channel or maybe it is a public + // channel that doesn't yet have 6 confirmations), we create a corresponding private channel + val pc = PrivateChannel(scia.localAlias, scia.channelId, localNodeId, scia.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)) + log.debug("adding unannounced local channel to remote={} channelId={} localAlias={}", scia.remoteNodeId, scia.channelId, scia.localAlias) + d1.copy(privateChannels = d1.privateChannels + (scia.channelId -> pc)) + } + } + + def handleLocalChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { + implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors + d.resolve(lcu.channelId, lcu.realShortChannelId_opt) match { + case Some(_: PublicChannel) => + // channel is already known, we can process the channel_update + handleChannelUpdate(d, db, routerConf, Left(lcu)) + case _ => + // known private channel, or unknown channel lcu.channelAnnouncement_opt match { case Some(c) if d.awaiting.contains(c) => - // channel is currently being verified, we can process the channel_update right away (it will be stashed) + // this is a public channel currently being verified, we can process the channel_update right away (it will be stashed) handleChannelUpdate(d, db, routerConf, Left(lcu)) case Some(c) => // channel wasn't announced but here is the announcement, we will process it *before* the channel_update watcher ! ValidateRequest(ctx.self, c) - val d1 = d.copy(awaiting = d.awaiting + (c -> Seq(LocalGossip))) // no origin + val d1 = d.copy(awaiting = d.awaiting + (c -> Seq(LocalGossip))) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(c.shortChannelId) handleChannelUpdate(d1, db, routerConf, Left(lcu)) - case None if d.privateChannels.contains(lcu.channelId) => - // channel isn't announced but we already know about it, we can process the channel_update - handleChannelUpdate(d, db, routerConf, Left(lcu)) case None => - // channel isn't announced and we never heard of it (maybe it is a private channel or maybe it is a public channel that doesn't yet have 6 confirmations) - // let's create a corresponding private channel and process the channel_update - log.warning("unknown local channel update to remote={} channelId={}, localAlias={}", lcu.remoteNodeId, lcu.channelId, lcu.localAlias) - val pc = PrivateChannel(lcu.localAlias, lcu.channelId, localNodeId, lcu.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)).updateBalances(lcu.commitments) - val mappings = lcu.realShortChannelId_opt match { - case Some(realScid) => Map(realScid -> lcu.channelId, lcu.localAlias -> lcu.channelId) - case None => Map(lcu.localAlias -> lcu.channelId) - } - val d1 = d.copy( - privateChannels = d.privateChannels + (lcu.channelId -> pc), - scid2PrivateChannels = d.scid2PrivateChannels + mappings - ) - handleChannelUpdate(d1, db, routerConf, Left(lcu)) + // should never happen, we log a warning and handle the update, it will be rejected since there is no related channel + log.warning("unrecognized local chanel update for channelId={} localAlias={}", lcu.channelId, lcu.localAlias) + handleChannelUpdate(d, db, routerConf, Left(lcu)) } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index afeaf971e4..e2d9a1d8e1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, SatoshiLong, Transaction, TxOut} import fr.acinq.eclair.TestConstants.Alice import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} -import fr.acinq.eclair.channel.{CommitmentsSpec, LocalChannelUpdate} +import fr.acinq.eclair.channel.{CommitmentsSpec, LocalChannelUpdate, ShortChannelIdAssigned} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager} import fr.acinq.eclair.io.Peer.PeerRoutingMessage @@ -154,6 +154,7 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_gh)) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_hg)) // then private channels + sender.send(router, ShortChannelIdAssigned(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, remoteAlias_opt = None, remoteNodeId = g)) sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, g, None, update_ag_private, CommitmentsSpec.makeCommitments(30000000 msat, 8000000 msat, a, g, announceChannel = false))) // watcher receives the get tx requests assert(watcher.expectMsgType[ValidateRequest].ann == chan_ab) From dd23c144e91c1e04b8bd53ed82c5a26cebb0ca85 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 17 May 2022 17:17:10 +0200 Subject: [PATCH 08/55] bypass blockchain validation for local channels --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 2 +- .../scala/fr/acinq/eclair/router/Router.scala | 2 +- .../fr/acinq/eclair/router/Validation.scala | 107 ++++++++++-------- .../router/ChannelRouterIntegrationSpec.scala | 6 +- 4 files changed, 65 insertions(+), 52 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 3de9adaeb0..6e965ba699 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 @@ -1629,7 +1629,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } emitEvent_opt.foreach { case EmitLocalChannelUpdate(reason, d, sendToPeer) => - log.info(s"emitting channel update: reason={} enabled={} sendToPeer={} realScid=${d.realShortChannelId_opt} channel_update={}", reason, d.channelUpdate.channelFlags.isEnabled, sendToPeer, d.channelUpdate) + log.info(s"emitting channel update: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.realShortChannelId_opt} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) context.system.eventStream.publish(LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments)) if (sendToPeer) { send(d.channelUpdate) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 72e65345b0..45675ad257 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -240,7 +240,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, Set(RemoteGossip(peerConnection, remoteNodeId))))) case Event(lcu: LocalChannelUpdate, d: Data) => // from local channel - stay() using Validation.handleLocalChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, watcher, lcu) + stay() using Validation.handleLocalChannelUpdate(d, nodeParams, lcu) case Event(lcd: LocalChannelDown, d: Data) => stay() using Validation.handleLocalChannelDown(d, nodeParams.nodeId, lcd) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 01e1117e07..eedf1c7530 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} -import fr.acinq.eclair.channel.{AvailableBalanceChanged, LocalChannelDown, LocalChannelUpdate, ShortChannelIdAssigned} +import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.db.NetworkDb import fr.acinq.eclair.router.Graph.GraphStructure.GraphEdge @@ -115,21 +115,12 @@ object Validation { val capacity = tx.txOut(outputIndex).amount ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(c, capacity, None, None) :: Nil)) db.addChannel(c, tx.txid, capacity) - // in case we just validated our first local channel, we announce the local node - if (!d0.nodes.contains(nodeParams.nodeId) && isRelatedTo(c, nodeParams.nodeId)) { - log.info("first local channel validated, announcing local node") - val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) - ctx.self ! nodeAnn - } - // maybe this previously was a local unannounced channel - val channelId = toLongId(tx.txid.reverse, outputIndex) - val privateChannel_opt = d0.privateChannels.get(channelId) Some(PublicChannel(c, tx.txid, capacity, - update_1_opt = privateChannel_opt.flatMap(_.update_1_opt), - update_2_opt = privateChannel_opt.flatMap(_.update_2_opt), - meta_opt = privateChannel_opt.map(_.meta))) + update_1_opt = None, + update_2_opt = None, + meta_opt = None)) } case ValidateResult(c, Right((tx, fundingTxStatus: UtxoStatus.Spent))) => if (fundingTxStatus.spendingTxConfirmed) { @@ -153,21 +144,9 @@ object Validation { val awaiting1 = d0.awaiting - c publicChannel_opt match { - case Some(pc) => - // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid - val updates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) - .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) - .toMap - val d1 = d0.copy( - channels = d0.channels + (c.shortChannelId -> pc), - privateChannels = d0.privateChannels - pc.channelId, // we remove the corresponding unannounced channel that we may have until now - rebroadcast = d0.rebroadcast.copy( - channels = d0.rebroadcast.channels + (c -> d0.awaiting.getOrElse(c, Nil).toSet), // we rebroadcast the channel to our peers - updates = d0.rebroadcast.updates ++ updates1 - ), // we also add the newly validated channels to the rebroadcast queue - stash = stash1, - awaiting = awaiting1) - // we only reprocess updates and nodes if validation succeeded + case Some(publicChannel) => + val d1 = addPublicChannel(d0, nodeParams, publicChannel).copy(stash = stash1, awaiting = awaiting1) + // we process channel updates and node announcements if validation succeeded val d2 = reprocessUpdates.foldLeft(d1) { case (d, (u, origins)) => Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, origins)), wasStashed = true) } @@ -176,6 +155,7 @@ object Validation { } d3 case None => + // if validation failed we can fast-discard related announcements reprocessUpdates.foreach { case (u, origins) => origins.collect { case o: RemoteGossip => sendDecision(o.peerConnection, GossipDecision.NoRelatedChannel(u)) } } reprocessNodes.foreach { case (n, origins) => origins.collect { case o: RemoteGossip => sendDecision(o.peerConnection, GossipDecision.NoKnownChannel(n)) } } d0.copy(stash = stash1, awaiting = awaiting1) @@ -183,6 +163,33 @@ object Validation { } } + private def addPublicChannel(d: Data, nodeParams: NodeParams, pc: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { + implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors + log.debug("adding public channel channelId={} realScid={}", pc.channelId, pc.shortChannelId) + // in case this was our first local channel, we make a node announcement + if (!d.nodes.contains(nodeParams.nodeId)) { + log.info("first local channel validated, announcing local node") + val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) + ctx.self ! nodeAnn + } + // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid + val updates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) + .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) + .toMap + d.copy( + channels = d.channels + (pc.shortChannelId -> pc), + // we remove the corresponding unannounced channel that we may have until now + privateChannels = d.privateChannels - pc.channelId, + // we also add the newly validated channels to the rebroadcast queue + rebroadcast = d.rebroadcast.copy( + // we rebroadcast the channel to our peers + channels = d.rebroadcast.channels + (pc.ann -> d.awaiting.getOrElse(pc.ann, if (pc.nodeId1 == nodeParams.nodeId || pc.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet), + // those updates are only defined if the channel is was previously an unannounced local channel, we broadcast them + updates = d.rebroadcast.updates ++ updates1 + ) + ) + } + def handleChannelSpent(d: Data, db: NetworkDb, shortChannelId: RealShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors val lostChannel = d.channels(shortChannelId).ann @@ -444,30 +451,40 @@ object Validation { } } - def handleLocalChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { + def handleLocalChannelUpdate(d: Data, nodeParams: NodeParams, lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors + import nodeParams.db.{network => db} d.resolve(lcu.channelId, lcu.realShortChannelId_opt) match { case Some(_: PublicChannel) => - // channel is already known, we can process the channel_update - handleChannelUpdate(d, db, routerConf, Left(lcu)) - case _ => - // known private channel, or unknown channel + // this a known public channel, we can process the channel_update + handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) + case Some(privateChannel: PrivateChannel) => lcu.channelAnnouncement_opt match { - case Some(c) if d.awaiting.contains(c) => - // this is a public channel currently being verified, we can process the channel_update right away (it will be stashed) - handleChannelUpdate(d, db, routerConf, Left(lcu)) - case Some(c) => - // channel wasn't announced but here is the announcement, we will process it *before* the channel_update - watcher ! ValidateRequest(ctx.self, c) - val d1 = d.copy(awaiting = d.awaiting + (c -> Seq(LocalGossip))) + case Some(ann) => + // channel is graduating from private to public + // since this is a local channel, we can trust the announcement, no need to go through the full + // verification process and make calls to bitcoin core + val commitments = lcu.commitments.asInstanceOf[Commitments] // TODO: ugly! a public channel has to have a real commitment + val publicChannel = PublicChannel( + ann = ann, + fundingTxid = commitments.commitInput.outPoint.txid, + capacity = commitments.capacity, + update_1_opt = privateChannel.update_1_opt, + update_2_opt = privateChannel.update_2_opt, + meta_opt = Some(privateChannel.meta) + ) + val d1 = addPublicChannel(d, nodeParams, publicChannel) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) - db.removeFromPruned(c.shortChannelId) - handleChannelUpdate(d1, db, routerConf, Left(lcu)) + db.removeFromPruned(ann.shortChannelId) + handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) case None => - // should never happen, we log a warning and handle the update, it will be rejected since there is no related channel - log.warning("unrecognized local chanel update for channelId={} localAlias={}", lcu.channelId, lcu.localAlias) - handleChannelUpdate(d, db, routerConf, Left(lcu)) + // this is a known unannounced channel, we can process the channel_update + handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) } + case None => + // should never happen, we log a warning and handle the update, it will be rejected since there is no related channel + log.warning("unrecognized local chanel update for channelId={} localAlias={}", lcu.channelId, lcu.localAlias) + handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index daa0d8ed65..b09b536d4a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -98,10 +98,6 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu channels.bob2alice.expectMsgType[AnnouncementSignatures] channels.bob2alice.forward(channels.alice) - // router gets notified and attempts to validate the local channel - val vr = channels.alice2blockchain.expectMsgType[ZmqWatcher.ValidateRequest] - vr.replyTo ! ZmqWatcher.ValidateResult(vr.ann, Right((fundingTx, ZmqWatcher.UtxoStatus.Unspent))) - awaitAssert { router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 } @@ -115,7 +111,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // manual rebroadcast router ! Router.TickBroadcast rebroadcastListener.expectMsg(Router.Rebroadcast( - channels = Map(vr.ann -> Set[GossipOrigin](LocalGossip)), + channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), updates = Map(aliceChannelUpdate -> Set[GossipOrigin](LocalGossip), bobChannelUpdate -> Set.empty[GossipOrigin]), // broadcast the channel_updates (they were previously unannounced) nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip)), // new node_announcement )) From 93bbb8cd34d6d8749a0659cb17d6cc5eb878f59d Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 1 Jun 2022 15:41:25 +0200 Subject: [PATCH 09/55] address review comments --- eclair-core/src/main/resources/reference.conf | 2 ++ .../fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala | 2 +- .../fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala | 3 ++- .../main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala | 4 ++-- .../src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index 8f5d406c2d..66ec236654 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -59,6 +59,8 @@ eclair { option_channel_type = optional option_scid_alias = optional option_payment_metadata = optional + // By enabling option_zeroconf, you will be trusting your peers as fundee. You will lose funds if they double spend + // their funding tx. option_zeroconf = disabled keysend = disabled trampoline_payment_prototype = disabled diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index f0b8d4f885..37c31a368a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -413,7 +413,7 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client case Some(confirmations) if confirmations >= w.minDepth => client.getTransaction(w.txId).flatMap { tx => w match { - case w: WatchFundingConfirmed if confirmations == 0 && w.minDepth == 0 => + case w: WatchFundingConfirmed if confirmations == 0 => // if the tx doesn't have confirmations but we don't require any, we reply with a fake block index // otherwise, we get the real short id context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(BlockHeight(0), 0, tx)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 27c1a024e5..dad280c14f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -81,6 +81,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { val fundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey val channelKeyPath = keyManager.keyPath(localParams, channelConfig) val minimumDepth = Funding.minDepthFundee(nodeParams.channelConf, channelFeatures, open.fundingSatoshis) + log.info("will use fundingMinDepth={}", minimumDepth) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. val localShutdownScript = if (Features.canUseFeature(localParams.initFeatures, remoteInit.features, Features.UpfrontShutdownScript)) localParams.defaultFinalScriptPubKey else ByteVector.empty @@ -151,7 +152,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { initFeatures = remoteInit.features, shutdownScript = remoteShutdownScript) log.debug("remote params: {}", remoteParams) - log.info("remote indicates they are going to use fundingMinDepth={}", accept.minimumDepth) + log.info("remote will use fundingMinDepth={}", accept.minimumDepth) val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath) val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey))) wallet.makeFundingTx(fundingPubkeyScript, fundingSatoshis, fundingTxFeerate).pipeTo(self) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala index 9ff575183d..d2476819b1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala @@ -114,10 +114,10 @@ object ChannelReadyTlv { case class ShortChannelIdTlv(alias: ShortChannelId) extends ChannelReadyTlv - val channelTypeCodec: Codec[ShortChannelIdTlv] = variableSizeBytesLong(varintoverflow, "alias" | shortchannelid).as[ShortChannelIdTlv] + val channelAliasTlvCodec: Codec[ShortChannelIdTlv] = variableSizeBytesLong(varintoverflow, "alias" | shortchannelid).as[ShortChannelIdTlv] val channelReadyTlvCodec: Codec[TlvStream[ChannelReadyTlv]] = tlvStream(discriminated[ChannelReadyTlv].by(varint) - .typecase(UInt64(1), channelTypeCodec) + .typecase(UInt64(1), channelAliasTlvCodec) ) } diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 20baf3637e..9d48e22f70 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -372,7 +372,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM Route.seal(mockService.route) ~> check { assert(handled) - //assert(status == OK) + assert(status == OK) assert(entityAs[String] == "\"created channel 56d7d6eda04d80138270c49709f1eadb5ab4939e5061309ccdacdb98ce637d0e\"") eclair.open(nodeId, 25000 sat, None, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)), None, None, None)(any[Timeout]).wasCalled(once) } From c965f97fc44176eb1c5f395fd4d7f86289c243b3 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 1 Jun 2022 14:55:23 +0200 Subject: [PATCH 10/55] fix codec bug and add non-reg test --- .../channel/version3/ChannelCodecs3.scala | 3 ++- .../channel/version3/ChannelCodecs3Spec.scala | 22 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index 98023ef261..6c0d4daf8b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -414,6 +414,7 @@ private[channel] object ChannelCodecs3 { // Order matters! val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) + .typecase(0x10, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x09, Codecs.DATA_NORMAL_Codec) .typecase(0x08, Codecs.DATA_SHUTDOWN_Codec) .typecase(0x07, Codecs.DATA_NORMAL_COMPAT_07_Codec) @@ -422,7 +423,7 @@ private[channel] object ChannelCodecs3 { .typecase(0x04, Codecs.DATA_NEGOTIATING_Codec) .typecase(0x03, Codecs.DATA_SHUTDOWN_COMPAT_03_Codec) .typecase(0x02, Codecs.DATA_NORMAL_COMPAT_02_Codec) - .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) + .typecase(0x01, Codecs.DATA_WAIT_FOR_CHANNEL_READY_COMPAT_01_Codec) .typecase(0x00, Codecs.DATA_WAIT_FOR_FUNDING_CONFIRMED_Codec) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index a024b15da8..d9d33d81b9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs._ import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.channelDataCodec -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, UInt64, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.{ByteVector, HexStringSyntax} @@ -250,4 +250,24 @@ class ChannelCodecs3Spec extends AnyFunSuite { } } + test("backwards compatibility with codecs pre-alias") { + { + val bin = hex"0001000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae00000000000000075bcd1541dd93fe76288e183498a7d57065cf472a4413c643730f9c117ca806be6b57f75303a153d48f51439f168ca85ef2fd6f3e0642fc7132f7a530cd50d880ec05cb4c17" + val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY] + assert(data.localAlias === ShortChannelId(123456789L)) + assert(data.realShortChannelId_opt === Some(ShortChannelId(123456789L))) + val binMigrated = channelDataCodec.encode(data).require.toHex + assert(binMigrated.startsWith("0010")) // NB: 01 -> 10 + } + + { + val bin = hex"0007000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000000000000000002aff008821c93fbf280b2391826bc70ae858cf815d4afc1816f85445364f188e635d4ae64dfe1c58bedb017dd6f267452444d991b66fcfc638396f72fa6926f69d6125ff01010101010101010101010101010101010101010101010101010101010101010000000000022cd962975eec0101002a000000000000000f0000023f0000003500000003e8000000000000" + val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] + assert(data.localAlias === ShortChannelId(42)) + assert(data.realShortChannelId_opt === Some(ShortChannelId(42))) + val binMigrated = channelDataCodec.encode(data).require.toHex + assert(binMigrated.startsWith("0009")) + } + } + } From a8940d3e9d9cee9a84a8f074c9af98298229b6b9 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 11:20:08 +0200 Subject: [PATCH 11/55] add test proving bug in scid selection for channel_update --- .../c/WaitForChannelReadyStateSpec.scala | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 9af8ce60d5..2370890a26 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, ShortChannelId, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{MilliSatoshiLong, ShortChannelId, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -46,7 +46,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val setup = init() import setup._ val channelConfig = ChannelConfig.standard - val channelFlags = ChannelFlags.Private + val channelFlags = ChannelFlags(announceChannel = test.tags.contains(ChannelStateTestsTags.ChannelsPublic)) val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val pushMsat = if (test.tags.contains(ChannelStateTestsTags.NoPushMsat)) 0.msat else TestConstants.pushMsat val aliceInit = Init(aliceParams.initFeatures) @@ -147,6 +147,22 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } + test("recv ChannelReady (public)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + import f._ + // we have a real scid at this stage, because this isn't a zero-conf channel + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.nonEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.channelFlags.announceChannel) + val channelReady = bob2alice.expectMsgType[ChannelReady] + bob2alice.forward(alice) + val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // we have a real scid, but it is not the final one (less than 6 confirmations) so alice uses bob's alias + assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + bob2alice.expectNoMessage(200 millis) + awaitCond(alice.stateName == NORMAL) + } + test("recv WatchFundingSpentTriggered (remote commit)") { f => import f._ // bob publishes his commitment tx From cac1d22bc4a6a0878b3bc0079afc0a16e3b2ce62 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 13:30:11 +0200 Subject: [PATCH 12/55] simplify scid selection for channel_update We don't discriminate between public/private channels, we only care about whether the channel has been anounced or not: - announced: use real scid - not announced: use remote alias Then we handle edge cases, such as no available remote alias. --- .../fr/acinq/eclair/channel/Helpers.scala | 49 ++++++++----------- .../fr/acinq/eclair/channel/fsm/Channel.scala | 7 ++- .../channel/fsm/ChannelOpenSingleFunder.scala | 2 +- .../c/WaitForChannelReadyStateSpec.scala | 21 ++++++-- .../channel/states/e/NormalStateSpec.scala | 17 +++---- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 7d8eec8493..8a4be1b964 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,7 +21,6 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat._ -import fr.acinq.eclair.{RealShortChannelId, _} import fr.acinq.eclair.blockchain.OnChainAddressGenerator import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratePerKw} import fr.acinq.eclair.channel.fsm.Channel @@ -36,6 +35,7 @@ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ +import fr.acinq.eclair.{RealShortChannelId, _} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -186,41 +186,34 @@ object Helpers { } /** - * The short channel id used in channel_update depends on multiple factors: - * - is the channel public or private? - * - do we have a real scid? - * - do we have a remote alias? + * The general rule is that we use remote_alias for our channel_update until the channel is publicly announced, and + * then we use the real scid. + * + * Private channels are handled like public channels that have not yet been announced, there is no special case. * - * {{{ + * Decision tree: * - received remote_alias from peer - * - public : - * - before 6 blocks : use remote_alias - * - after 6 blocks : use real scid - * - private : use remote_alias + * - before channel announcement: use remote_alias + * - after channel announcement: use real scid * - no remote_alias from peer * - min_depth > 0 : use real scid (may change if reorg between min_depth and 6 conf) * - min_depth = 0 (zero-conf) : unsupported - * }}} */ - def scidForChannelUpdate(channelFlags: ChannelFlags, realShortChannelId_opt: Option[ShortChannelId], remoteAlias_opt: Option[ShortChannelId])(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { - val scid_opt = if (channelFlags.announceChannel) { - // public channel: we prefer the real scid - realShortChannelId_opt.orElse(remoteAlias_opt) - } else { - // private channel: we prefer the remote alias - remoteAlias_opt.orElse(realShortChannelId_opt) - } - scid_opt.getOrElse { - // TODO A bit hacky! - // Our model requires a channel_update in DATA_NORMAL. But if we are in zero-conf and our peer - // does not send an alias, then we have neither a remote alias, nor a real scid. This should never happen, so - // we default to a ShortChannelId(0) which should never be used. - log.warning("no real scid and no alias!! this should never happen") - ShortChannelId(0) - } + def scidForChannelUpdate(channelAnnouncement_opt: Option[ChannelAnnouncement], realShortChannelId_opt: Option[ShortChannelId], remoteAlias_opt: Option[ShortChannelId])(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { + channelAnnouncement_opt.map(_.shortChannelId) // we use the real "final" scid when it is publicly announced + .orElse(remoteAlias_opt) // otherwise the remote alias + .orElse(realShortChannelId_opt) // if we don't have a remote alias, we use the real scid (which could change because the funding tx possibly has less than 6 confs here) + .getOrElse { + // Our model requires a channel_update in DATA_NORMAL. But if we are in zero-conf and our peer + // does not send an alias, then we have neither a remote alias, nor a real scid. This should never happen, so + // we default to a ShortChannelId(0) which should never be used. + log.warning("no real scid and no alias!! this should never happen") + ShortChannelId(0) + } + //.getOrElse(throw new RuntimeException("this is a zero-conf channel and remote hasn't provided an alias")) // if we don't have a real scid, it means this is a zero-conf channel and our peer must have sent an alias } - def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.commitments.channelFlags, d.realShortChannelId_opt, d.remoteAlias_opt) + def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, realShortChannelId_opt = d.realShortChannelId_opt, remoteAlias_opt = d.remoteAlias_opt) /** * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by 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 6e965ba699..f9c024842d 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 @@ -621,7 +621,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // we announce the new shortChannelId context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, realShortChannelId_opt = Some(realShortChannelId), localAlias = d.localAlias, remoteAlias_opt = d.remoteAlias_opt, remoteNodeId = remoteNodeId)) } - val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.commitments.channelFlags, Some(realShortChannelId), d.remoteAlias_opt) + val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.channelAnnouncement, realShortChannelId_opt = Some(realShortChannelId), remoteAlias_opt = d.remoteAlias_opt) // if the shortChannelId is different from the one we had before, we need to re-announce it val channelUpdate1 = if (d.channelUpdate.shortChannelId != scidForChannelUpdate) { log.info(s"using new scid in channel_update: old=${d.channelUpdate.shortChannelId} new=$scidForChannelUpdate") @@ -653,8 +653,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val if (!Announcements.checkSigs(channelAnn)) { handleLocalError(InvalidAnnouncementSignatures(d.channelId, remoteAnnSigs), d, Some(remoteAnnSigs)) } else { + // we generate a new channel_update because the scid used may change if we were previously using an alias + val scidForChannelUpdate = Helpers.scidForChannelUpdate(Some(channelAnn), realShortChannelId_opt = d.realShortChannelId_opt, remoteAlias_opt = d.remoteAlias_opt) + val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) // we use goto() instead of stay() because we want to fire transitions - goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn)) storing() + goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate) storing() } case Some(_) => // they have sent their announcement sigs, but we already have a valid channel announcement diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index dad280c14f..f020bbb22a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -427,7 +427,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = Some(remoteAlias), remoteNodeId = remoteNodeId)) } // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced - val scidForChannelUpdate = Helpers.scidForChannelUpdate(commitments.channelFlags, realShortChannelId_opt, remoteAlias_opt) + val scidForChannelUpdate = Helpers.scidForChannelUpdate(channelAnnouncement_opt = None, realShortChannelId_opt = realShortChannelId_opt, remoteAlias_opt = remoteAlias_opt) log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) val relayFees = getRelayFees(nodeParams, remoteNodeId, commitments) val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, relayFees.feeBase, relayFees.feeProportionalMillionths, commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 2370890a26..5dda9eb621 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -92,7 +92,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - // we have a real scid, but this is a private channel so alice uses bob's alias + // we have a real scid, but the channel is not announced so alice uses bob's alias assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) @@ -108,7 +108,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - // this is a private channel but bob didn't send an alias so we use the real scid + // the channel is not announced but bob didn't send an alias so we use the real scid assert(initialChannelUpdate.shortChannelId == realScid) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) @@ -123,7 +123,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - // this is a private channel so alice uses bob's alias + // the channel is not announced so alice uses bob's alias (we have a no real scid anyway) assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) @@ -163,6 +163,21 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu awaitCond(alice.stateName == NORMAL) } + test("recv ChannelReady (public, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + // zero-conf channel: we don't have a real scid + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + val channelReady = bob2alice.expectMsgType[ChannelReady] + bob2alice.forward(alice) + val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // the channel is not announced, so alice uses bob's alias (we have a no real scid anyway) + assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + bob2alice.expectNoMessage(200 millis) + awaitCond(alice.stateName == NORMAL) + } + test("recv WatchFundingSpentTriggered (remote commit)") { f => import f._ // bob publishes his commitment tx diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index d73a80c198..a51b47181a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3428,10 +3428,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(annSigs.shortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) - // public channel: we prefer the real scid alias so we switched from remote alias to real scid : we send a new channel_update - val channelUpdate1 = alice2bob.expectMsgType[ChannelUpdate] - assert(channelUpdate1.shortChannelId == realShortChannelId) - assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate == channelUpdate1) + // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures channelUpdateListener.expectNoMessage(1 second) } @@ -3448,10 +3445,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(annSigs.shortChannelId == newRealShortChannelId) // update data with real short channel id awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(newRealShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) - // public channel: we prefer the real scid alias and the real scid alias changed, so we send a new channel_update - val channelUpdate1 = alice2bob.expectMsgType[ChannelUpdate] - assert(channelUpdate1.shortChannelId == newRealShortChannelId) - assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate == channelUpdate1) + // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures channelUpdateListener.expectNoMessage(1 second) } @@ -3519,7 +3513,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val normal = alice.stateData.asInstanceOf[DATA_NORMAL] normal.realShortChannelId_opt.contains(annSigsA.shortChannelId) && normal.buried && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId }) - assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelAnnouncement_opt == Some(channelAnn)) + // we use the real scid instead of remote alias as soon as the channel is announced + val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate] + assert(lcu.channelUpdate.shortChannelId == realShortChannelId) + assert(lcu.channelAnnouncement_opt == Some(channelAnn)) + // we don't send directly the channel_update to our peer, public announcements are handled by the router + alice2bob.expectNoMessage(100 millis) } test("recv AnnouncementSignatures (re-send)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => From aab4f76f051507e1beb66d3489cda26f816c23ba Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 14:38:58 +0200 Subject: [PATCH 13/55] add test proving bug in rebroadcast See https://github.com/ACINQ/eclair/pull/2270#discussion_r875891916 --- .../ChannelStateTestsHelperMethods.scala | 10 +- .../router/ChannelRouterIntegrationSpec.scala | 109 ++++++++++++------ 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 181ed4ae08..2c254e09a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -192,7 +192,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { (aliceParams, bobParams, channelType) } - def reachNormal(setup: SetupFixture, tags: Set[String] = Set.empty): Transaction = { + def reachNormal(setup: SetupFixture, tags: Set[String] = Set.empty, interceptChannelUpdates: Boolean = true): Transaction = { import setup._ @@ -237,9 +237,11 @@ trait ChannelStateTestsBase extends Assertions with Eventually { alice2bob.forward(bob) bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) - // we don't forward the channel updates, in reality they would be processed by the router - alice2bob.expectMsgType[ChannelUpdate] - bob2alice.expectMsgType[ChannelUpdate] + if (interceptChannelUpdates) { + // we don't forward the channel updates, in reality they would be processed by the router + alice2bob.expectMsgType[ChannelUpdate] + bob2alice.expectMsgType[ChannelUpdate] + } alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] bob2blockchain.expectMsgType[WatchFundingDeeplyBuried] eventually(assert(alice.stateName == NORMAL)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index b09b536d4a..65144ddef1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -2,12 +2,11 @@ package fr.acinq.eclair.router import akka.actor.ActorSystem import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingDeeplyBuriedTriggered import fr.acinq.eclair.channel.DATA_NORMAL import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.io.Peer.PeerRoutingMessage -import fr.acinq.eclair.router.Router.{GossipOrigin, LocalGossip} +import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate} import fr.acinq.eclair.{BlockHeight, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -39,7 +38,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu reachNormal(channels, testTags) - awaitAssert(router.stateData.privateChannels.size == 1) + awaitCond(router.stateData.privateChannels.size == 1) { // only the local channel_update is known (bob won't send his before the channel is deeply buried) @@ -52,7 +51,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu val bobChannelUpdate = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate) - awaitAssert { + awaitCond { val pc = router.stateData.privateChannels.values.head // both channel_updates are known pc.update_1_opt.isDefined && pc.update_2_opt.isDefined @@ -67,28 +66,45 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu test("public local channel", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ - val fundingTx = reachNormal(channels, testTags) - - awaitAssert(router.stateData.privateChannels.size == 1) - - { - val pc = router.stateData.privateChannels.values.head - // only the local channel_update is known - assert(pc.update_1_opt.isDefined ^ pc.update_2_opt.isDefined) - } - + reachNormal(channels, testTags, interceptChannelUpdates = false) + + // the router learns about the local, still unannounced, channel + awaitCond(router.stateData.privateChannels.size == 1) + + //@formatter:off + /** there is only one channel here */ + def privateChannel: PrivateChannel = router.stateData.privateChannels.values.head + def publicChannel: PublicChannel = router.stateData.channels.values.head + //@formatter:on + + // only alice's channel_update is known (NB : due to how node ids are constructed, 1 = alice and 2 = bob) + assert(privateChannel.update_1_opt.isDefined) + assert(privateChannel.update_2_opt.isEmpty) + // alice already has a real scid because this is not a zeroconf channel + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.isDefined) + // alice uses bob's alias for her channel update + assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.localAlias) + assert(privateChannel.update_1_opt.get.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.get) + + // alice and bob send their channel_updates using remote alias when they go to NORMAL state + val aliceChannelUpdate1 = channels.alice2bob.expectMsgType[ChannelUpdate] + val bobChannelUpdate1 = channels.bob2alice.expectMsgType[ChannelUpdate] + // alice's channel_update uses bob's alias, and vice versa + assert(aliceChannelUpdate1.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].localAlias) + assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].localAlias) + // channel_updates are handled by the peer connection and sent to the router val peerConnection = TestProbe() - // alice and bob haven't yet sent their channel_updates but we can get them by looking at their internal data - val aliceChannelUpdate = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - val bobChannelUpdate = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate) + router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) - awaitAssert { - val pc = router.stateData.privateChannels.values.head - // both channel_updates are known - pc.update_1_opt.isDefined && pc.update_2_opt.isDefined + // router processes bob's channel_update and now knows both channel updates + awaitCond { + privateChannel.update_1_opt.contains(aliceChannelUpdate1) && privateChannel.update_2_opt.contains(bobChannelUpdate1) } + // there is nothing for the router to rebroadcast, channel is not announced + assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) + // funding tx reaches 6 blocks, announcements are exchanged channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) channels.alice2bob.expectMsgType[AnnouncementSignatures] @@ -98,24 +114,49 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu channels.bob2alice.expectMsgType[AnnouncementSignatures] channels.bob2alice.forward(channels.alice) - awaitAssert { + // alice and bob won't send their channel_update directly to each other because the channel has been announced + // but we can get the update from their data + awaitCond { + channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined && + channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined + } + val aliceChannelUpdate2 = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val bobChannelUpdate2 = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // this time, they use the real scid + assert(aliceChannelUpdate2.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) + assert(bobChannelUpdate2.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) + + // the router learns about the announcement and channel graduates from private to public + awaitCond { router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 } - awaitAssert { - val pc = router.stateData.channels.values.head - // both channel updates are preserved - pc.update_1_opt.isDefined && pc.update_2_opt.isDefined - } + // the router has already processed the new local channel update from alice which uses the real scid, and keeps bob's previous channel update + assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1)) - // manual rebroadcast - router ! Router.TickBroadcast - rebroadcastListener.expectMsg(Router.Rebroadcast( + // the router prepares to rebroadcast the channel announcement, the local update which use the real scid, and the first node announcement + assert(router.stateData.rebroadcast == Rebroadcast( channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), - updates = Map(aliceChannelUpdate -> Set[GossipOrigin](LocalGossip), bobChannelUpdate -> Set.empty[GossipOrigin]), // broadcast the channel_updates (they were previously unannounced) - nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip)), // new node_announcement - )) + updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)), + nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + ) + + // bob's channel_update reaches the router + router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) + // router processes bob's channel_update and now knows both channel updates with real scids + awaitCond { + publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate2) + } + + // router is now ready to rebroadcast both channel updates + assert(router.stateData.rebroadcast == Rebroadcast( + channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), + updates = Map( + aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip), + bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), + nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + ) } } From 32a0d5a2115b354990bc032525ec127dcf177e8d Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 15:30:12 +0200 Subject: [PATCH 14/55] fix rebroadcast bug --- .../fr/acinq/eclair/router/Validation.scala | 8 ++++--- .../router/ChannelRouterIntegrationSpec.scala | 22 +++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index eedf1c7530..26fa6ba5e4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.router.Monitoring.Metrics import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates, toLongId} +import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates} object Validation { @@ -174,6 +174,7 @@ object Validation { } // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid val updates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) + .filter(_.shortChannelId == pc.shortChannelId) .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) .toMap d.copy( @@ -297,7 +298,7 @@ object Validation { log.debug("ignoring {} (stale)", u) sendDecision(origins, GossipDecision.Stale(u)) d - } else if (pc.getChannelUpdateSameSideAs(u).exists(_.timestamp >= u.timestamp)) { + } else if (pc.getChannelUpdateSameSideAs(u).exists(previous => previous.timestamp >= u.timestamp && previous.shortChannelId == u.shortChannelId)) { // NB: we also check the id because there could be a switch alias->real scid log.debug("ignoring {} (duplicate)", u) sendDecision(origins, GossipDecision.Duplicate(u)) update match { @@ -476,7 +477,8 @@ object Validation { val d1 = addPublicChannel(d, nodeParams, publicChannel) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) - handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) + val d2 = handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) + d2 case None => // this is a known unannounced channel, we can process the channel_update handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 65144ddef1..b322b02527 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -68,15 +68,15 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu reachNormal(channels, testTags, interceptChannelUpdates = false) - // the router learns about the local, still unannounced, channel - awaitCond(router.stateData.privateChannels.size == 1) - //@formatter:off /** there is only one channel here */ def privateChannel: PrivateChannel = router.stateData.privateChannels.values.head def publicChannel: PublicChannel = router.stateData.channels.values.head //@formatter:on + // the router learns about the local, still unannounced, channel + awaitCond(router.stateData.privateChannels.size == 1) + // only alice's channel_update is known (NB : due to how node ids are constructed, 1 = alice and 2 = bob) assert(privateChannel.update_1_opt.isDefined) assert(privateChannel.update_2_opt.isEmpty) @@ -114,6 +114,11 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu channels.bob2alice.expectMsgType[AnnouncementSignatures] channels.bob2alice.forward(channels.alice) + // the router learns about the announcement and channel graduates from private to public + awaitCond { + router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 + } + // alice and bob won't send their channel_update directly to each other because the channel has been announced // but we can get the update from their data awaitCond { @@ -126,11 +131,6 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(aliceChannelUpdate2.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) assert(bobChannelUpdate2.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) - // the router learns about the announcement and channel graduates from private to public - awaitCond { - router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 - } - // the router has already processed the new local channel update from alice which uses the real scid, and keeps bob's previous channel update assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1)) @@ -138,11 +138,11 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(router.stateData.rebroadcast == Rebroadcast( channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)), - nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) ) // bob's channel_update reaches the router - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) + router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate2) // router processes bob's channel_update and now knows both channel updates with real scids awaitCond { @@ -155,7 +155,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu updates = Map( aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip), bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), - nodes = Map(router.underlyingActor.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) ) } From 3aa0560e7f01bc6cb51e89c7f21d5598f1e2f040 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 15:38:09 +0200 Subject: [PATCH 15/55] handle local node announcement synchronously It is more consistent with how we handle channel updates, and makes testing easier. --- .../scala/fr/acinq/eclair/router/Validation.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 26fa6ba5e4..5a5ef4739f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -166,18 +166,12 @@ object Validation { private def addPublicChannel(d: Data, nodeParams: NodeParams, pc: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors log.debug("adding public channel channelId={} realScid={}", pc.channelId, pc.shortChannelId) - // in case this was our first local channel, we make a node announcement - if (!d.nodes.contains(nodeParams.nodeId)) { - log.info("first local channel validated, announcing local node") - val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) - ctx.self ! nodeAnn - } // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid val updates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) .filter(_.shortChannelId == pc.shortChannelId) .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) .toMap - d.copy( + val d1 = d.copy( channels = d.channels + (pc.shortChannelId -> pc), // we remove the corresponding unannounced channel that we may have until now privateChannels = d.privateChannels - pc.channelId, @@ -189,6 +183,12 @@ object Validation { updates = d.rebroadcast.updates ++ updates1 ) ) + // in case this was our first local channel, we make a node announcement + if (!d.nodes.contains(nodeParams.nodeId)) { + log.info("first local channel validated, announcing local node") + val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) + handleNodeAnnouncement(d1, nodeParams.db.network, Set(LocalGossip), nodeAnn) + } else d1 } def handleChannelSpent(d: Data, db: NetworkDb, shortChannelId: RealShortChannelId)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { From 774432e357f51c38c0411d1437b480e606334a32 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 2 Jun 2022 17:15:31 +0200 Subject: [PATCH 16/55] always re-send channel_update at reconnection for unannounced channels Don't take the `buried` property into account. See https://github.com/ACINQ/eclair/pull/2270#discussion_r876039902 --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 4 +- .../fr/acinq/eclair/channel/RestoreSpec.scala | 40 +++++++----- .../channel/states/e/OfflineStateSpec.scala | 63 +++++++++++++------ .../router/ChannelRouterIntegrationSpec.scala | 55 +++++++++++----- 4 files changed, 107 insertions(+), 55 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 f9c024842d..add4ee1b8f 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 @@ -1623,8 +1623,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val val emitEvent_opt: Option[EmitLocalChannelEvent] = (state, nextState, stateData, nextStateData) match { case (WAIT_FOR_INIT_INTERNAL, OFFLINE, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate("restore", d, sendToPeer = false)) 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 && d2.buried)) - 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 && d2.buried)) + 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 (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)) case (NORMAL | SYNCING | OFFLINE, SHUTDOWN | NEGOTIATING | CLOSING | CLOSED | ERR_INFORMATION_LEAK | WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, d: DATA_NORMAL, _) => Some(EmitLocalChannelDown(d)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala index 3da94a0bae..65f4343ce0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala @@ -5,10 +5,10 @@ import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.testkit import akka.testkit.{TestActor, TestFSMRef, TestProbe} import com.softwaremill.quicklens.ModifyPimp +import fr.acinq.bitcoin +import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat._ -import fr.acinq.bitcoin.ScriptFlags -import fr.acinq.bitcoin import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingSpentTriggered import fr.acinq.eclair.channel.fsm.Channel @@ -19,7 +19,7 @@ import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.transactions.Transactions.{ClaimP2WPKHOutputTx, DefaultCommitmentFormat, InputInfo, TxOwner} -import fr.acinq.eclair.wire.protocol.{ChannelReestablish, CommitSig, Error, ChannelReady, Init, RevokeAndAck} +import fr.acinq.eclair.wire.protocol.{ChannelReady, ChannelReestablish, ChannelUpdate, CommitSig, Error, Init, RevokeAndAck} import fr.acinq.eclair.{TestKitBaseClass, _} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -31,19 +31,16 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan type FixtureParam = SetupFixture override def withFixture(test: OneArgTest): Outcome = { - val setup = test.tags.contains("disable-offline-mismatch") match { - case false => init() - case true => init(nodeParamsA = Alice.nodeParams.copy(onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(closeOnOfflineMismatch = false))) - } + val setup = init() within(30 seconds) { reachNormal(setup) withFixture(test.toNoArgTest(setup)) } } - def aliceInit = Init(Alice.nodeParams.features.initFeatures()) + private def aliceInit = Init(Alice.nodeParams.features.initFeatures()) - def bobInit = Init(Bob.nodeParams.features.initFeatures()) + private def bobInit = Init(Bob.nodeParams.features.initFeatures()) test("use funding pubkeys from publish commitment to spend our output") { f => import f._ @@ -136,14 +133,14 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan /** We are only interested in channel updates from Alice, we use the channel flag to discriminate */ def aliceChannelUpdateListener(channelUpdateListener: TestProbe): TestProbe = { val aliceListener = TestProbe() - channelUpdateListener.setAutoPilot(new testkit.TestActor.AutoPilot { - override def run(sender: ActorRef, msg: Any): TestActor.AutoPilot = msg match { + channelUpdateListener.setAutoPilot { + (sender: ActorRef, msg: Any) => msg match { case u: ChannelUpdateParametersChanged if u.channelUpdate.channelFlags.isNode1 == Announcements.isNode1(Alice.nodeParams.nodeId, Bob.nodeParams.nodeId) => aliceListener.ref.tell(msg, sender) TestActor.KeepRunning case _ => TestActor.KeepRunning } - }) + } aliceListener } @@ -206,17 +203,21 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan // and we terminate Alice alice.stop() + // there should ne no pending messages + alice2bob.expectNoMessage() + bob2alice.expectNoMessage() + // we restart Alice with different configurations Seq( Alice.nodeParams .modify(_.relayParams.privateChannelFees.feeBase).setTo(765 msat), Alice.nodeParams .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345), - Alice.nodeParams - .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), - Alice.nodeParams - .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345) - .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), + // Alice.nodeParams + // .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), + // Alice.nodeParams + // .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345) + // .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), ) foreach { newConfig => val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(newConfig, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref) @@ -238,6 +239,11 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan bob2alice.expectMsgType[ChannelReady] alice2bob.forward(bob) bob2alice.forward(newAlice) + alice2bob.expectMsgType[ChannelUpdate] + bob2alice.expectMsgType[ChannelUpdate] + alice2bob.expectNoMessage() + bob2alice.expectNoMessage() + awaitCond(newAlice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 6a4620f372..d66d57c927 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -18,9 +18,10 @@ package fr.acinq.eclair.channel.states.e import akka.actor.ActorRef import akka.testkit.{TestFSMRef, TestProbe} +import com.softwaremill.quicklens.ModifyPimp +import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Transaction} -import fr.acinq.bitcoin.ScriptFlags import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw @@ -45,14 +46,22 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with type FixtureParam = SetupFixture + /** If set, do not close channel in case of a fee mismatch when disconnected */ + val DisableOfflineMismatch = "disable_offline_mismatch" + /** If set, channel_update will be ignored */ + val IgnoreChannelUpdates = "ignore_channel_updates" + override def withFixture(test: OneArgTest): Outcome = { - val setup = test.tags.contains("disable-offline-mismatch") match { - case false => init() - case true => init(nodeParamsA = Alice.nodeParams.copy(onChainFeeConf = Alice.nodeParams.onChainFeeConf.copy(closeOnOfflineMismatch = false))) - } + val aliceParams = Alice.nodeParams + .modify(_.onChainFeeConf.closeOnOfflineMismatch).setToIf(test.tags.contains(DisableOfflineMismatch))(false) + val setup = init(nodeParamsA = aliceParams) import setup._ within(30 seconds) { reachNormal(setup) + if (test.tags.contains(IgnoreChannelUpdates)) { + setup.alice2bob.ignoreMsg({ case _: ChannelUpdate => true }) + setup.bob2alice.ignoreMsg({ case _: ChannelUpdate => true }) + } awaitCond(alice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) withFixture(test.toNoArgTest(setup)) @@ -62,7 +71,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val aliceInit = Init(TestConstants.Alice.nodeParams.features.initFeatures()) val bobInit = Init(TestConstants.Bob.nodeParams.features.initFeatures()) - test("re-send lost htlc and signature after first commitment") { f => + test("re-send lost htlc and signature after first commitment", Tag(IgnoreChannelUpdates)) { f => import f._ // alice bob // | | @@ -115,7 +124,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == NORMAL) } - test("re-send lost revocation") { f => + test("re-send lost revocation", Tag(IgnoreChannelUpdates)) { f => import f._ // alice bob // | | @@ -159,7 +168,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == NORMAL) } - test("re-send lost signature after revocation") { f => + test("re-send lost signature after revocation", Tag(IgnoreChannelUpdates)) { f => import f._ // alice bob // | | @@ -232,7 +241,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with awaitCond(bob.stateName == NORMAL) } - test("resume htlc settlement") { f => + test("resume htlc settlement", Tag(IgnoreChannelUpdates)) { f => import f._ // Successfully send a first payment. @@ -279,7 +288,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.index == 4) } - test("reconnect with an outdated commitment") { f => + test("reconnect with an outdated commitment", Tag(IgnoreChannelUpdates)) { f => import f._ val (ra1, htlca1) = addHtlc(250000000 msat, alice, bob, alice2bob, bob2alice) @@ -329,7 +338,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("reconnect with an outdated commitment (but counterparty can't tell)") { f => + test("reconnect with an outdated commitment (but counterparty can't tell)", Tag(IgnoreChannelUpdates)) { f => import f._ // we start by storing the current state @@ -384,7 +393,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Transaction.correctlySpends(claimMainOutput, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } - test("counterparty lies about having a more recent commitment") { f => + test("counterparty lies about having a more recent commitment", Tag(IgnoreChannelUpdates)) { f => import f._ val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx @@ -406,7 +415,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(error == Error(channelId(alice), InvalidRevokedCommitProof(channelId(alice), 0, 42, invalidReestablish.yourLastPerCommitmentSecret).getMessage)) } - test("change relay fee while offline") { f => + test("change relay fee while offline", Tag(IgnoreChannelUpdates)) { f => import f._ val sender = TestProbe() @@ -461,7 +470,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(!update.channelUpdate.channelFlags.isEnabled) } - test("replay pending commands when going back to NORMAL") { f => + test("replay pending commands when going back to NORMAL", Tag(IgnoreChannelUpdates)) { f => import f._ val (r, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) @@ -613,7 +622,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - test("handle feerate changes while offline (don't close on mismatch)", Tag("disable-offline-mismatch")) { f => + test("handle feerate changes while offline (don't close on mismatch)", Tag(DisableOfflineMismatch)) { f => import f._ // we only close channels on feerate mismatch if there are HTLCs at risk in the commitment @@ -669,11 +678,11 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - test("handle feerate changes while offline (update at reconnection)") { f => + test("handle feerate changes while offline (update at reconnection)", Tag(IgnoreChannelUpdates)) { f => testUpdateFeeOnReconnect(f, shouldUpdateFee = true) } - test("handle feerate changes while offline (shutdown sent, don't update at reconnection)") { f => + test("handle feerate changes while offline (shutdown sent, don't update at reconnection)", Tag(IgnoreChannelUpdates)) { f => import f._ // alice initiates a shutdown @@ -722,20 +731,27 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with } } - test("re-send channel_update at reconnection for private channels") { f => + test("re-send channel_update at reconnection for unannounced channels") { f => import f._ // we simulate a disconnection / reconnection disconnect(alice, bob) + // we wait 1s so the new channel_update doesn't have the same timestamp + Thread.sleep(1_000) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] bob2alice.forward(alice) alice2bob.forward(bob) - // at this point the channel isn't deeply buried: channel_update isn't sent again + // alice and bob resend their channel_ready because there hasn't been payments on the channel alice2bob.expectMsgType[ChannelReady] bob2alice.expectMsgType[ChannelReady] + + // alice and bob resend their channel update at reconnection (unannounced channel) + alice2bob.expectMsgType[ChannelUpdate] + bob2alice.expectMsgType[ChannelUpdate] + alice2bob.expectNoMessage() bob2alice.expectNoMessage() @@ -745,13 +761,18 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we simulate a disconnection / reconnection disconnect(alice, bob) + // we wait 1s so the new channel_update doesn't have the same timestamp + Thread.sleep(1_000) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] bob2alice.forward(alice) alice2bob.forward(bob) - // at this point the channel still isn't deeply buried: channel_update isn't sent again + // alice and bob resend their channel update at reconnection (unannounced channel) + alice2bob.expectMsgType[ChannelUpdate] + bob2alice.expectMsgType[ChannelUpdate] + alice2bob.expectNoMessage() bob2alice.expectNoMessage() @@ -763,6 +784,8 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we get disconnected again disconnect(alice, bob) + // we wait 1s so the new channel_update doesn't have the same timestamp + Thread.sleep(1_000) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index b322b02527..75d6d7e959 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -12,12 +12,20 @@ import fr.acinq.eclair.{BlockHeight, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} +import scala.concurrent.duration.DurationInt + /** * This test checks the integration between Channel and Router (events, etc.) */ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ChannelStateTestsBase { - case class FixtureParam(router: TestFSMRef[Router.State, Router.Data, Router], rebroadcastListener: TestProbe, channels: SetupFixture, testTags: Set[String]) + case class FixtureParam(router: TestFSMRef[Router.State, Router.Data, Router], rebroadcastListener: TestProbe, channels: SetupFixture, testTags: Set[String]) { + //@formatter:off + /** there is only one channel here */ + def privateChannel: PrivateChannel = router.stateData.privateChannels.values.head + def publicChannel: PublicChannel = router.stateData.channels.values.head + //@formatter:on + } implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging @@ -36,31 +44,46 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu test("private local channel") { f => import f._ - reachNormal(channels, testTags) + reachNormal(channels, testTags, interceptChannelUpdates = false) + // the router learns about the local, still unannounced, channel awaitCond(router.stateData.privateChannels.size == 1) - { - // only the local channel_update is known (bob won't send his before the channel is deeply buried) - val pc = router.stateData.privateChannels.values.head - assert(pc.update_1_opt.isDefined ^ pc.update_2_opt.isDefined) - } + // only alice's channel_update is known (NB : due to how node ids are constructed, 1 = alice and 2 = bob) + assert(privateChannel.update_1_opt.isDefined) + assert(privateChannel.update_2_opt.isEmpty) + // alice already has a real scid because this is not a zeroconf channel + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.isDefined) + // alice uses bob's alias for her channel update + assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.localAlias) + assert(privateChannel.update_1_opt.get.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.get) + // alice and bob send their channel_updates using remote alias when they go to NORMAL state + val aliceChannelUpdate1 = channels.alice2bob.expectMsgType[ChannelUpdate] + val bobChannelUpdate1 = channels.bob2alice.expectMsgType[ChannelUpdate] + // alice's channel_update uses bob's alias, and vice versa + assert(aliceChannelUpdate1.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].localAlias) + assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].localAlias) + // channel_updates are handled by the peer connection and sent to the router val peerConnection = TestProbe() - // bob hasn't yet sent his channel_update but we can get it by looking at its internal data - val bobChannelUpdate = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate) + router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) + // router processes bob's channel_update and now knows both channel updates awaitCond { - val pc = router.stateData.privateChannels.values.head - // both channel_updates are known - pc.update_1_opt.isDefined && pc.update_2_opt.isDefined + privateChannel.update_1_opt.contains(aliceChannelUpdate1) && privateChannel.update_2_opt.contains(bobChannelUpdate1) } - // manual rebroadcast - router ! Router.TickBroadcast - rebroadcastListener.expectNoMessage() + // there is nothing for the router to rebroadcast, channel is not announced + assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) + + // funding tx reaches 6 blocks, no announcements are exchanged because the channel is private + channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + // alice and bob won't send their channel_update directly to each other because they haven't changed + channels.alice2bob.expectNoMessage(100 millis) + channels.bob2alice.expectNoMessage(100 millis) } test("public local channel", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => From 93a3d7ef63dd8bab219ea7daa37939a94b071e7b Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 10:54:01 +0200 Subject: [PATCH 17/55] add channel relay tests --- .../eclair/payment/PaymentPacketSpec.scala | 4 +- .../payment/relay/ChannelRelayerSpec.scala | 204 +++++++++++------- 2 files changed, 131 insertions(+), 77 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index 491f200789..1a3edce695 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -368,12 +368,12 @@ object PaymentPacketSpec { Sphinx.create(sessionKey, packetPayloadLength, nodes, payloadsBin, Some(associatedData)).get.packet } - def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat): Commitments = { + def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat, channelFeatures: ChannelFeatures = ChannelFeatures()): Commitments = { val params = LocalParams(null, null, null, null, None, null, null, 0, isInitiator = true, null, None, null) val remoteParams = RemoteParams(randomKey().publicKey, null, null, None, null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, null, None) val commitInput = InputInfo(OutPoint(randomBytes32(), 1), TxOut(testCapacity, Nil), Nil) val channelFlags = ChannelFlags.Private - new Commitments(channelId, ChannelConfig.standard, ChannelFeatures(), params, remoteParams, channelFlags, null, null, null, null, 0, 0, Map.empty, null, commitInput, null) { + new Commitments(channelId, ChannelConfig.standard, channelFeatures, params, remoteParams, channelFlags, null, null, null, null, 0, 0, Map.empty, null, commitInput, null) { override lazy val availableBalanceForSend: MilliSatoshi = testAvailableBalanceForSend.max(0 msat) override lazy val availableBalanceForReceive: MilliSatoshi = testAvailableBalanceForReceive.max(0 msat) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index 4aca396be2..2f53c48740 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -23,6 +23,7 @@ import akka.actor.typed.scaladsl.adapter.TypedActorRefOps import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong} +import fr.acinq.eclair.Features.ScidAlias import fr.acinq.eclair.TestConstants.emptyOnionPacket import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.fee.FeeratePerKw @@ -58,8 +59,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a def expectFwdFail(register: TestProbe[Any], channelId: ByteVector32, cmd: channel.Command): Register.Forward[channel.Command] = { val fwd = register.expectMessageType[Register.Forward[channel.Command]] - assert(fwd.channelId == channelId) assert(fwd.message == cmd) + assert(fwd.channelId == channelId) fwd } @@ -75,39 +76,91 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a fwd } - test("relay htlc-add with the real scid") { f => + def basicRelaytest(f: FixtureParam)(relayPayloadScid: ShortChannelId, lcu: LocalChannelUpdate, success: Boolean): Unit = { import f._ - val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val payload = RelayLegacyPayload(relayPayloadScid, outgoingAmount, outgoingExpiry) + val r = createValidIncomingPacket(payload) - channelRelayer ! WrappedLocalChannelUpdate(u) + channelRelayer ! WrappedLocalChannelUpdate(lcu) channelRelayer ! Relay(r) - expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) + if (success) { + expectFwdAdd(register, lcu.channelId, outgoingAmount, outgoingExpiry) + } else { + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) + } } - test("relay htlc-add with the local alias") { f => - import f._ + test("relay with real scid (channel update uses real scid)") { f => + basicRelaytest(f)(relayPayloadScid = realScid1, lcu = createLocalUpdate(channelId1), success = true) + } - val payload = RelayLegacyPayload(localAlias1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(localAlias1) + test("relay with real scid (channel update uses remote alias)") { f => + basicRelaytest(f)(relayPayloadScid = realScid1, lcu = createLocalUpdate(channelId1, channelUpdateScid_opt = Some(remoteAlias1)), success = true) + } - channelRelayer ! WrappedLocalChannelUpdate(u) - channelRelayer ! Relay(r) + test("relay with local alias (channel update uses real scid)") { f => + basicRelaytest(f)(relayPayloadScid = localAlias1, lcu = createLocalUpdate(channelId1), success = true) + } - expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) + test("relay with local alias (channel update uses remote alias)") { f => + // we use a random value to simulate a remote alias + basicRelaytest(f)(relayPayloadScid = localAlias1, lcu = createLocalUpdate(channelId1, channelUpdateScid_opt = Some(remoteAlias1)), success = true) + } + + test("fail to relay with real scid when option_scid_alias is enabled (channel update uses real scid)") { f => + basicRelaytest(f)(relayPayloadScid = realScid1, lcu = createLocalUpdate(channelId1, optionScidAlias = true), success = false) + } + + test("fail to relay with real scid when option_scid_alias is enabled (channel update uses remote alias)") { f => + basicRelaytest(f)(relayPayloadScid = realScid1, lcu = createLocalUpdate(channelId1, optionScidAlias = true, channelUpdateScid_opt = Some(remoteAlias1)), success = false) + } + + test("relay with local alias when option_scid_alias is enabled (channel update uses real scid)") { f => + basicRelaytest(f)(relayPayloadScid = localAlias1, lcu = createLocalUpdate(channelId1, optionScidAlias = true), success = true) + } + + test("relay with local alias when option_scid_alias is enabled (channel update uses remote alias)") { f => + // we use a random value to simulate a remote alias + basicRelaytest(f)(relayPayloadScid = localAlias1, lcu = createLocalUpdate(channelId1, optionScidAlias = true, channelUpdateScid_opt = Some(remoteAlias1)), success = true) } - test("relay an htlc-add with onion tlv payload") { f => + test("relay with new real scid after reorg") { f => + import f._ + import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv._ + + // initial channel update + val lcu1 = createLocalUpdate(channelId1) + val payload1 = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) + val r1 = createValidIncomingPacket(payload1) + channelRelayer ! WrappedLocalChannelUpdate(lcu1) + channelRelayer ! Relay(r1) + expectFwdAdd(register, lcu1.channelId, outgoingAmount, outgoingExpiry) + + // reorg happens + val realScid1AfterReorg = ShortChannelId(111112).toReal + val lcu2 = createLocalUpdate(channelId1).copy(realShortChannelId_opt = Some(realScid1AfterReorg)) + val payload2 = RelayLegacyPayload(realScid1AfterReorg, outgoingAmount, outgoingExpiry) + val r2 = createValidIncomingPacket(payload2) + channelRelayer ! WrappedLocalChannelUpdate(lcu2) + + // both old and new real scids work + channelRelayer ! Relay(r1) + expectFwdAdd(register, lcu1.channelId, outgoingAmount, outgoingExpiry) + // new real scid works + channelRelayer ! Relay(r2) + expectFwdAdd(register, lcu2.channelId, outgoingAmount, outgoingExpiry) + } + + + test("relay with onion tlv payload") { f => import f._ import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv._ val payload = ChannelRelayTlvPayload(TlvStream[OnionPaymentPayloadTlv](AmountToForward(outgoingAmount), OutgoingCltv(outgoingExpiry), OutgoingChannelId(realScid1))) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -115,18 +168,18 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) } - test("relay an htlc-add with retries") { f => + test("relay with retries") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) + val r = createValidIncomingPacket(payload) // we tell the relayer about the first channel - val u1 = createLocalUpdate(realScid1) + val u1 = createLocalUpdate(channelId1) channelRelayer ! WrappedLocalChannelUpdate(u1) // this is another channel, with less balance (it will be preferred) - val u2 = createLocalUpdate(realScId2, 8000000 msat) + val u2 = createLocalUpdate(channelId2, balance = 8000000 msat) channelRelayer ! WrappedLocalChannelUpdate(u2) channelRelayer ! Relay(r) @@ -145,23 +198,23 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryNodeFailure), commit = true)) } - test("fail to relay an htlc-add when we have no channel_update for the next channel") { f => + test("fail to relay when we have no channel_update for the next channel") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) + val r = createValidIncomingPacket(payload) channelRelayer ! Relay(r) expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) } - test("fail to relay an htlc-add when register returns an error") { f => + test("fail to relay when register returns an error") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -172,12 +225,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) } - test("fail to relay an htlc-add when the channel is advertised as unusable (down)") { f => + test("fail to relay when the channel is advertised as unusable (down)") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1) val d = LocalChannelDown(null, channelId = channelIds(realScid1), Some(realScid1.toReal), null, outgoingNodeId) channelRelayer ! WrappedLocalChannelUpdate(u) @@ -187,12 +240,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) } - test("fail to relay an htlc-add (channel disabled)") { f => + test("fail to relay when channel is disabled") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1, enabled = false) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1, enabled = false) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -200,12 +253,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(u.channelUpdate.messageFlags, u.channelUpdate.channelFlags, u.channelUpdate)), commit = true)) } - test("fail to relay an htlc-add (amount below minimum)") { f => + test("fail to relay when amount is below minimum") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1, htlcMinimum = outgoingAmount + 1.msat) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1, htlcMinimum = outgoingAmount + 1.msat) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -213,12 +266,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(outgoingAmount, u.channelUpdate)), commit = true)) } - test("relay an htlc-add (expiry larger than our requirements)") { f => + test("relay when expiry larger than our requirements") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val u = createLocalUpdate(realScid1) - val r = createValidIncomingPacket(1100000 msat, outgoingExpiry + u.channelUpdate.cltvExpiryDelta + CltvExpiryDelta(1), payload) + val u = createLocalUpdate(channelId1) + val r = createValidIncomingPacket(payload, expiryIn = outgoingExpiry + u.channelUpdate.cltvExpiryDelta + CltvExpiryDelta(1)) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -226,12 +279,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdAdd(register, channelIds(realScid1), payload.amountToForward, payload.outgoingCltv).message } - test("fail to relay an htlc-add (expiry too small)") { f => + test("fail to relay when expiry is too small") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val u = createLocalUpdate(realScid1) - val r = createValidIncomingPacket(1100000 msat, outgoingExpiry + u.channelUpdate.cltvExpiryDelta - CltvExpiryDelta(1), payload) + val u = createLocalUpdate(channelId1) + val r = createValidIncomingPacket(payload, expiryIn = outgoingExpiry + u.channelUpdate.cltvExpiryDelta - CltvExpiryDelta(1)) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -239,12 +292,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(payload.outgoingCltv, u.channelUpdate)), commit = true)) } - test("fail to relay an htlc-add (fee insufficient)") { f => + test("fail to relay when fee is insufficient") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(outgoingAmount + 1.msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val r = createValidIncomingPacket(payload, amountIn = outgoingAmount + 1.msat) + val u = createLocalUpdate(channelId1) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) @@ -252,12 +305,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, u.channelUpdate)), commit = true)) } - test("relay an htlc-add that would fail (fee insufficient) with a recent channel update but succeed with the previous update") { f => + test("relay that would fail (fee insufficient) with a recent channel update but succeed with the previous update") { f => import f._ val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(outgoingAmount + 1.msat, CltvExpiry(400100), payload) - val u1 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now(), feeBaseMsat = 1 msat, feeProportionalMillionths = 0) + val r = createValidIncomingPacket(payload, amountIn = outgoingAmount + 1.msat) + val u1 = createLocalUpdate(channelId1, timestamp = TimestampSecond.now(), feeBaseMsat = 1 msat, feeProportionalMillionths = 0) channelRelayer ! WrappedLocalChannelUpdate(u1) channelRelayer ! Relay(r) @@ -265,7 +318,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a // relay succeeds with current channel update (u1) with lower fees expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) - val u2 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now() - 530) + val u2 = createLocalUpdate(channelId1, timestamp = TimestampSecond.now() - 530) channelRelayer ! WrappedLocalChannelUpdate(u2) channelRelayer ! Relay(r) @@ -273,7 +326,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a // relay succeeds because the current update (u2) with higher fees occurred less than 10 minutes ago expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry) - val u3 = createLocalUpdate(realScid1, timestamp = TimestampSecond.now() - 601) + val u3 = createLocalUpdate(channelId1, timestamp = TimestampSecond.now() - 601) channelRelayer ! WrappedLocalChannelUpdate(u1) channelRelayer ! WrappedLocalChannelUpdate(u3) @@ -283,14 +336,14 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, u3.channelUpdate)), commit = true)) } - test("fail to relay an htlc-add (local error)") { f => + test("fail to relay when there is a local error") { f => import f._ val channelId1 = channelIds(realScid1) val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) - val u_disabled = createLocalUpdate(realScid1, enabled = false) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1) + val u_disabled = createLocalUpdate(channelId1, enabled = false) case class TestCase(exc: ChannelException, update: ChannelUpdate, failure: FailureMessage) @@ -337,7 +390,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a { val payload = RelayLegacyPayload(ShortChannelId(12345), 998900 msat, CltvExpiry(60)) - val r = createValidIncomingPacket(1000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) // select the channel to the same node, with the lowest capacity and balance but still high enough to handle the payment val cmd1 = expectFwdAdd(register, channelUpdates(ShortChannelId(22223)).channelId, payload.amountToForward, payload.outgoingCltv).message @@ -357,35 +410,35 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a { // higher amount payment (have to increased incoming htlc amount for fees to be sufficient) val payload = RelayLegacyPayload(ShortChannelId(12345), 50000000 msat, CltvExpiry(60)) - val r = createValidIncomingPacket(60000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 60000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) expectFwdAdd(register, channelUpdates(ShortChannelId(11111)).channelId, payload.amountToForward, payload.outgoingCltv).message } { // lower amount payment val payload = RelayLegacyPayload(ShortChannelId(12345), 1000 msat, CltvExpiry(60)) - val r = createValidIncomingPacket(60000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 60000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) expectFwdAdd(register, channelUpdates(ShortChannelId(33333)).channelId, payload.amountToForward, payload.outgoingCltv).message } { // payment too high, no suitable channel found, we keep the requested one val payload = RelayLegacyPayload(ShortChannelId(12345), 1000000000 msat, CltvExpiry(60)) - val r = createValidIncomingPacket(1010000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 1010000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) expectFwdAdd(register, channelUpdates(ShortChannelId(12345)).channelId, payload.amountToForward, payload.outgoingCltv).message } { // cltv expiry larger than our requirements val payload = RelayLegacyPayload(ShortChannelId(12345), 998900 msat, CltvExpiry(50)) - val r = createValidIncomingPacket(1000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) expectFwdAdd(register, channelUpdates(ShortChannelId(22223)).channelId, payload.amountToForward, payload.outgoingCltv).message } { // cltv expiry too small, no suitable channel found val payload = RelayLegacyPayload(ShortChannelId(12345), 998900 msat, CltvExpiry(61)) - val r = createValidIncomingPacket(1000000 msat, CltvExpiry(70), payload) + val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70)) channelRelayer ! Relay(r) expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(CltvExpiry(61), channelUpdates(ShortChannelId(12345)).channelUpdate)), commit = true)) } @@ -396,9 +449,9 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val channelId1 = channelIds(realScid1) val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) - val u_disabled = createLocalUpdate(realScid1, enabled = false) + val r = createValidIncomingPacket(payload, 1100000 msat, CltvExpiry(400100)) + val u = createLocalUpdate(channelId1) + val u_disabled = createLocalUpdate(channelId1, enabled = false) val downstream_htlc = UpdateAddHtlc(channelId1, 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket) case class TestCase(result: HtlcResult, cmd: channel.HtlcSettlementCommand) @@ -428,8 +481,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val channelId1 = channelIds(realScid1) val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) - val r = createValidIncomingPacket(1100000 msat, CltvExpiry(400100), payload) - val u = createLocalUpdate(realScid1) + val r = createValidIncomingPacket(payload) + val u = createLocalUpdate(channelId1) val downstream_htlc = UpdateAddHtlc(channelId1, 7, outgoingAmount, paymentHash, outgoingExpiry, emptyOnionPacket) case class TestCase(result: HtlcResult) @@ -463,6 +516,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val channelId_ab = randomBytes32() val channelId_bc = randomBytes32() val alias_ab = ShortChannelId.generateLocalAlias() + val alias_bc = ShortChannelId.generateLocalAlias() val a = PaymentPacketSpec.a val sender = TestProbe[Relayer.OutgoingChannels]() @@ -474,7 +528,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a } channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, -2000 msat, 300000 msat))) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_bc, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) val channels1 = getOutgoingChannels(true) assert(channels1.size == 2) @@ -519,6 +573,8 @@ object ChannelRelayerSpec { val localAlias1: LocalAlias = ShortChannelId(111000).toAlias val localAlias2: LocalAlias = ShortChannelId(222000).toAlias + val remoteAlias1: ShortChannelId = ShortChannelId(111999) + val channelId1: ByteVector32 = randomBytes32() val channelId2: ByteVector32 = randomBytes32() @@ -529,20 +585,18 @@ object ChannelRelayerSpec { localAlias2 -> channelId2, ) - def createValidIncomingPacket(amountIn: MilliSatoshi, expiryIn: CltvExpiry, payload: ChannelRelayPayload): IncomingPaymentPacket.ChannelRelayPacket = { + def createValidIncomingPacket(payload: ChannelRelayPayload, amountIn: MilliSatoshi = 1_100_000 msat, expiryIn: CltvExpiry = CltvExpiry(400_100)): IncomingPaymentPacket.ChannelRelayPacket = { val add_ab = UpdateAddHtlc(channelId = randomBytes32(), id = 123456, amountIn, paymentHash, expiryIn, emptyOnionPacket) ChannelRelayPacket(add_ab, payload, emptyOnionPacket) } - /** - * @param realScidOrAlias will be used as id for the channel update - */ - def createLocalUpdate(realScidOrAlias: ShortChannelId, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100): LocalChannelUpdate = { - val channelId = channelIds(realScidOrAlias) + def createLocalUpdate(channelId: ByteVector32, channelUpdateScid_opt: Option[ShortChannelId] = None, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100, optionScidAlias: Boolean = false): LocalChannelUpdate = { val realScid = channelIds.collectFirst { case (realScid: RealShortChannelId, cid) if cid == channelId => realScid }.get val localAlias = channelIds.collectFirst { case (localAlias: LocalAlias, cid) if cid == channelId => localAlias }.get - val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, realScidOrAlias, timestamp, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, feeBaseMsat, feeProportionalMillionths, Some(capacity.toMilliSatoshi)) - val commitments = PaymentPacketSpec.makeCommitments(channelId, testAvailableBalanceForSend = balance, testCapacity = capacity) - LocalChannelUpdate(null, channelId, Some(realScid), localAlias, outgoingNodeId, None, update, commitments) + val channelUpdateScid = channelUpdateScid_opt.getOrElse(realScid) + val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, channelUpdateScid, timestamp, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, feeBaseMsat, feeProportionalMillionths, Some(capacity.toMilliSatoshi)) + val channelFeatures = if (optionScidAlias) ChannelFeatures(ScidAlias) else ChannelFeatures() + val commitments = PaymentPacketSpec.makeCommitments(channelId, testAvailableBalanceForSend = balance, testCapacity = capacity, channelFeatures = channelFeatures) + LocalChannelUpdate(null, channelId, realShortChannelId_opt = Some(realScid), localAlias = localAlias, outgoingNodeId, None, update, commitments) } } \ No newline at end of file From 2cf133893a429231bc0ecc28f70c181ccabea564 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 13:14:24 +0200 Subject: [PATCH 18/55] remove remaining triple equals --- .../c/WaitForChannelReadyStateSpec.scala | 30 +++++++++---------- .../fr/acinq/eclair/router/RouterSpec.scala | 8 ++--- .../channel/version3/ChannelCodecs3Spec.scala | 8 ++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 5dda9eb621..d6a03f4b5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -93,9 +93,9 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // we have a real scid, but the channel is not announced so alice uses bob's alias - assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) - assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) } @@ -124,9 +124,9 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // the channel is not announced so alice uses bob's alias (we have a no real scid anyway) - assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) - assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) } @@ -140,9 +140,9 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice, channelReadyNoAlias) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // edge case: we have neither a real scid nor an alias, we use a fake scid - assert(initialChannelUpdate.shortChannelId === ShortChannelId(0)) - assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + assert(initialChannelUpdate.shortChannelId == ShortChannelId(0)) + assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) } @@ -156,9 +156,9 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // we have a real scid, but it is not the final one (less than 6 confirmations) so alice uses bob's alias - assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) - assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) } @@ -171,9 +171,9 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // the channel is not announced, so alice uses bob's alias (we have a no real scid anyway) - assert(initialChannelUpdate.shortChannelId === channelReady.alias_opt.get) - assert(initialChannelUpdate.feeBaseMsat === relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths === relayFees.feeProportionalMillionths) + assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) + assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) + assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) bob2alice.expectNoMessage(200 millis) awaitCond(alice.stateName == NORMAL) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 7eac225155..115c2482fa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -489,11 +489,11 @@ class RouterSpec extends BaseRouterSpec { val preComputedRoute = PredefinedChannelRoute(g, Seq(alias_ag_private)) sender.send(router, FinalizeRoute(10000 msat, preComputedRoute)) val response = sender.expectMsgType[RouteResponse] - assert(response.routes.length === 1) + assert(response.routes.length == 1) val route = response.routes.head - assert(route.hops.map(_.params) === Seq(ChannelRelayParams.FromAnnouncement(update_ag_private))) - assert(route.hops.head.nodeId === a) - assert(route.hops.head.nextNodeId === g) + assert(route.hops.map(_.params) == Seq(ChannelRelayParams.FromAnnouncement(update_ag_private))) + assert(route.hops.head.nodeId == a) + assert(route.hops.head.nextNodeId == g) } { // using the real scid diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index d9d33d81b9..cf5ab464c5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -254,8 +254,8 @@ class ChannelCodecs3Spec extends AnyFunSuite { { val bin = hex"0001000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae00000000000000075bcd1541dd93fe76288e183498a7d57065cf472a4413c643730f9c117ca806be6b57f75303a153d48f51439f168ca85ef2fd6f3e0642fc7132f7a530cd50d880ec05cb4c17" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY] - assert(data.localAlias === ShortChannelId(123456789L)) - assert(data.realShortChannelId_opt === Some(ShortChannelId(123456789L))) + assert(data.localAlias == ShortChannelId(123456789L)) + assert(data.realShortChannelId_opt == Some(ShortChannelId(123456789L))) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0010")) // NB: 01 -> 10 } @@ -263,8 +263,8 @@ class ChannelCodecs3Spec extends AnyFunSuite { { val bin = hex"0007000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000000000000000002aff008821c93fbf280b2391826bc70ae858cf815d4afc1816f85445364f188e635d4ae64dfe1c58bedb017dd6f267452444d991b66fcfc638396f72fa6926f69d6125ff01010101010101010101010101010101010101010101010101010101010101010000000000022cd962975eec0101002a000000000000000f0000023f0000003500000003e8000000000000" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] - assert(data.localAlias === ShortChannelId(42)) - assert(data.realShortChannelId_opt === Some(ShortChannelId(42))) + assert(data.localAlias == ShortChannelId(42)) + assert(data.realShortChannelId_opt == Some(ShortChannelId(42))) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0009")) } From 7a5a3df6777557b92f5635535eff038efb0a0782 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 13:23:03 +0200 Subject: [PATCH 19/55] fix rebase --- .../states/ChannelStateTestsHelperMethods.scala | 1 - .../basic/fixtures/MinimalNodeFixture.scala | 17 +++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 2c254e09a2..45c4faca93 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -225,7 +225,6 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] assert(bob2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId != ByteVector32.Zeroes) bob2blockchain.expectMsgType[WatchFundingSpent] - bob2blockchain.expectMsgType[WatchFundingConfirmed] val bobWatchFundingConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] eventually(assert(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)) val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 9962d5972b..57982029f3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -22,7 +22,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator import fr.acinq.eclair.payment.{Bolt11Invoice, PaymentSent} import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.IPAddress -import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, ShortChannelId, TestBitcoinCoreClient, TestDatabases, TestFeeEstimator} +import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TestBitcoinCoreClient, TestDatabases, TestFeeEstimator} import org.scalatest.Assertions import org.scalatest.concurrent.Eventually.eventually @@ -152,7 +152,7 @@ object MinimalNodeFixture extends Assertions { node.wallet.funded(fundingTxid) } - def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): ShortChannelId = { + def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealShortChannelId = { assert(getChannelState(node1, channelId) == WAIT_FOR_FUNDING_CONFIRMED) val data1Before = getChannelData(node1, channelId).asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] val fundingTx = data1Before.fundingTx.get @@ -170,13 +170,14 @@ object MinimalNodeFixture extends Assertions { val data1After = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val data2After = getChannelData(node2, channelId).asInstanceOf[DATA_NORMAL] - assert(data1After.shortChannelId == data2After.shortChannelId) + assert(data1After.realShortChannelId_opt.isDefined && data2After.realShortChannelId_opt.isDefined) + assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) assert(!data1After.buried && !data2After.buried) - data1After.shortChannelId + data1After.realShortChannelId_opt.get } - def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): ShortChannelId = { + def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealShortChannelId = { assert(getChannelState(node1, channelId) == NORMAL) val data1Before = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val fundingTxid = data1Before.commitments.commitInput.outPoint.txid @@ -193,10 +194,10 @@ object MinimalNodeFixture extends Assertions { val data1After = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val data2After = getChannelData(node2, channelId).asInstanceOf[DATA_NORMAL] - assert(data1After.shortChannelId == data2After.shortChannelId) + assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) assert(data1After.buried && data2After.buried) - data1After.shortChannelId + data1After.realShortChannelId_opt.get } /** Utility method to make sure that the channel has processed all previous messages */ @@ -242,7 +243,7 @@ object MinimalNodeFixture extends Assertions { } /** An autopilot method for the watcher, that handles channel validation requests from the router */ - def autoValidatePublicChannels(fundingTxs: Map[ShortChannelId, Transaction]): TestActor.AutoPilot = (_, msg: Any) => msg match { + def autoValidatePublicChannels(fundingTxs: Map[RealShortChannelId, Transaction]): TestActor.AutoPilot = (_, msg: Any) => msg match { case vr: ZmqWatcher.ValidateRequest => val res = Try(fundingTxs(vr.ann.shortChannelId), ZmqWatcher.UtxoStatus.Unspent).toEither vr.replyTo ! ZmqWatcher.ValidateResult(vr.ann, res) From a9e572f968e0443a9b440494a5e96523d1fc0877 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 14:48:13 +0200 Subject: [PATCH 20/55] fix bug with graph update when channel graduates from private to public --- .../fr/acinq/eclair/router/Validation.scala | 22 ++++++++++++++++--- .../router/ChannelRouterIntegrationSpec.scala | 14 ++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 5a5ef4739f..6d38352954 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -166,8 +166,23 @@ object Validation { private def addPublicChannel(d: Data, nodeParams: NodeParams, pc: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors log.debug("adding public channel channelId={} realScid={}", pc.channelId, pc.shortChannelId) + val graph1 = d.privateChannels.get(pc.channelId) match { + case Some(privateChannel) => + // we need to update the graph because the edge identifiers change from alias to real scid + log.debug("updating the graph") + // mutable variable is simpler here + var graph = d.graphWithBalances + // remove previous private edges + pc.update_1_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) + pc.update_2_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) + // add new public edges + pc.update_1_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pc))) + pc.update_2_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pc))) + graph + case None => d.graphWithBalances + } // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid - val updates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) + val rebroadcastUpdates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) .filter(_.shortChannelId == pc.shortChannelId) .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) .toMap @@ -180,8 +195,9 @@ object Validation { // we rebroadcast the channel to our peers channels = d.rebroadcast.channels + (pc.ann -> d.awaiting.getOrElse(pc.ann, if (pc.nodeId1 == nodeParams.nodeId || pc.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet), // those updates are only defined if the channel is was previously an unannounced local channel, we broadcast them - updates = d.rebroadcast.updates ++ updates1 - ) + updates = d.rebroadcast.updates ++ rebroadcastUpdates1 + ), + graphWithBalances = graph1 ) // in case this was our first local channel, we make a node announcement if (!d.nodes.contains(nodeParams.nodeId)) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 75d6d7e959..be9868b03a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -6,6 +6,7 @@ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingDeeplyBuriedTr import fr.acinq.eclair.channel.DATA_NORMAL import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.io.Peer.PeerRoutingMessage +import fr.acinq.eclair.router.Graph.GraphStructure.GraphEdge import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate} import fr.acinq.eclair.{BlockHeight, TestKitBaseClass} @@ -84,6 +85,10 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // alice and bob won't send their channel_update directly to each other because they haven't changed channels.alice2bob.expectNoMessage(100 millis) channels.bob2alice.expectNoMessage(100 millis) + + // router graph contains a single channel + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) } test("public local channel", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => @@ -128,6 +133,10 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // there is nothing for the router to rebroadcast, channel is not announced assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) + // router graph contains a single channel + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) + // funding tx reaches 6 blocks, announcements are exchanged channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) channels.alice2bob.expectMsgType[AnnouncementSignatures] @@ -180,6 +189,11 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) ) + + // router graph contains a single channel + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.edgeSet().size == 2) + assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel))) } } From ecbcb58a735ca547c481db069261290828d1a774 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 15:03:31 +0200 Subject: [PATCH 21/55] factor channel-router integration test --- .../router/ChannelRouterIntegrationSpec.scala | 198 ++++++++---------- 1 file changed, 88 insertions(+), 110 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index be9868b03a..e848ab3e5f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -42,7 +42,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu withFixture(test.toNoArgTest(FixtureParam(router, rebroadcastListener, channels, test.tags))) } - test("private local channel") { f => + private def internalTest(f: FixtureParam): Unit = { import f._ reachNormal(channels, testTags, interceptChannelUpdates = false) @@ -53,8 +53,8 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // only alice's channel_update is known (NB : due to how node ids are constructed, 1 = alice and 2 = bob) assert(privateChannel.update_1_opt.isDefined) assert(privateChannel.update_2_opt.isEmpty) - // alice already has a real scid because this is not a zeroconf channel - assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + // alice will only have a real scid if this is not a zeroconf channel + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty == f.testTags.contains(ChannelStateTestsTags.ZeroConf)) assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.isDefined) // alice uses bob's alias for her channel update assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.localAlias) @@ -78,122 +78,100 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // there is nothing for the router to rebroadcast, channel is not announced assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) - // funding tx reaches 6 blocks, no announcements are exchanged because the channel is private - channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - - // alice and bob won't send their channel_update directly to each other because they haven't changed - channels.alice2bob.expectNoMessage(100 millis) - channels.bob2alice.expectNoMessage(100 millis) - // router graph contains a single channel assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) - } - - test("public local channel", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => - import f._ - - reachNormal(channels, testTags, interceptChannelUpdates = false) - - //@formatter:off - /** there is only one channel here */ - def privateChannel: PrivateChannel = router.stateData.privateChannels.values.head - def publicChannel: PublicChannel = router.stateData.channels.values.head - //@formatter:on - - // the router learns about the local, still unannounced, channel - awaitCond(router.stateData.privateChannels.size == 1) - - // only alice's channel_update is known (NB : due to how node ids are constructed, 1 = alice and 2 = bob) - assert(privateChannel.update_1_opt.isDefined) - assert(privateChannel.update_2_opt.isEmpty) - // alice already has a real scid because this is not a zeroconf channel - assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.isDefined) - // alice uses bob's alias for her channel update - assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.localAlias) - assert(privateChannel.update_1_opt.get.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.get) - - // alice and bob send their channel_updates using remote alias when they go to NORMAL state - val aliceChannelUpdate1 = channels.alice2bob.expectMsgType[ChannelUpdate] - val bobChannelUpdate1 = channels.bob2alice.expectMsgType[ChannelUpdate] - // alice's channel_update uses bob's alias, and vice versa - assert(aliceChannelUpdate1.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].localAlias) - assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].localAlias) - // channel_updates are handled by the peer connection and sent to the router - val peerConnection = TestProbe() - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) - // router processes bob's channel_update and now knows both channel updates - awaitCond { - privateChannel.update_1_opt.contains(aliceChannelUpdate1) && privateChannel.update_2_opt.contains(bobChannelUpdate1) + if (testTags.contains(ChannelStateTestsTags.ChannelsPublic)) { + // this is a public channel + // funding tx reaches 6 blocks, announcements are exchanged + channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + channels.alice2bob.expectMsgType[AnnouncementSignatures] + channels.alice2bob.forward(channels.bob) + + channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + channels.bob2alice.expectMsgType[AnnouncementSignatures] + channels.bob2alice.forward(channels.alice) + + // the router learns about the announcement and channel graduates from private to public + awaitCond { + router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 + } + + // alice and bob won't send their channel_update directly to each other because the channel has been announced + // but we can get the update from their data + awaitCond { + channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined && + channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined + } + val aliceChannelUpdate2 = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val bobChannelUpdate2 = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + // this time, they use the real scid + assert(aliceChannelUpdate2.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) + assert(bobChannelUpdate2.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) + + // the router has already processed the new local channel update from alice which uses the real scid, and keeps bob's previous channel update + assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1)) + + // the router prepares to rebroadcast the channel announcement, the local update which use the real scid, and the first node announcement + assert(router.stateData.rebroadcast == Rebroadcast( + channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), + updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)), + nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + ) + + // bob's channel_update reaches the router + router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate2) + + // router processes bob's channel_update and now knows both channel updates with real scids + awaitCond { + publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate2) + } + + // router is now ready to rebroadcast both channel updates + assert(router.stateData.rebroadcast == Rebroadcast( + channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), + updates = Map( + aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip), + bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), + nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) + ) + + // router graph contains a single channel + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.edgeSet().size == 2) + assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel))) + } else { + // this is a private channel + + // funding tx reaches 6 blocks, no announcements are exchanged because the channel is private + channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) + + // alice and bob won't send their channel_update directly to each other because they haven't changed + channels.alice2bob.expectNoMessage(100 millis) + channels.bob2alice.expectNoMessage(100 millis) + + // router graph contains a single channel + assert(router.stateData.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) } + } - // there is nothing for the router to rebroadcast, channel is not announced - assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) - - // router graph contains a single channel - assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) - assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) - - // funding tx reaches 6 blocks, announcements are exchanged - channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - channels.alice2bob.expectMsgType[AnnouncementSignatures] - channels.alice2bob.forward(channels.bob) - - channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) - channels.bob2alice.expectMsgType[AnnouncementSignatures] - channels.bob2alice.forward(channels.alice) - - // the router learns about the announcement and channel graduates from private to public - awaitCond { - router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 - } + test("private local channel") { f => + internalTest(f) + } - // alice and bob won't send their channel_update directly to each other because the channel has been announced - // but we can get the update from their data - awaitCond { - channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined && - channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.isDefined - } - val aliceChannelUpdate2 = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - val bobChannelUpdate2 = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - // this time, they use the real scid - assert(aliceChannelUpdate2.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) - assert(bobChannelUpdate2.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) - - // the router has already processed the new local channel update from alice which uses the real scid, and keeps bob's previous channel update - assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1)) - - // the router prepares to rebroadcast the channel announcement, the local update which use the real scid, and the first node announcement - assert(router.stateData.rebroadcast == Rebroadcast( - channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), - updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)), - nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) - ) - - // bob's channel_update reaches the router - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate2) - - // router processes bob's channel_update and now knows both channel updates with real scids - awaitCond { - publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate2) - } + test("private local channel (zeroconf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + internalTest(f) + } - // router is now ready to rebroadcast both channel updates - assert(router.stateData.rebroadcast == Rebroadcast( - channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), - updates = Map( - aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip), - bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), - nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) - ) + test("public local channel", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => + internalTest(f) + } - // router graph contains a single channel - assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) - assert(router.stateData.graphWithBalances.graph.edgeSet().size == 2) - assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel))) + test("public local channel (zeroconf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + internalTest(f) } } From c1fabe791ce9af763c431af0029ab9db93899557 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 3 Jun 2022 17:15:00 +0200 Subject: [PATCH 22/55] add integration tests for zero-conf --- .../eclair/payment/relay/ChannelRelay.scala | 3 +- .../eclair/payment/relay/ChannelRelayer.scala | 9 +- .../basic/ThreeNodesIntegrationSpec.scala | 28 +- .../basic/TwoNodesIntegrationSpec.scala | 8 +- .../basic/ZeroConfAliasIntegrationSpec.scala | 397 ++++++++++++++++++ .../basic/fixtures/MinimalNodeFixture.scala | 76 ++-- 6 files changed, 467 insertions(+), 54 deletions(-) create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala index bcec91b1d0..3475b97f74 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala @@ -52,7 +52,8 @@ object ChannelRelay { Behaviors.withMdc(Logs.mdc( category_opt = Some(Logs.LogCategory.PAYMENT), parentPaymentId_opt = Some(relayId), // for a channel relay, parent payment id = relay id - paymentHash_opt = Some(r.add.paymentHash))) { + paymentHash_opt = Some(r.add.paymentHash), + nodeAlias_opt = Some(nodeParams.alias))) { context.self ! DoRelay new ChannelRelay(nodeParams, register, channels, r, context).relay(Seq.empty) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala index 2275e8ea56..486944c2f5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala @@ -50,6 +50,9 @@ object ChannelRelayer { def mdc: Command => Map[String, String] = { case c: Relay => Logs.mdc(paymentHash_opt = Some(c.channelRelayPacket.add.paymentHash)) + case c: WrappedLocalChannelUpdate => Logs.mdc(channelId_opt = Some(c.localChannelUpdate.channelId)) + case c: WrappedLocalChannelDown => Logs.mdc(channelId_opt = Some(c.localChannelDown.channelId)) + case c: WrappedAvailableBalanceChanged => Logs.mdc(channelId_opt = Some(c.availableBalanceChanged.channelId)) case _ => Map.empty } @@ -63,7 +66,7 @@ object ChannelRelayer { context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[LocalChannelDown](WrappedLocalChannelDown)) context.system.eventStream ! EventStream.Subscribe(context.messageAdapter[AvailableBalanceChanged](WrappedAvailableBalanceChanged)) context.messageAdapter[IncomingPaymentPacket.ChannelRelayPacket](Relay) - Behaviors.withMdc(Logs.mdc(category_opt = Some(Logs.LogCategory.PAYMENT)), mdc) { + Behaviors.withMdc(Logs.mdc(category_opt = Some(Logs.LogCategory.PAYMENT), nodeAlias_opt = Some(nodeParams.alias)), mdc) { Behaviors.receiveMessage { case Relay(channelRelayPacket) => val relayId = UUID.randomUUID() @@ -88,8 +91,8 @@ object ChannelRelayer { replyTo ! Relayer.OutgoingChannels(selected.toSeq) Behaviors.same - case WrappedLocalChannelUpdate(lcu@LocalChannelUpdate(_, channelId, _, localAlias, remoteNodeId, _, channelUpdate, commitments)) => - context.log.debug(s"updating local channel info for channelId=$channelId localAlias=$localAlias remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) + case WrappedLocalChannelUpdate(lcu@LocalChannelUpdate(_, channelId, realShortChannelId_opt, localAlias, remoteNodeId, _, channelUpdate, commitments)) => + context.log.debug(s"updating local channel info for channelId=$channelId realScid=$realShortChannelId_opt localAlias=$localAlias remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) val prevChannelUpdate = channels.get(channelId).map(_.channelUpdate) val channel = Relayer.OutgoingChannel(remoteNodeId, channelUpdate, prevChannelUpdate, commitments) val channels1 = channels + (channelId -> channel) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala index f6c2eedcc4..a1bbda4deb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala @@ -1,10 +1,9 @@ package fr.acinq.eclair.integration.basic import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} -import fr.acinq.eclair.ShortChannelId.txIndex +import fr.acinq.eclair.MilliSatoshiLong import fr.acinq.eclair.integration.basic.fixtures.ThreeNodesFixture import fr.acinq.eclair.testutils.FixtureSpec -import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong} import org.scalatest.TestData import org.scalatest.concurrent.IntegrationPatience import scodec.bits.HexStringSyntax @@ -36,26 +35,13 @@ class ThreeNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { connect(alice, bob) connect(bob, carol) - val channelIdAB = openChannel(alice, bob, 100_000 sat).channelId - val channelIdBC = openChannel(bob, carol, 100_000 sat).channelId + // we put watchers on auto pilot to confirm funding txs + alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol))) + bob.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol))) + carol.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol))) - val fundingTxAB = fundingTx(alice, channelIdAB) - val fundingTxBC = fundingTx(bob, channelIdBC) - - val shortIdAB = confirmChannel(alice, bob, channelIdAB, BlockHeight(420_000), 21) - val shortIdBC = confirmChannel(bob, carol, channelIdBC, BlockHeight(420_001), 22) - - val fundingTxs = Map( - shortIdAB -> fundingTxAB, - shortIdBC -> fundingTxBC - ) - - // auto-validate channel announcements - alice.watcher.setAutoPilot(autoValidatePublicChannels(fundingTxs)) - bob.watcher.setAutoPilot(autoValidatePublicChannels(fundingTxs)) - - confirmChannelDeep(alice, bob, channelIdAB, shortIdAB.blockHeight, txIndex(shortIdAB)) - confirmChannelDeep(bob, carol, channelIdBC, shortIdBC.blockHeight, txIndex(shortIdBC)) + openChannel(alice, bob, 100_000 sat).channelId + openChannel(bob, carol, 100_000 sat).channelId // alice now knows about bob-carol eventually { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala index ae2ab79092..707317fcd3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala @@ -45,8 +45,8 @@ class TwoNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { test("open a channel alice-bob (autoconfirm)") { f => import f._ - alice.watcher.setAutoPilot(autoConfirmLocalChannels(alice.wallet.funded)) - bob.watcher.setAutoPilot(autoConfirmLocalChannels(alice.wallet.funded)) + alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice))) + bob.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice))) connect(alice, bob) val channelId = openChannel(alice, bob, 100_000 sat).channelId eventually { @@ -67,8 +67,8 @@ class TwoNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { test("open a channel alice-bob and confirm deeply (autoconfirm)") { f => import f._ - alice.watcher.setAutoPilot(autoConfirmLocalChannels(alice.wallet.funded)) - bob.watcher.setAutoPilot(autoConfirmLocalChannels(alice.wallet.funded)) + alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice))) + bob.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice))) connect(alice, bob) val channelId = openChannel(alice, bob, 100_000 sat).channelId eventually { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala new file mode 100644 index 0000000000..3514f33357 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -0,0 +1,397 @@ +package fr.acinq.eclair.integration.basic + +import com.softwaremill.quicklens._ +import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} +import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} +import fr.acinq.eclair.Features.{ScidAlias, ZeroConf} +import fr.acinq.eclair.channel.DATA_NORMAL +import fr.acinq.eclair.integration.basic.fixtures.ThreeNodesFixture +import fr.acinq.eclair.payment.PaymentSent +import fr.acinq.eclair.testutils.FixtureSpec +import fr.acinq.eclair.wire.protocol.{ChannelReady, ChannelReadyTlv} +import fr.acinq.eclair.{MilliSatoshiLong, RealShortChannelId} +import org.scalatest.concurrent.IntegrationPatience +import org.scalatest.{Tag, TestData} +import scodec.bits.HexStringSyntax + +class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience { + + type FixtureParam = ThreeNodesFixture + + val ZeroConfBobCarol = "zeroconf_bob_carol" + val ScidAliasBobCarol = "scid_alias_bob_carol" + val PublicBobCarol = "public_bob_carol" + + import fr.acinq.eclair.integration.basic.fixtures.MinimalNodeFixture._ + + override def createFixture(testData: TestData): FixtureParam = { + // seeds have been chosen so that node ids start with 02aaaa for alice, 02bbbb for bob, etc. + val aliceParams = nodeParamsFor("alice", ByteVector32(hex"b4acd47335b25ab7b84b8c020997b12018592bb4631b868762154d77fa8b93a3")) + val bobParams = nodeParamsFor("bob", ByteVector32(hex"7620226fec887b0b2ebe76492e5a3fd3eb0e47cd3773263f6a81b59a704dc492")) + .modify(_.features.activated).using(_ - ZeroConf - ScidAlias) // we will enable those features on demand + .modify(_.features.activated).usingIf(testData.tags.contains(ZeroConfBobCarol))(_ + (ZeroConf -> Optional)) + .modify(_.features.activated).usingIf(testData.tags.contains(ScidAliasBobCarol))(_ + (ScidAlias -> Optional)) + .modify(_.channelConf.channelFlags.announceChannel).setTo(testData.tags.contains(PublicBobCarol)) + val carolParams = nodeParamsFor("carol", ByteVector32(hex"ebd5a5d3abfb3ef73731eb3418d918f247445183180522674666db98a66411cc")) + .modify(_.features.activated).using(_ - ZeroConf - ScidAlias) // we will enable those features on demand + .modify(_.features.activated).usingIf(testData.tags.contains(ZeroConfBobCarol))(_ + (ZeroConf -> Mandatory)) + .modify(_.features.activated).usingIf(testData.tags.contains(ScidAliasBobCarol))(_ + (ScidAlias -> Mandatory)) + .modify(_.channelConf.channelFlags.announceChannel).setTo(testData.tags.contains(PublicBobCarol)) + ThreeNodesFixture(aliceParams, bobParams, carolParams) + } + + override def cleanupFixture(fixture: FixtureParam): Unit = { + fixture.cleanup() + } + + private def createChannels(f: FixtureParam)(deepConfirm: Boolean, stripAliasFromCarol: Boolean = false): (ByteVector32, ByteVector32) = { + import f._ + + alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol), deepConfirm = deepConfirm)) + bob.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol), deepConfirm = deepConfirm)) + carol.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol), deepConfirm = deepConfirm)) + + connect(alice, bob) + connect(bob, carol, mutate21 = { + case channelReady: ChannelReady if stripAliasFromCarol => + channelReady.modify(_.tlvStream.records).using(_.filter { case _: ChannelReadyTlv.ShortChannelIdTlv => false; case _ => true }) + case other => other + }) + + val channelId_ab = openChannel(alice, bob, 100_000 sat).channelId + val channelId_bc = openChannel(bob, carol, 100_000 sat).channelId + + (channelId_ab, channelId_bc) + } + + private def sendPaymentAliceToCarol(f: FixtureParam, useHint: Boolean = false, overrideHintScid_opt: Option[RealShortChannelId] = None): PaymentSent = { + import f._ + val hint = if (useHint) { + val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop + Seq(hint.modify(_.shortChannelId).setToIfDefined(overrideHintScid_opt)) + } else Seq.empty + sendPayment(alice, carol, 100_000 msat, hints = Seq(hint)) + } + + test("a->b->c (b-c private)") { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + + eventually { + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases + assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == + getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + + // if alice uses the real scid instead of the b-c alias, it still works + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + + test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + + eventually { + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases + assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == + getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + + // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + } + + test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // a-b is in NORMAL, the funding tx has reach min_depth + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + } + + test("a->b->c (b-c zero-conf unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false, stripAliasFromCarol = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // a-b is in NORMAL, the funding tx has reach min_depth + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // carol don't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel + assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) + } + + test("a->b->c (b-c zero-conf scid-alias unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false, stripAliasFromCarol = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // a-b is in NORMAL, the funding tx has reach min_depth + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // carol don't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel + assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) + } + + test("a->b->c (b-c zero-conf deeply confirmed private)", Tag(ZeroConfBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // both channels have real scids because they are deeply confirmed, even the zeroconf channel + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + + // if alice uses the real scid instead of the b-c alias, it still works + // TODO This actually doesn't work, because the ChannelRelayer relies on the the LocalChannelUpdate event to maintain + // TODO its scid resolution map, and the channel doesn't emit a new one when a real scid is assigned, because we use the + // TODO remote alias for the channel_update, not the real scid. So the channel_update remains the same. We used to + // TODO have the ChannelRelayer also listen to ShortChannelIdAssigned event, but it's doesn't seem worth it here. + //sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + + test("a->b->c (b-c zero-conf scid-alias deeply confirmed private)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) + // both channels have real scids because they are deeply confirmed, even the zeroconf channel + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + + // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + } + + test("a->b->c (b-c zero-conf deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true, stripAliasFromCarol = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + // both channels have real scids because they are deeply confirmed, even the zeroconf channel + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid + val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop + // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it midflight, + // she will still understand bob's alias and use it in her routing hint + assert(hint.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + + // if alice uses the real scid instead of the b-c alias, it still works + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + + test("a->b->c (b-c zero-conf scid-alias deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true, stripAliasFromCarol = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + // both channels have real scids because they are deeply confirmed, even the zeroconf channel + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + eventually { + // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid + val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop + // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it midflight, + // she will still understand bob's alias and use it in her routing hint + assert(hint.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + } + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + + // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + } + + test("a->b->c (b-c zero-conf unconfirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + // a-b is in NORMAL, the funding tx has reach min_depth + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + } + + // alice can't find a route to carol because b-c isn't announced + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } + + // with a routing hint the payment works + sendPaymentAliceToCarol(f, useHint = true) + } + + test("a->b->c (b-c zero-conf deeply confirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => + import f._ + + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) + + eventually { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) + assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) + // both channels have real scids because they are deeply confirmed, even the zeroconf channel + assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + } + + // alice eventually learns about public channel b-c + eventually { + assert(getRouterData(alice).channels.size == 2) + } + + // alice can make a payment to carol without routing hints + sendPaymentAliceToCarol(f) + } + +} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 57982029f3..e590cb46e2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -6,6 +6,7 @@ import akka.testkit.{TestActor, TestProbe} import com.softwaremill.quicklens.ModifyPimp import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, Transaction} +import fr.acinq.eclair.ShortChannelId.txIndex import fr.acinq.eclair.blockchain.DummyOnChainWallet import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{WatchFundingConfirmed, WatchFundingConfirmedTriggered, WatchFundingDeeplyBuried, WatchFundingDeeplyBuriedTriggered} @@ -16,6 +17,7 @@ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager} import fr.acinq.eclair.io.PeerConnection.ConnectionResult import fr.acinq.eclair.io.{Peer, PeerConnection, Switchboard} +import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop import fr.acinq.eclair.payment.receive.{MultiPartHandler, PaymentHandler} import fr.acinq.eclair.payment.relay.Relayer import fr.acinq.eclair.payment.send.PaymentInitiator @@ -97,7 +99,7 @@ object MinimalNodeFixture extends Assertions { ) } - def connect(node1: MinimalNodeFixture, node2: MinimalNodeFixture)(implicit system: ActorSystem): ConnectionResult.Connected = { + def connect(node1: MinimalNodeFixture, node2: MinimalNodeFixture, mutate12: Any => Any = identity, mutate21: Any => Any = identity)(implicit system: ActorSystem): ConnectionResult.Connected = { val sender = TestProbe("sender") val connection1 = TestProbe("connection-1")(node1.system) @@ -114,7 +116,7 @@ object MinimalNodeFixture extends Assertions { case _: TransportHandler.Listener => TestActor.KeepRunning case _: TransportHandler.ReadAck => TestActor.KeepRunning case _ => - peerConnection2.tell(msg, transport2.ref) + peerConnection2.tell(mutate12(msg), transport2.ref) TestActor.KeepRunning } } @@ -124,7 +126,7 @@ object MinimalNodeFixture extends Assertions { case _: TransportHandler.Listener => TestActor.KeepRunning case _: TransportHandler.ReadAck => TestActor.KeepRunning case _ => - peerConnection1.tell(msg, transport1.ref) + peerConnection1.tell(mutate21(msg), transport1.ref) TestActor.KeepRunning } } @@ -152,7 +154,7 @@ object MinimalNodeFixture extends Assertions { node.wallet.funded(fundingTxid) } - def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealShortChannelId = { + def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): Option[RealShortChannelId] = { assert(getChannelState(node1, channelId) == WAIT_FOR_FUNDING_CONFIRMED) val data1Before = getChannelData(node1, channelId).asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] val fundingTx = data1Before.fundingTx.get @@ -170,11 +172,17 @@ object MinimalNodeFixture extends Assertions { val data1After = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val data2After = getChannelData(node2, channelId).asInstanceOf[DATA_NORMAL] - assert(data1After.realShortChannelId_opt.isDefined && data2After.realShortChannelId_opt.isDefined) - assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) - assert(!data1After.buried && !data2After.buried) - data1After.realShortChannelId_opt.get + if ((blockHeight, txIndex) == (BlockHeight(0), 0)) { + // special case for zero-conf channels, the watcher answers with a special 0/0 coordinates when the funding tx + // hasn't been confirmed yet and doesn't have a real scid + None + } else { + assert(data1After.realShortChannelId_opt.isDefined && data2After.realShortChannelId_opt.isDefined) + assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) + assert(!data1After.buried && !data2After.buried) + Some(data1After.realShortChannelId_opt.get) + } } def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealShortChannelId = { @@ -223,41 +231,59 @@ object MinimalNodeFixture extends Assertions { sender.expectMsgType[Router.Data] } - private def deterministicShortId(txId: ByteVector32): (BlockHeight, Int) = { + /** + * Computes a deterministic [[RealShortChannelId]] based on a txid. We need this so that watchers can verify + * transactions in a independent and stateless fashion, since there is no actual blockchain in those tests. + */ + def deterministicShortId(txId: ByteVector32): RealShortChannelId = { val blockHeight = txId.take(3).toInt(signed = false) val txIndex = txId.takeRight(2).toInt(signed = false) - (BlockHeight(blockHeight), txIndex) + val outputIndex = 0 // funding txs created by the dummy wallet used in tests only have one output + ShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex).toReal } - /** An autopilot method for the watcher, that handled funding confirmation requests from the channel */ - def autoConfirmLocalChannels(fundingTxs: collection.concurrent.Map[ByteVector32, Transaction]): TestActor.AutoPilot = (_, msg) => msg match { + /** All known funding txs (we don't evaluate immediately because new ones could be created) */ + def knownFundingTxs(nodes: MinimalNodeFixture*): () => Iterable[Transaction] = () => nodes.map(_.wallet.funded.values).reduce(_ ++ _) + + /** + * An autopilot method for the watcher, that handled funding confirmation requests from the channel and channel + * validation requests from the router + */ + def watcherAutopilot(knownFundingTxs: () => Iterable[Transaction], deepConfirm: Boolean = true): TestActor.AutoPilot = (_, msg) => msg match { case watch: ZmqWatcher.WatchFundingConfirmed => - val (blockHeight, txIndex) = deterministicShortId(watch.txId) - watch.replyTo ! ZmqWatcher.WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTxs(watch.txId)) + val realScid = if (watch.minDepth == 0) { + // special case for zero-conf channels, this is what the real watcher does + ShortChannelId(BlockHeight(0), 0, 0).toReal + } else { + deterministicShortId(watch.txId) + } + val fundingTx = knownFundingTxs().find(_.txid == watch.txId) + .getOrElse(throw new RuntimeException(s"unknown fundingTxId=${watch.txId}, known=${knownFundingTxs().map(_.txid).mkString(",")}")) + watch.replyTo ! ZmqWatcher.WatchFundingConfirmedTriggered(realScid.blockHeight, txIndex(realScid), fundingTx) TestActor.KeepRunning - case watch: ZmqWatcher.WatchFundingDeeplyBuried => - val (blockHeight, txIndex) = deterministicShortId(watch.txId) - watch.replyTo ! ZmqWatcher.WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTxs(watch.txId)) + case watch: ZmqWatcher.WatchFundingDeeplyBuried if deepConfirm => + val realScid = deterministicShortId(watch.txId) + val fundingTx = knownFundingTxs().find(_.txid == watch.txId).get + watch.replyTo ! ZmqWatcher.WatchFundingDeeplyBuriedTriggered(realScid.blockHeight, txIndex(realScid), fundingTx) TestActor.KeepRunning - case _ => TestActor.KeepRunning - } - - /** An autopilot method for the watcher, that handles channel validation requests from the router */ - def autoValidatePublicChannels(fundingTxs: Map[RealShortChannelId, Transaction]): TestActor.AutoPilot = (_, msg: Any) => msg match { case vr: ZmqWatcher.ValidateRequest => - val res = Try(fundingTxs(vr.ann.shortChannelId), ZmqWatcher.UtxoStatus.Unspent).toEither + val res = Try { + val fundingTx = knownFundingTxs().find(tx => deterministicShortId(tx.txid) == vr.ann.shortChannelId) + .getOrElse(throw new RuntimeException(s"unknown realScid=${vr.ann.shortChannelId}, known=${knownFundingTxs().map(tx => deterministicShortId(tx.txid)).mkString(",")}")) + (fundingTx, ZmqWatcher.UtxoStatus.Unspent) + }.toEither vr.replyTo ! ZmqWatcher.ValidateResult(vr.ann, res) TestActor.KeepRunning case _ => TestActor.KeepRunning } - def sendPayment(node1: MinimalNodeFixture, node2: MinimalNodeFixture, amount: MilliSatoshi)(implicit system: ActorSystem): PaymentSent = { + def sendPayment(node1: MinimalNodeFixture, node2: MinimalNodeFixture, amount: MilliSatoshi, hints: Seq[Seq[ExtraHop]] = Seq.empty)(implicit system: ActorSystem): PaymentSent = { val sender = TestProbe("sender") sender.send(node2.paymentHandler, MultiPartHandler.ReceivePayment(Some(amount), Left("test payment"))) val invoice = sender.expectMsgType[Bolt11Invoice] val routeParams = node1.nodeParams.routerConf.pathFindingExperimentConf.experiments.values.head.getDefaultRouteParams - sender.send(node1.paymentInitiator, PaymentInitiator.SendPaymentToNode(amount, invoice, maxAttempts = 1, routeParams = routeParams, blockUntilComplete = true)) + sender.send(node1.paymentInitiator, PaymentInitiator.SendPaymentToNode(amount, invoice, maxAttempts = 1, routeParams = routeParams, assistedRoutes = hints, blockUntilComplete = true)) sender.expectMsgType[PaymentSent] } From 4d2553567dbb43cb71c7adbd805d066140b17aff Mon Sep 17 00:00:00 2001 From: pm47 Date: Sun, 5 Jun 2022 16:43:27 +0200 Subject: [PATCH 23/55] fix bug created by de2dee374: need to populate meta for graduating channels --- .../fr/acinq/eclair/router/Validation.scala | 77 ++++++++++--------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 6d38352954..b98d729459 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -19,8 +19,10 @@ package fr.acinq.eclair.router import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.{ActorContext, ActorRef, typed} import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} +import fr.acinq.eclair.ShortChannelId.outputIndex import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} import fr.acinq.eclair.channel._ @@ -31,7 +33,7 @@ import fr.acinq.eclair.router.Monitoring.Metrics import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates} +import fr.acinq.eclair.{Logs, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TxCoordinates, toLongId} object Validation { @@ -91,7 +93,7 @@ object Validation { val remoteOrigins = d0.awaiting.getOrElse(c, Set.empty).collect { case rg: RemoteGossip => rg } Logs.withMdc(log)(Logs.mdc(remoteNodeId_opt = remoteOrigins.headOption.map(_.nodeId))) { // in the MDC we use the node id that sent us the announcement first log.debug("got validation result for shortChannelId={} (awaiting={} stash.nodes={} stash.updates={})", c.shortChannelId, d0.awaiting.size, d0.stash.nodes.size, d0.stash.updates.size) - val publicChannel_opt = r match { + val d1_opt = r match { case ValidateResult(c, Left(t)) => log.warning("validation failure for shortChannelId={} reason={}", c.shortChannelId, t.getMessage) remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.ValidationFailure(c))) @@ -115,12 +117,7 @@ object Validation { val capacity = tx.txOut(outputIndex).amount ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(c, capacity, None, None) :: Nil)) db.addChannel(c, tx.txid, capacity) - Some(PublicChannel(c, - tx.txid, - capacity, - update_1_opt = None, - update_2_opt = None, - meta_opt = None)) + Some(addPublicChannel(d0, nodeParams, c, fundingTxid = tx.txid, capacity = capacity)) } case ValidateResult(c, Right((tx, fundingTxStatus: UtxoStatus.Spent))) => if (fundingTxStatus.spendingTxConfirmed) { @@ -143,17 +140,17 @@ object Validation { // we remove channel from awaiting map val awaiting1 = d0.awaiting - c - publicChannel_opt match { - case Some(publicChannel) => - val d1 = addPublicChannel(d0, nodeParams, publicChannel).copy(stash = stash1, awaiting = awaiting1) + d1_opt match { + case Some(d1) => + val d2 = d1.copy(stash = stash1, awaiting = awaiting1) // we process channel updates and node announcements if validation succeeded - val d2 = reprocessUpdates.foldLeft(d1) { + val d3 = reprocessUpdates.foldLeft(d2) { case (d, (u, origins)) => Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, origins)), wasStashed = true) } - val d3 = reprocessNodes.foldLeft(d2) { + val d4 = reprocessNodes.foldLeft(d3) { case (d, (n, origins)) => Validation.handleNodeAnnouncement(d, nodeParams.db.network, origins, n, wasStashed = true) } - d3 + d4 case None => // if validation failed we can fast-discard related announcements reprocessUpdates.foreach { case (u, origins) => origins.collect { case o: RemoteGossip => sendDecision(o.peerConnection, GossipDecision.NoRelatedChannel(u)) } } @@ -163,37 +160,49 @@ object Validation { } } - private def addPublicChannel(d: Data, nodeParams: NodeParams, pc: PublicChannel)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { + private def addPublicChannel(d: Data, nodeParams: NodeParams, ann: ChannelAnnouncement, fundingTxid: ByteVector32, capacity: Satoshi)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - log.debug("adding public channel channelId={} realScid={}", pc.channelId, pc.shortChannelId) - val graph1 = d.privateChannels.get(pc.channelId) match { + val channelId = toLongId(fundingTxid.reverse, outputIndex(ann.shortChannelId)) + // if this is a local channel graduating from private to public, we already have data + val privChan_opt = d.privateChannels.get(channelId) + log.debug("adding public channel channelId={} realScid={} localChannel={}", channelId, ann.shortChannelId, privChan_opt.isDefined) + val pubChan = PublicChannel( + ann = ann, + fundingTxid = fundingTxid, + capacity = capacity, + update_1_opt = privChan_opt.flatMap(_.update_1_opt), + update_2_opt = privChan_opt.flatMap(_.update_2_opt), + meta_opt = privChan_opt.map(_.meta) + ) + // if this is a local channel graduating from private to public, we need to update the graph because the edge + // identifiers change from alias to real scid, and we can also populate the metadata + val graph1 = d.privateChannels.get(pubChan.channelId) match { case Some(privateChannel) => - // we need to update the graph because the edge identifiers change from alias to real scid - log.debug("updating the graph") + log.debug("updating the graph for shortChannelId={}", pubChan.shortChannelId) // mutable variable is simpler here var graph = d.graphWithBalances // remove previous private edges - pc.update_1_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) - pc.update_2_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) + pubChan.update_1_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) + pubChan.update_2_opt.foreach(u => graph = graph.removeEdge(ChannelDesc(u, privateChannel))) // add new public edges - pc.update_1_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pc))) - pc.update_2_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pc))) + pubChan.update_1_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pubChan))) + pubChan.update_2_opt.foreach(u => graph = graph.addEdge(GraphEdge(u, pubChan))) graph case None => d.graphWithBalances } // those updates are only defined if this was a previously an unannounced local channel, we broadcast them if they use the real scid - val rebroadcastUpdates1 = (pc.update_1_opt.toSet ++ pc.update_2_opt.toSet) - .filter(_.shortChannelId == pc.shortChannelId) - .map(u => u -> (if (pc.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) + val rebroadcastUpdates1 = (pubChan.update_1_opt.toSet ++ pubChan.update_2_opt.toSet) + .filter(_.shortChannelId == pubChan.shortChannelId) + .map(u => u -> (if (pubChan.getNodeIdSameSideAs(u) == nodeParams.nodeId) Set[GossipOrigin](LocalGossip) else Set.empty[GossipOrigin])) .toMap val d1 = d.copy( - channels = d.channels + (pc.shortChannelId -> pc), + channels = d.channels + (pubChan.shortChannelId -> pubChan), // we remove the corresponding unannounced channel that we may have until now - privateChannels = d.privateChannels - pc.channelId, + privateChannels = d.privateChannels - pubChan.channelId, // we also add the newly validated channels to the rebroadcast queue rebroadcast = d.rebroadcast.copy( // we rebroadcast the channel to our peers - channels = d.rebroadcast.channels + (pc.ann -> d.awaiting.getOrElse(pc.ann, if (pc.nodeId1 == nodeParams.nodeId || pc.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet), + channels = d.rebroadcast.channels + (pubChan.ann -> d.awaiting.getOrElse(pubChan.ann, if (pubChan.nodeId1 == nodeParams.nodeId || pubChan.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet), // those updates are only defined if the channel is was previously an unannounced local channel, we broadcast them updates = d.rebroadcast.updates ++ rebroadcastUpdates1 ), @@ -482,15 +491,7 @@ object Validation { // since this is a local channel, we can trust the announcement, no need to go through the full // verification process and make calls to bitcoin core val commitments = lcu.commitments.asInstanceOf[Commitments] // TODO: ugly! a public channel has to have a real commitment - val publicChannel = PublicChannel( - ann = ann, - fundingTxid = commitments.commitInput.outPoint.txid, - capacity = commitments.capacity, - update_1_opt = privateChannel.update_1_opt, - update_2_opt = privateChannel.update_2_opt, - meta_opt = Some(privateChannel.meta) - ) - val d1 = addPublicChannel(d, nodeParams, publicChannel) + val d1 = addPublicChannel(d, nodeParams, ann, fundingTxid = commitments.commitInput.outPoint.txid, capacity = commitments.capacity) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) val d2 = handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) From 58de78c94cd47b7ee033bf587c90e078ef8b5485 Mon Sep 17 00:00:00 2001 From: pm47 Date: Sun, 5 Jun 2022 18:07:42 +0200 Subject: [PATCH 24/55] fix bug created by de2dee374: need to do watcher/db calls for local public channels too --- .../scala/fr/acinq/eclair/router/Router.scala | 2 +- .../fr/acinq/eclair/router/Validation.scala | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 45675ad257..a93a9c899a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -240,7 +240,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm stay() using Validation.handleChannelUpdate(d, nodeParams.db.network, nodeParams.routerConf, Right(RemoteChannelUpdate(u, Set(RemoteGossip(peerConnection, remoteNodeId))))) case Event(lcu: LocalChannelUpdate, d: Data) => // from local channel - stay() using Validation.handleLocalChannelUpdate(d, nodeParams, lcu) + stay() using Validation.handleLocalChannelUpdate(d, nodeParams, watcher, lcu) case Event(lcd: LocalChannelDown, d: Data) => stay() using Validation.handleLocalChannelDown(d, nodeParams.nodeId, lcd) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index b98d729459..bb8c38ac7a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -19,9 +19,9 @@ package fr.acinq.eclair.router import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.{ActorContext, ActorRef, typed} import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter} -import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} import fr.acinq.eclair.ShortChannelId.outputIndex import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} @@ -111,13 +111,10 @@ object Validation { remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.InvalidAnnouncement(c))) None } else { - watcher ! WatchExternalChannelSpent(ctx.self, tx.txid, outputIndex, c.shortChannelId) - log.debug("added channel channelId={}", c.shortChannelId) + log.debug("validation succesful for shortChannelId={}", c.shortChannelId) remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.Accepted(c))) val capacity = tx.txOut(outputIndex).amount - ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(c, capacity, None, None) :: Nil)) - db.addChannel(c, tx.txid, capacity) - Some(addPublicChannel(d0, nodeParams, c, fundingTxid = tx.txid, capacity = capacity)) + Some(addPublicChannel(d0, nodeParams, watcher, c, fundingTxid = tx.txid, capacity = capacity)) } case ValidateResult(c, Right((tx, fundingTxStatus: UtxoStatus.Spent))) => if (fundingTxStatus.spendingTxConfirmed) { @@ -160,9 +157,14 @@ object Validation { } } - private def addPublicChannel(d: Data, nodeParams: NodeParams, ann: ChannelAnnouncement, fundingTxid: ByteVector32, capacity: Satoshi)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { + private def addPublicChannel(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], ann: ChannelAnnouncement, fundingTxid: ByteVector32, capacity: Satoshi)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - val channelId = toLongId(fundingTxid.reverse, outputIndex(ann.shortChannelId)) + import nodeParams.db.{network => db} + val fundingOutputIndex = outputIndex(ann.shortChannelId) + val channelId = toLongId(fundingTxid.reverse, fundingOutputIndex) + watcher ! WatchExternalChannelSpent(ctx.self, fundingTxid, fundingOutputIndex, ann.shortChannelId) + ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(ann, capacity, None, None) :: Nil)) + db.addChannel(ann, fundingTxid, capacity) // if this is a local channel graduating from private to public, we already have data val privChan_opt = d.privateChannels.get(channelId) log.debug("adding public channel channelId={} realScid={} localChannel={}", channelId, ann.shortChannelId, privChan_opt.isDefined) @@ -477,7 +479,7 @@ object Validation { } } - def handleLocalChannelUpdate(d: Data, nodeParams: NodeParams, lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { + def handleLocalChannelUpdate(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors import nodeParams.db.{network => db} d.resolve(lcu.channelId, lcu.realShortChannelId_opt) match { @@ -491,7 +493,7 @@ object Validation { // since this is a local channel, we can trust the announcement, no need to go through the full // verification process and make calls to bitcoin core val commitments = lcu.commitments.asInstanceOf[Commitments] // TODO: ugly! a public channel has to have a real commitment - val d1 = addPublicChannel(d, nodeParams, ann, fundingTxid = commitments.commitInput.outPoint.txid, capacity = commitments.capacity) + val d1 = addPublicChannel(d, nodeParams, watcher, ann, fundingTxid = commitments.commitInput.outPoint.txid, capacity = commitments.capacity) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) val d2 = handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) From ca67ffc9a1473970e9f2129f3164c253917d3b01 Mon Sep 17 00:00:00 2001 From: pm47 Date: Sun, 5 Jun 2022 20:05:37 +0200 Subject: [PATCH 25/55] nit: comment formatting --- .../main/scala/fr/acinq/eclair/channel/fsm/Channel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 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 add4ee1b8f..af1d3d719b 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 @@ -1605,9 +1605,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val sealed trait EmitLocalChannelEvent /** * This event is for: - * - the router: so that it knows about this channel to find routes - * - the relayers: so they can learn about the channel scid/alias and route to it - * - the peer: so they can "learn the other end's forwarding parameters" (BOLT 7) + * - the router: so that it knows about this channel to find routes + * - the relayers: so they can learn about the channel scid/alias and route to it + * - the peer: so they can "learn the other end's forwarding parameters" (BOLT 7) * * @param sendToPeer indicates whether we should send the channel_update directly to our peer */ From 19d2943b4a261493908af9c06fb74d282659ab95 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 12:05:07 +0200 Subject: [PATCH 26/55] fix race condition in PaymentIntegrationTest --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 6 ++-- .../scala/fr/acinq/eclair/router/Router.scala | 15 ++++++---- .../fr/acinq/eclair/router/Validation.scala | 14 +++++++--- .../eclair/integration/IntegrationSpec.scala | 1 - .../integration/PaymentIntegrationSpec.scala | 28 ++++++++----------- 5 files changed, 35 insertions(+), 29 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 af1d3d719b..5ff0fc0303 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 @@ -1633,12 +1633,14 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val emitEvent_opt.foreach { case EmitLocalChannelUpdate(reason, d, sendToPeer) => log.info(s"emitting channel update: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.realShortChannelId_opt} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) - context.system.eventStream.publish(LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments)) + val lcu = LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments) + context.system.eventStream.publish(lcu) if (sendToPeer) { send(d.channelUpdate) } case EmitLocalChannelDown(d) => - context.system.eventStream.publish(LocalChannelDown(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId)) + val lcd = LocalChannelDown(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId) + context.system.eventStream.publish(lcd) } // When we change our channel update parameters (e.g. relay fees), we want to advertise it to other actors. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index a93a9c899a..90d9b84c91 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -279,13 +279,16 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm override def mdc(currentMessage: Any): MDC = { val category_opt = LogCategory(currentMessage) - val remoteNodeId_opt = currentMessage match { - case s: SendChannelQuery => Some(s.remoteNodeId) - case prm: PeerRoutingMessage => Some(prm.remoteNodeId) - case lcu: LocalChannelUpdate => Some(lcu.remoteNodeId) - case _ => None + val (remoteNodeId_opt, channelId_opt) = currentMessage match { + case s: SendChannelQuery => (Some(s.remoteNodeId), None) + case prm: PeerRoutingMessage => (Some(prm.remoteNodeId), None) + case sca: ShortChannelIdAssigned => (Some(sca.remoteNodeId), Some(sca.channelId)) + case lcu: LocalChannelUpdate => (Some(lcu.remoteNodeId), Some(lcu.channelId)) + case lcd: LocalChannelDown => (Some(lcd.remoteNodeId), Some(lcd.channelId)) + case abc: AvailableBalanceChanged => (Some(abc.commitments.remoteNodeId), Some(abc.channelId)) + case _ => (None, None) } - Logs.mdc(category_opt, remoteNodeId_opt = remoteNodeId_opt, nodeAlias_opt = Some(nodeParams.alias)) + Logs.mdc(category_opt, remoteNodeId_opt = remoteNodeId_opt, channelId_opt = channelId_opt, nodeAlias_opt = Some(nodeParams.alias)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index bb8c38ac7a..e195c24a61 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -111,7 +111,7 @@ object Validation { remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.InvalidAnnouncement(c))) None } else { - log.debug("validation succesful for shortChannelId={}", c.shortChannelId) + log.debug("validation successful for shortChannelId={}", c.shortChannelId) remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.Accepted(c))) val capacity = tx.txOut(outputIndex).amount Some(addPublicChannel(d0, nodeParams, watcher, c, fundingTxid = tx.txid, capacity = capacity)) @@ -167,7 +167,6 @@ object Validation { db.addChannel(ann, fundingTxid, capacity) // if this is a local channel graduating from private to public, we already have data val privChan_opt = d.privateChannels.get(channelId) - log.debug("adding public channel channelId={} realScid={} localChannel={}", channelId, ann.shortChannelId, privChan_opt.isDefined) val pubChan = PublicChannel( ann = ann, fundingTxid = fundingTxid, @@ -176,6 +175,7 @@ object Validation { update_2_opt = privChan_opt.flatMap(_.update_2_opt), meta_opt = privChan_opt.map(_.meta) ) + log.debug("adding public channel channelId={} realScid={} localChannel={} publicChannel={}", channelId, ann.shortChannelId, privChan_opt.isDefined, pubChan) // if this is a local channel graduating from private to public, we need to update the graph because the edge // identifiers change from alias to real scid, and we can also populate the metadata val graph1 = d.privateChannels.get(pubChan.channelId) match { @@ -211,7 +211,7 @@ object Validation { graphWithBalances = graph1 ) // in case this was our first local channel, we make a node announcement - if (!d.nodes.contains(nodeParams.nodeId)) { + if (!d.nodes.contains(nodeParams.nodeId) && isRelatedTo(ann, nodeParams.nodeId)) { log.info("first local channel validated, announcing local node") val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses, nodeParams.features.nodeAnnouncementFeatures()) handleNodeAnnouncement(d1, nodeParams.db.network, Set(LocalGossip), nodeAnn) @@ -465,6 +465,7 @@ object Validation { case Some(realScid) => Map(realScid -> scia.channelId, scia.localAlias -> scia.channelId) case None => Map(scia.localAlias -> scia.channelId) } + log.debug("handleShortChannelIdAssigned scia={} mappings={}", scia, mappings) val d1 = d.copy(scid2PrivateChannels = d.scid2PrivateChannels ++ mappings) d1.resolve(scia.channelId, scia.realShortChannelId_opt) match { case Some(_) => @@ -482,13 +483,16 @@ object Validation { def handleLocalChannelUpdate(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], lcu: LocalChannelUpdate)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors import nodeParams.db.{network => db} + log.debug("handleLocalChannelUpdate lcu={}", lcu) d.resolve(lcu.channelId, lcu.realShortChannelId_opt) match { - case Some(_: PublicChannel) => + case Some(publicChannel: PublicChannel) => // this a known public channel, we can process the channel_update + log.debug("this is a known public channel, processing channel_update publicChannel={}", publicChannel) handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) case Some(privateChannel: PrivateChannel) => lcu.channelAnnouncement_opt match { case Some(ann) => + log.debug("private channel graduating to public privateChannel={}", privateChannel) // channel is graduating from private to public // since this is a local channel, we can trust the announcement, no need to go through the full // verification process and make calls to bitcoin core @@ -496,9 +500,11 @@ object Validation { val d1 = addPublicChannel(d, nodeParams, watcher, ann, fundingTxid = commitments.commitInput.outPoint.txid, capacity = commitments.capacity) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) + log.debug("processing channel_update") val d2 = handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) d2 case None => + log.debug("this is a known private channel, processing channel_update privateChannel={}", privateChannel) // this is a known unannounced channel, we can process the channel_update handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index 0b540986b5..1e383811e0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -28,7 +28,6 @@ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Graph.WeightRatios import fr.acinq.eclair.router.RouteCalculation.ROUTE_MAX_LENGTH import fr.acinq.eclair.router.Router.{MultiPartParams, PathFindingConf, SearchBoundaries, NORMAL => _, State => _} -import fr.acinq.eclair.wire.protocol.NodeAddress import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Kit, MilliSatoshi, MilliSatoshiLong, Setup, TestKitBaseClass} import grizzled.slf4j.Logging import org.json4s.{DefaultFormats, Formats} diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 4db2f68f79..e1df7768a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -32,7 +32,6 @@ import fr.acinq.eclair.crypto.Sphinx.DecryptedFailurePacket import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.db._ import fr.acinq.eclair.io.Peer.PeerRoutingMessage -import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop import fr.acinq.eclair.payment._ import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment import fr.acinq.eclair.payment.relay.Relayer @@ -41,7 +40,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator.{SendPaymentToNode, SendTra import fr.acinq.eclair.router.Graph.WeightRatios import fr.acinq.eclair.router.Router.{GossipDecision, PublicChannel} import fr.acinq.eclair.router.{Announcements, AnnouncementsBatchValidationSpec, Router} -import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, IncorrectOrUnknownPaymentDetails, NodeAnnouncement} +import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate, IncorrectOrUnknownPaymentDetails} import fr.acinq.eclair.{CltvExpiryDelta, Features, Kit, MilliSatoshiLong, ShortChannelId, TimestampMilli, randomBytes32} import org.json4s.JsonAST.{JString, JValue} import scodec.bits.ByteVector @@ -114,22 +113,19 @@ class PaymentIntegrationSpec extends IntegrationSpec { } } - def awaitAnnouncements(subset: Map[String, Kit], nodes: Int, channels: Int, updates: Int): Unit = { + def awaitAnnouncements(subset: Map[String, Kit], nodes: Int, privateChannels: Int, publicChannels: Int, privateUpdates: Int, publicUpdates: Int): Unit = { val sender = TestProbe() subset.foreach { case (node, setup) => withClue(node) { awaitAssert({ - sender.send(setup.router, Router.GetNodes) - assert(sender.expectMsgType[Iterable[NodeAnnouncement]].size == nodes) - }, max = 10 seconds, interval = 1 second) - awaitAssert({ - sender.send(setup.router, Router.GetChannels) - sender.expectMsgType[Iterable[ChannelAnnouncement]].size == channels - }, max = 10 seconds, interval = 1 second) - awaitAssert({ - sender.send(setup.router, Router.GetChannelUpdates) - sender.expectMsgType[Iterable[ChannelUpdate]].size == updates + sender.send(setup.router, Router.GetRouterData) + val data = sender.expectMsgType[Router.Data] + assert(data.nodes.size == nodes) + assert(data.privateChannels.size == privateChannels) + assert(data.channels.size == publicChannels) + assert(data.privateChannels.values.flatMap(pc => pc.update_1_opt.toSeq ++ pc.update_2_opt.toSeq).size == privateUpdates) + assert(data.channels.values.flatMap(pc => pc.update_1_opt.toSeq ++ pc.update_2_opt.toSeq).size == publicUpdates) }, max = 10 seconds, interval = 1 second) } } @@ -141,8 +137,8 @@ class PaymentIntegrationSpec extends IntegrationSpec { // A requires private channels, as a consequence: // - only A and B know about channel A-B (and there is no channel_announcement) // - A is not announced (no node_announcement) - awaitAnnouncements(nodes.view.filterKeys(key => List("A", "B").contains(key)).toMap, 6, 8, 18) - awaitAnnouncements(nodes.view.filterKeys(key => List("C", "D", "E", "G").contains(key)).toMap, 6, 8, 16) + awaitAnnouncements(nodes.view.filterKeys(key => List("A", "B").contains(key)).toMap, nodes = 6, privateChannels = 1, publicChannels = 8, privateUpdates = 2, publicUpdates = 16) + awaitAnnouncements(nodes.view.filterKeys(key => List("C", "D", "E", "G").contains(key)).toMap, nodes = 6, privateChannels = 0, publicChannels = 8, privateUpdates = 0, publicUpdates = 16) } test("wait for channels balance") { @@ -153,7 +149,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { val routingState = sender.expectMsgType[Router.RoutingState] val publicChannels = routingState.channels.filter(pc => Set(pc.ann.nodeId1, pc.ann.nodeId2).contains(nodeId)) assert(publicChannels.nonEmpty) - publicChannels.foreach(pc => assert(pc.meta_opt.map(m => m.balance1 > 0.msat || m.balance2 > 0.msat) == Some(true), pc)) + publicChannels.foreach(pc => assert(pc.meta_opt.exists(m => m.balance1 > 0.msat || m.balance2 > 0.msat), pc)) } test("send an HTLC A->D") { From 9743113af258ea1a2e43637008aad06359782883 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 13:21:48 +0200 Subject: [PATCH 27/55] update scala plugins --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 48f3e25e5f..adb6006aea 100644 --- a/pom.xml +++ b/pom.xml @@ -132,7 +132,7 @@ net.alchim31.maven scala-maven-plugin - 4.6.1 + 4.6.2 -feature @@ -228,7 +228,7 @@ org.scalatest scalatest-maven-plugin - 2.0.0 + 2.0.2 true I From 391c8a215bd402effa8523d9db5b9628f131eccc Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 14:06:33 +0200 Subject: [PATCH 28/55] factor ZeroConfAliasIntegrationSpec --- .../basic/ZeroConfAliasIntegrationSpec.scala | 453 +++++++----------- .../router/ChannelRouterIntegrationSpec.scala | 4 +- 2 files changed, 186 insertions(+), 271 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index 3514f33357..a15fa42dad 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -73,325 +73,240 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience sendPayment(alice, carol, 100_000 msat, hints = Seq(hint)) } - test("a->b->c (b-c private)") { f => + private def internalTest(f: FixtureParam, + deepConfirm: Boolean, + stripAliasFromCarol: Boolean, + bcPublic: Boolean, + bcZeroConf: Boolean, + bcScidAlias: Boolean, + bcHasRealScid: Boolean, + paymentWorksWithoutHint: Boolean, + paymentWorksWithHint_opt: Option[Boolean], + paymentWorksWithRealScidHint_opt: Option[Boolean], + ): Unit = { import f._ - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = deepConfirm, stripAliasFromCarol = stripAliasFromCarol) eventually { - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf) == bcZeroConf) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias) == bcScidAlias) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel == bcPublic) assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined == bcHasRealScid) + assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined == bcHasRealScid) } - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) + if (bcPublic && deepConfirm) { + // if channel b-c is public, we wait for alice to learn about it + eventually { + assert(getRouterData(alice).channels.size == 2) + } } - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases - assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == - getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - - // if alice uses the real scid instead of the b-c alias, it still works - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) - } - - test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) - - eventually { - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { + if (paymentWorksWithoutHint) { sendPaymentAliceToCarol(f) + } else { + intercept[AssertionError] { + sendPaymentAliceToCarol(f) + } } - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases - assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == - getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + if (paymentWorksWithHint_opt.contains(true)) { + sendPaymentAliceToCarol(f, useHint = true) + } else if (paymentWorksWithHint_opt.contains(false)) { + intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true) + } + } else { + // skipped + } - // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias - intercept[AssertionError] { + if (paymentWorksWithRealScidHint_opt.contains(true)) { + // if alice uses the real scid instead of the b-c alias, it still works sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } else if (paymentWorksWithRealScidHint_opt.contains(false)) { + intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + } + } else { + // skipped } } - test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) + test("a->b->c (b-c private)") { f => + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = false, + bcPublic = false, + bcZeroConf = false, + bcScidAlias = false, + bcHasRealScid = true, // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works + ) + + //TODO + // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases + //assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == + // getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // a-b is in NORMAL, the funding tx has reach min_depth - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - } + } - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } + test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = false, + bcPublic = false, + bcZeroConf = false, + bcScidAlias = true, + bcHasRealScid = true, // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + ) + } - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) + test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => + internalTest(f, + deepConfirm = false, + stripAliasFromCarol = false, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + ) } test("a->b->c (b-c zero-conf unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => + internalTest(f, + deepConfirm = false, + stripAliasFromCarol = true, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = None, // see below + paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + ) import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false, stripAliasFromCarol = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // a-b is in NORMAL, the funding tx has reach min_depth - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // carol don't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel + // carol doesn't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) } test("a->b->c (b-c zero-conf scid-alias unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => + internalTest(f, + deepConfirm = false, + stripAliasFromCarol = true, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = true, + bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = None, // see below + paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + ) import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false, stripAliasFromCarol = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // a-b is in NORMAL, the funding tx has reach min_depth - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // carol don't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel + // carol doesn't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) } test("a->b->c (b-c zero-conf deeply confirmed private)", Tag(ZeroConfBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // both channels have real scids because they are deeply confirmed, even the zeroconf channel - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - - // if alice uses the real scid instead of the b-c alias, it still works - // TODO This actually doesn't work, because the ChannelRelayer relies on the the LocalChannelUpdate event to maintain - // TODO its scid resolution map, and the channel doesn't emit a new one when a real scid is assigned, because we use the - // TODO remote alias for the channel_update, not the real scid. So the channel_update remains the same. We used to - // TODO have the ChannelRelayer also listen to ShortChannelIdAssigned event, but it's doesn't seem worth it here. - //sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = false, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = None // skipped, see below + // TODO This actually doesn't work, because the ChannelRelayer relies on the the LocalChannelUpdate event to maintain + // TODO its scid resolution map, and the channel doesn't emit a new one when a real scid is assigned, because we use the + // TODO remote alias for the channel_update, not the real scid. So the channel_update remains the same. We used to + // TODO have the ChannelRelayer also listen to ShortChannelIdAssigned event, but it's doesn't seem worth it here. + ) } test("a->b->c (b-c zero-conf scid-alias deeply confirmed private)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel) - // both channels have real scids because they are deeply confirmed, even the zeroconf channel - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - - // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias - intercept[AssertionError] { - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) - } + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = false, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = true, + bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + ) } test("a->b->c (b-c zero-conf deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true, stripAliasFromCarol = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - // both channels have real scids because they are deeply confirmed, even the zeroconf channel - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop - // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it midflight, - // she will still understand bob's alias and use it in her routing hint - assert(hint.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - - // if alice uses the real scid instead of the b-c alias, it still works - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = true, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid + // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it mid-flight, + // she will still understand bob's alias and use it in her routing hint + paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works + ) } test("a->b->c (b-c zero-conf scid-alias deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true, stripAliasFromCarol = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - // both channels have real scids because they are deeply confirmed, even the zeroconf channel - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - eventually { - // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop - // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it midflight, + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = true, + bcPublic = false, + bcZeroConf = true, + bcScidAlias = true, + bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid + // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it mid-flight, // she will still understand bob's alias and use it in her routing hint - assert(hint.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - } - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) - - // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias - intercept[AssertionError] { - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) - } + paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + ) } test("a->b->c (b-c zero-conf unconfirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = false) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - // a-b is in NORMAL, the funding tx has reach min_depth - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - // b-c is in NORMAL too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) - } - - // alice can't find a route to carol because b-c isn't announced - intercept[AssertionError] { - sendPaymentAliceToCarol(f) - } - - // with a routing hint the payment works - sendPaymentAliceToCarol(f, useHint = true) + internalTest(f, + deepConfirm = false, + stripAliasFromCarol = false, + bcPublic = true, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid + paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + ) } test("a->b->c (b-c zero-conf deeply confirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => - import f._ - - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = true) - - eventually { - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf)) - assert(!getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias)) - // both channels have real scids because they are deeply confirmed, even the zeroconf channel - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - } - - // alice eventually learns about public channel b-c - eventually { - assert(getRouterData(alice).channels.size == 2) - } - - // alice can make a payment to carol without routing hints - sendPaymentAliceToCarol(f) + internalTest(f, + deepConfirm = true, + stripAliasFromCarol = false, + bcPublic = true, + bcZeroConf = true, + bcScidAlias = false, + bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel + paymentWorksWithoutHint = true, + paymentWorksWithHint_opt = None, // there is no routing hints for public channels + paymentWorksWithRealScidHint_opt = None // there is no routing hints for public channels + ) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index e848ab3e5f..920e9dfbaa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -153,8 +153,8 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu channels.bob2alice.expectNoMessage(100 millis) // router graph contains a single channel - assert(router.stateData.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) - assert(router.stateData.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) } } From e26420b2a02f88ef281910020abe654dc94c7cd8 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 14:11:57 +0200 Subject: [PATCH 29/55] add unit test on scid_alias activation --- .../states/ChannelStateTestsHelperMethods.scala | 8 ++++++-- .../states/a/WaitForAcceptChannelStateSpec.scala | 10 ++++++++++ .../channel/states/a/WaitForOpenChannelStateSpec.scala | 9 +++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 45c4faca93..1c5fa9f68c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -74,6 +74,8 @@ object ChannelStateTestsTags { val ChannelType = "option_channel_type" /** If set, channels will use option_zeroconf. */ val ZeroConf = "zeroconf" + /** If set, channels will use option_scid_alias. */ + val ScidAlias = "scid_alias" } trait ChannelStateTestsBase extends Assertions with Eventually { @@ -159,7 +161,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional).updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ScidAlias))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) .initFeatures() val bobInitFeatures = Bob.nodeParams.features .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.Wumbo))(_.updated(Features.Wumbo, FeatureSupport.Optional)) @@ -169,7 +172,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually { .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ShutdownAnySegwit))(_.updated(Features.ShutdownAnySegwit, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.UpfrontShutdownScript))(_.updated(Features.UpfrontShutdownScript, FeatureSupport.Optional)) .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ChannelType))(_.updated(Features.ChannelType, FeatureSupport.Optional)) - .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ScidAlias, FeatureSupport.Optional).updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ZeroConf))(_.updated(Features.ZeroConf, FeatureSupport.Optional)) + .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.ScidAlias))(_.updated(Features.ScidAlias, FeatureSupport.Optional)) .initFeatures() val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, channelFlags.announceChannel) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index d09af51d74..3ed6f1429b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -104,6 +104,16 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS aliceOrigin.expectNoMessage() } + test("recv AcceptChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => + import f._ + val accept = bob2alice.expectMsgType[AcceptChannel] + assert(accept.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false))) + bob2alice.forward(alice) + awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_INTERNAL].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)) + aliceOrigin.expectNoMessage() + } + test("recv AcceptChannel (channel type not set)", Tag(ChannelStateTestsTags.AnchorOutputs)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index d776ccecce..13ac83a34c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -94,6 +94,15 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)) } + test("recv OpenChannel (anchor outputs zero fee htlc txs and scid alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ScidAlias)) { f => + import f._ + val open = alice2bob.expectMsgType[OpenChannel] + assert(open.channelType_opt == Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false))) + alice2bob.forward(bob) + awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].channelFeatures.channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)) + } + test("recv OpenChannel (non-default channel type)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag("standard-channel-type")) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] From a4c3ab456b81b9a8e161d332bfa81e37f024562c Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 14:37:01 +0200 Subject: [PATCH 30/55] added channel_type encoding tests with zeroconf/alias --- .../eclair/wire/protocol/LightningMessageCodecsSpec.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index a5e8cb54c3..f7f87263e2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -224,6 +224,12 @@ class LightningMessageCodecsSpec extends AnyFunSuite { // non-empty upfront_shutdown_script + channel type defaultEncoded ++ hex"0004 01abcdef" ++ hex"0103101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs))), defaultEncoded ++ hex"0002 abcd" ++ hex"0103401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"abcd"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false)))), + // empty upfront_shutdown_script + channel type (scid-alias) + defaultEncoded ++ hex"0000" ++ hex"0106 400000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false)))), + // empty upfront_shutdown_script + channel type (zeroconf) + defaultEncoded ++ hex"0000" ++ hex"0107 04000000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true)))), + // empty upfront_shutdown_script + channel type (scid-alias + zeroconf) + defaultEncoded ++ hex"0000" ++ hex"0107 04400000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)))), ) for ((encoded, expected) <- testCases) { From 14837e3138b2aec8a49879920e5456bb07100302 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 14:41:46 +0200 Subject: [PATCH 31/55] added roundtrip serialization test on channel_ready with alias --- .../acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index f7f87263e2..a7ec2e3118 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -356,6 +356,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { FundingCreated(randomBytes32(), bin32(0), 3, randomBytes64()), FundingSigned(randomBytes32(), randomBytes64()), ChannelReady(randomBytes32(), point(2)), + ChannelReady(randomBytes32(), point(2), TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId(123456)))), UpdateFee(randomBytes32(), FeeratePerKw(2 sat)), Shutdown(randomBytes32(), bin(47, 0)), ClosingSigned(randomBytes32(), 2 sat, randomBytes64()), From 18a3e287b5d9244ef091097788eba305644b836c Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 16:10:16 +0200 Subject: [PATCH 32/55] streamline channel_type handling in open api --- .../acinq/eclair/api/handlers/Channel.scala | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index dcd59d84f8..0948988816 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -32,19 +32,31 @@ trait Channel { import fr.acinq.eclair.api.serde.JsonSupport.{formats, marshaller, serialization} + val supportedChannelTypes = Set( + ChannelTypes.Standard, + ChannelTypes.StaticRemoteKey, + ChannelTypes.AnchorOutputs, + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = false), + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true), + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false), + ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true) + ).map(ct => ct.toString -> ct) // we use the toString method as name in the api + .toMap + val open: Route = postRequest("open") { implicit t => - formFields(nodeIdFormParam, "fundingSatoshis".as[Satoshi], "pushMsat".as[MilliSatoshi].?, "channelType".?, "fundingFeerateSatByte".as[FeeratePerByte].?, "announceChannel".as[Boolean].?, "zeroConf".as[Boolean]?false, "openTimeoutSeconds".as[Timeout].?) { - (nodeId, fundingSatoshis, pushMsat, channelType, fundingFeerateSatByte, announceChannel_opt, zeroConf, openTimeout_opt) => - val (channelTypeOk, channelType_opt) = channelType match { - case Some(str) if str == ChannelTypes.Standard.toString => (true, Some(ChannelTypes.Standard)) - case Some(str) if str == ChannelTypes.StaticRemoteKey.toString => (true, Some(ChannelTypes.StaticRemoteKey)) - case Some(str) if str == ChannelTypes.AnchorOutputs.toString => (true, Some(ChannelTypes.AnchorOutputs)) - case Some(str) if str == "anchor_outputs_zero_fee_htlc_tx" => (true, Some(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = announceChannel_opt.contains(true), zeroConf = zeroConf))) // alias feature is incompatible with public channel - case Some(_) => (false, None) + formFields(nodeIdFormParam, "fundingSatoshis".as[Satoshi], "pushMsat".as[MilliSatoshi].?, "channelType".?, "fundingFeerateSatByte".as[FeeratePerByte].?, "announceChannel".as[Boolean].?, "openTimeoutSeconds".as[Timeout].?) { + (nodeId, fundingSatoshis, pushMsat, channelTypeName_opt, fundingFeerateSatByte, announceChannel_opt, openTimeout_opt) => + + val (channelTypeOk, channelType_opt) = channelTypeName_opt match { + case Some(channelTypeName) => supportedChannelTypes.get(channelTypeName) match { + case Some(channelType) => (true, Some(channelType)) + case None => (false, None) // invalid channel type name + } case None => (true, None) } + if (!channelTypeOk) { - reject(MalformedFormFieldRejection("channelType", s"Channel type not supported: must be ${ChannelTypes.Standard.toString}, ${ChannelTypes.StaticRemoteKey.toString}, ${ChannelTypes.AnchorOutputs.toString} or ${ChannelTypes.AnchorOutputsZeroFeeHtlcTx.toString}")) + reject(MalformedFormFieldRejection("channelType", s"Channel type not supported: must be ${supportedChannelTypes.keys.mkString(",")}")) } else { complete { eclairApi.open(nodeId, fundingSatoshis, pushMsat, channelType_opt, fundingFeerateSatByte, announceChannel_opt, openTimeout_opt) From 7e41af21b92a5a7c4af3820967b50bafde610bc9 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 16:44:01 +0200 Subject: [PATCH 33/55] (hopefully) fix race condition in ZeroConfAliasIntegrationSpec --- .../basic/ZeroConfAliasIntegrationSpec.scala | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index a15fa42dad..5a930ac8b4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -101,7 +101,9 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience if (bcPublic && deepConfirm) { // if channel b-c is public, we wait for alice to learn about it eventually { - assert(getRouterData(alice).channels.size == 2) + val data = getRouterData(alice) + assert(data.channels.size == 2) + assert(data.channels.values.forall(pc => pc.update_1_opt.isDefined && pc.update_2_opt.isDefined)) } } @@ -113,6 +115,21 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience } } + if (bcZeroConf && stripAliasFromCarol && deepConfirm) { + // if b-c is zeroconf but carol doesn't send an alias to bob (we actually stripped it mid-flight), bob will use + // scid 0x0x0 for his channel_update, which carol won't be able to attribute to a channel. But when the channel + // deeply confirms, bob will start using the real scid for his channel_update and it will work. That is why we + // need to wait for carol to receive bob's new channel_update before attempting to use routing hints (because they + // use bob's channel_update). + eventually { + val hint_opt = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop + assert(hint_opt.isDefined) + // NB: in this convoluted scenario, where we are modifying messages on the wire, carol still received bob's + // alias and will use it in her routing hint + assert(hint_opt.get.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) + } + } + if (paymentWorksWithHint_opt.contains(true)) { sendPaymentAliceToCarol(f, useHint = true) } else if (paymentWorksWithHint_opt.contains(false)) { @@ -259,8 +276,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it mid-flight, - // she will still understand bob's alias and use it in her routing hint paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works ) } @@ -275,8 +290,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - // NB: in this convoluted scenario where carol has actually sent her alias but we have stripped it mid-flight, - // she will still understand bob's alias and use it in her routing hint paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias ) } From 1523c41207082d1c902bc9807342d89e5622f5e4 Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 6 Jun 2022 18:08:36 +0200 Subject: [PATCH 34/55] fix comment --- .../src/main/scala/fr/acinq/eclair/channel/Helpers.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 8a4be1b964..97a9e6e869 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -294,11 +294,9 @@ object Helpers { /** * As funder we trust ourselves to not double spend funding txs: we could always use a zero-confirmation watch, - * but we need a scid to send the initial channel_update and remote may not provide an alias (and we don't want to - * trust the real scid sent by remote in their channel_ready). So we always wait for one conf, except if the channel - * has the zero-conf feature (because presumably the peer will sends an alias in that case). - * - * @return + * but we need a scid to send the initial channel_update and remote may not provide an alias. That's why we always + * wait for one conf, except if the channel has the zero-conf feature (because presumably the peer will send an + * alias in that case). */ def minDepthFunder(channelFeatures: ChannelFeatures): Long = { if (channelFeatures.hasFeature(Features.ZeroConf)) { From 0a5a2393f0e9eef824cb349a2fddca363943fb92 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 8 Jun 2022 11:36:10 +0200 Subject: [PATCH 35/55] force-close zero-conf channels if no remote_alias --- .../fr/acinq/eclair/channel/Helpers.scala | 9 +- .../channel/fsm/ChannelOpenSingleFunder.scala | 4 +- .../c/WaitForChannelReadyStateSpec.scala | 12 +-- .../basic/ZeroConfAliasIntegrationSpec.scala | 95 +------------------ .../basic/fixtures/MinimalNodeFixture.scala | 6 ++ 5 files changed, 17 insertions(+), 109 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 97a9e6e869..0932acbb67 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -203,14 +203,7 @@ object Helpers { channelAnnouncement_opt.map(_.shortChannelId) // we use the real "final" scid when it is publicly announced .orElse(remoteAlias_opt) // otherwise the remote alias .orElse(realShortChannelId_opt) // if we don't have a remote alias, we use the real scid (which could change because the funding tx possibly has less than 6 confs here) - .getOrElse { - // Our model requires a channel_update in DATA_NORMAL. But if we are in zero-conf and our peer - // does not send an alias, then we have neither a remote alias, nor a real scid. This should never happen, so - // we default to a ShortChannelId(0) which should never be used. - log.warning("no real scid and no alias!! this should never happen") - ShortChannelId(0) - } - //.getOrElse(throw new RuntimeException("this is a zero-conf channel and remote hasn't provided an alias")) // if we don't have a real scid, it means this is a zero-conf channel and our peer must have sent an alias + .getOrElse(throw new RuntimeException("this is a zero-conf channel and no alias was provided in channel_ready")) // if we don't have a real scid, it means this is a zero-conf channel and our peer must have sent an alias } def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, realShortChannelId_opt = d.realShortChannelId_opt, remoteAlias_opt = d.remoteAlias_opt) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index f020bbb22a..52524a6034 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -419,8 +419,6 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { when(WAIT_FOR_CHANNEL_READY)(handleExceptions { case Event(channelReady: ChannelReady, d@DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt, localAlias, _)) => - // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) - blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) val remoteAlias_opt = channelReady.alias_opt remoteAlias_opt.foreach { remoteAlias => log.info("received remoteAlias={}", remoteAlias) @@ -433,6 +431,8 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, relayFees.feeBase, relayFees.feeProportionalMillionths, commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) // we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network context.system.scheduler.scheduleWithFixedDelay(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, delay = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh)) + // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) + blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), realShortChannelId_opt = realShortChannelId_opt, buried = false, None, initialChannelUpdate, localAlias = localAlias, remoteAlias_opt = remoteAlias_opt, None, None, None) storing() case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_CHANNEL_READY) if d.commitments.announceChannel => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index d6a03f4b5e..87bac3dc40 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -138,13 +138,11 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val channelReady = bob2alice.expectMsgType[ChannelReady] val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate - // edge case: we have neither a real scid nor an alias, we use a fake scid - assert(initialChannelUpdate.shortChannelId == ShortChannelId(0)) - assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) - assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) - awaitCond(alice.stateName == NORMAL) + awaitCond(alice.stateName == CLOSING) + assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].desc == "commit-tx") + assert(alice2blockchain.expectMsgType[TxPublisher.PublishTx].desc == "local-anchor") + assert(alice2blockchain.expectMsgType[TxPublisher.PublishFinalTx].desc == "local-main-delayed") + alice2blockchain.expectMsgType[WatchTxConfirmed] } test("recv ChannelReady (public)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index 5a930ac8b4..2bfcf84bcb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -44,7 +44,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience fixture.cleanup() } - private def createChannels(f: FixtureParam)(deepConfirm: Boolean, stripAliasFromCarol: Boolean = false): (ByteVector32, ByteVector32) = { + private def createChannels(f: FixtureParam)(deepConfirm: Boolean): (ByteVector32, ByteVector32) = { import f._ alice.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol), deepConfirm = deepConfirm)) @@ -52,11 +52,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience carol.watcher.setAutoPilot(watcherAutopilot(knownFundingTxs(alice, bob, carol), deepConfirm = deepConfirm)) connect(alice, bob) - connect(bob, carol, mutate21 = { - case channelReady: ChannelReady if stripAliasFromCarol => - channelReady.modify(_.tlvStream.records).using(_.filter { case _: ChannelReadyTlv.ShortChannelIdTlv => false; case _ => true }) - case other => other - }) + connect(bob, carol) val channelId_ab = openChannel(alice, bob, 100_000 sat).channelId val channelId_bc = openChannel(bob, carol, 100_000 sat).channelId @@ -75,7 +71,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience private def internalTest(f: FixtureParam, deepConfirm: Boolean, - stripAliasFromCarol: Boolean, bcPublic: Boolean, bcZeroConf: Boolean, bcScidAlias: Boolean, @@ -86,7 +81,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience ): Unit = { import f._ - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = deepConfirm, stripAliasFromCarol = stripAliasFromCarol) + val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = deepConfirm) eventually { assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf) == bcZeroConf) @@ -115,21 +110,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience } } - if (bcZeroConf && stripAliasFromCarol && deepConfirm) { - // if b-c is zeroconf but carol doesn't send an alias to bob (we actually stripped it mid-flight), bob will use - // scid 0x0x0 for his channel_update, which carol won't be able to attribute to a channel. But when the channel - // deeply confirms, bob will start using the real scid for his channel_update and it will work. That is why we - // need to wait for carol to receive bob's new channel_update before attempting to use routing hints (because they - // use bob's channel_update). - eventually { - val hint_opt = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop - assert(hint_opt.isDefined) - // NB: in this convoluted scenario, where we are modifying messages on the wire, carol still received bob's - // alias and will use it in her routing hint - assert(hint_opt.get.shortChannelId == getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - } - } - if (paymentWorksWithHint_opt.contains(true)) { sendPaymentAliceToCarol(f, useHint = true) } else if (paymentWorksWithHint_opt.contains(false)) { @@ -155,7 +135,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience test("a->b->c (b-c private)") { f => internalTest(f, deepConfirm = true, - stripAliasFromCarol = false, bcPublic = false, bcZeroConf = false, bcScidAlias = false, @@ -175,7 +154,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => internalTest(f, deepConfirm = true, - stripAliasFromCarol = false, bcPublic = false, bcZeroConf = false, bcScidAlias = true, @@ -189,7 +167,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => internalTest(f, deepConfirm = false, - stripAliasFromCarol = false, bcPublic = false, bcZeroConf = true, bcScidAlias = false, @@ -200,44 +177,9 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience ) } - test("a->b->c (b-c zero-conf unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => - internalTest(f, - deepConfirm = false, - stripAliasFromCarol = true, - bcPublic = false, - bcZeroConf = true, - bcScidAlias = false, - bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced - paymentWorksWithHint_opt = None, // see below - paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet - ) - import f._ - // carol doesn't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel - assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) - } - - test("a->b->c (b-c zero-conf scid-alias unconfirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => - internalTest(f, - deepConfirm = false, - stripAliasFromCarol = true, - bcPublic = false, - bcZeroConf = true, - bcScidAlias = true, - bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced - paymentWorksWithHint_opt = None, // see below - paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet - ) - import f._ - // carol doesn't have hints, because bob sent her a channel_update with scid=0, which carol couldn't attribute to a channel - assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.isEmpty) - } - test("a->b->c (b-c zero-conf deeply confirmed private)", Tag(ZeroConfBobCarol)) { f => internalTest(f, deepConfirm = true, - stripAliasFromCarol = false, bcPublic = false, bcZeroConf = true, bcScidAlias = false, @@ -255,7 +197,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience test("a->b->c (b-c zero-conf scid-alias deeply confirmed private)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => internalTest(f, deepConfirm = true, - stripAliasFromCarol = false, bcPublic = false, bcZeroConf = true, bcScidAlias = true, @@ -266,38 +207,9 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience ) } - test("a->b->c (b-c zero-conf deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol)) { f => - internalTest(f, - deepConfirm = true, - stripAliasFromCarol = true, - bcPublic = false, - bcZeroConf = true, - bcScidAlias = false, - bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced - paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works - ) - } - - test("a->b->c (b-c zero-conf scid-alias deeply confirmed private, no alias from carol)", Tag(ZeroConfBobCarol), Tag(ScidAliasBobCarol)) { f => - internalTest(f, - deepConfirm = true, - stripAliasFromCarol = true, - bcPublic = false, - bcZeroConf = true, - bcScidAlias = true, - bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced - paymentWorksWithHint_opt = Some(true), // carol is able to give routing hints from bob, because bob has sent a new channel_update using the real scid - paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias - ) - } - test("a->b->c (b-c zero-conf unconfirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => internalTest(f, deepConfirm = false, - stripAliasFromCarol = false, bcPublic = true, bcZeroConf = true, bcScidAlias = false, @@ -311,7 +223,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience test("a->b->c (b-c zero-conf deeply confirmed public)", Tag(ZeroConfBobCarol), Tag(PublicBobCarol)) { f => internalTest(f, deepConfirm = true, - stripAliasFromCarol = false, bcPublic = true, bcZeroConf = true, bcScidAlias = false, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index e590cb46e2..2c279453d0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -99,6 +99,12 @@ object MinimalNodeFixture extends Assertions { ) } + /** + * Connect node1 to node2, using a real [[PeerConnection]] and a fake transport layer. + * + * @param mutate12 a method to alter messages from node1 to node2 mid-flight for testing purposes + * @param mutate21 a method to alter messages from node2 to node1 mid-flight for testing purposes + */ def connect(node1: MinimalNodeFixture, node2: MinimalNodeFixture, mutate12: Any => Any = identity, mutate21: Any => Any = identity)(implicit system: ActorSystem): ConnectionResult.Connected = { val sender = TestProbe("sender") From 9be02bfa27621720650356cbd2a2829aa024a83d Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 8 Jun 2022 14:25:44 +0200 Subject: [PATCH 36/55] support early channel_ready as funder As funder, if we receive a `channel_ready` from the fundee containing an alias, we can instantly use the channel. We trust ourselves to not double spend our own funding txs, the only thing that prevent us from always using a zero-conf watch is that we need to have an alias from remote. --- .../channel/fsm/ChannelOpenSingleFunder.scala | 17 ++- .../c/WaitForFundingConfirmedStateSpec.scala | 111 +++++++++++++++--- 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 52524a6034..d123a22144 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -345,14 +345,21 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions { - case Event(msg: ChannelReady, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => - log.info(s"received their ChannelReady, deferring message") - stay() using d.copy(deferred = Some(msg)) // no need to store, they will re-send if we get disconnected + case Event(channelReady: ChannelReady, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => + if (channelReady.alias_opt.isDefined && + d.commitments.localParams.isInitiator && + !d.commitments.channelFeatures.features.contains(Features.ZeroConf)) { + 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") + // we set a new zero-conf watch which will trigger instantly, the original watch will trigger later and be ignored + blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, 0) + } else { + log.info("received their channel_ready, deferring message") + } + stay() using d.copy(deferred = Some(channelReady)) // no need to store, they will re-send if we get disconnected case Event(WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTx), d@DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, _, _, deferred, _)) => Try(Transaction.correctlySpends(commitments.fullySignedLocalCommitTx(keyManager).tx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) match { case Success(_) => - log.info(s"channelId=${commitments.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex") blockchain ! WatchFundingLost(self, commitments.commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks) if (!d.commitments.localParams.isInitiator) context.system.eventStream.publish(TransactionPublished(commitments.channelId, remoteNodeId, fundingTx, 0 sat, "funding")) context.system.eventStream.publish(TransactionConfirmed(commitments.channelId, remoteNodeId, fundingTx)) @@ -364,9 +371,11 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { // If we are using zero-conf then the transaction may not have been confirmed yet, that's why the block // height is zero. In some cases (e.g. we were down for some time) the tx may actually have confirmed, in // that case we will have a real scid even if the channel is zero-conf + log.info("skipping funding tx confirmation") None } else { + log.info(s"channel was confirmed at blockHeight=$blockHeight txIndex=$txIndex") Some(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) } // the alias will use in our channel_update message, the goal is to be able to use our channel diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index e0e578b2d2..7c26e85ae3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -17,7 +17,9 @@ package fr.acinq.eclair.channel.states.c import akka.testkit.{TestFSMRef, TestProbe} +import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Script, Transaction} +import fr.acinq.eclair.Features.ZeroConf import fr.acinq.eclair.blockchain.CurrentBlockHeight import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ @@ -26,10 +28,10 @@ import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITC import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Scripts.multiSig2of2 -import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, ChannelReady, FundingSigned, Init, OpenChannel} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, ChannelReadyTlv, Error, FundingCreated, FundingSigned, Init, OpenChannel} import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomKey} -import org.scalatest.{Outcome, Tag} import org.scalatest.funsuite.FixtureAnyFunSuiteLike +import org.scalatest.{Outcome, Tag} import scala.concurrent.duration._ @@ -80,26 +82,74 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF } } - test("recv ChannelReady") { f => + test("recv ChannelReady (funder)") { f => import f._ - // we create a new listener that registers after alice has published the funding tx - val listener = TestProbe() - bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished]) - bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) // make bob send a ChannelReady msg val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) - val txPublished = listener.expectMsgType[TransactionPublished] - assert(txPublished.tx == fundingTx) - assert(txPublished.miningFee == 0.sat) // bob is fundee - assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) - val msg = bob2alice.expectMsgType[ChannelReady] + val channelReady = bob2alice.expectMsgType[ChannelReady] + assert(channelReady.alias_opt.isDefined) + // test starts here + bob2alice.forward(alice) + // alice keeps bob's channel_ready for later processing + eventually { + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReady)) + } + // and alice also creates a zero-conf watch + val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] + assert(aliceWatchFundingConfirmed.minDepth == 0) + } + + test("recv ChannelReady (funder, no remote alias)") { f => + import f._ + // make bob send a ChannelReady msg + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) + val channelReadyNoAlias = bob2alice.expectMsgType[ChannelReady] + .modify(_.tlvStream.records).using(_.filter { case _: ChannelReadyTlv.ShortChannelIdTlv => false; case _ => true }) + // test starts here + bob2alice.forward(alice, channelReadyNoAlias) + // alice keeps bob's channel_ready for later processing + eventually { + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReadyNoAlias)) + } + alice2blockchain.expectNoMessage() + } + + test("recv ChannelReady (funder, zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => + import f._ + // make bob send a ChannelReady msg + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) + val channelReady = bob2alice.expectMsgType[ChannelReady] + assert(channelReady.alias_opt.isDefined) + // test starts here bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(msg)) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) + // alice keeps bob's channel_ready for later processing + eventually { + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReady)) + } + // and alice also doesn't creates a zero-conf watch because the channel is already zero-conf + alice2blockchain.expectNoMessage() } - test("recv WatchFundingConfirmedTriggered") { f => + test("recv ChannelReady (fundee)") { f => + import f._ + // make alice send a ChannelReady msg + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + alice ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) + val channelReady = alice2bob.expectMsgType[ChannelReady] + // test starts here + alice2bob.forward(bob) + // alice keeps bob's channel_ready for later processing + eventually { + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReady)) + } + // bob is fundee, he doesn't trust alice and won't create a zero-conf watch + bob2blockchain.expectNoMessage() + } + + test("recv WatchFundingConfirmedTriggered (funder)") { f => import f._ // we create a new listener that registers after alice has published the funding tx val listener = TestProbe() @@ -115,6 +165,37 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF assert(channelReady.alias_opt.isDefined) } + test("recv WatchFundingConfirmedTriggered (funder, early ChannelReady from fundee)") { f => + import f._ + // the channel isn't zero-conf, but bob (fundee) has sent an early channel_ready so alice created a zero-conf watch + assert(!alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.channelFeatures.hasFeature(ZeroConf)) + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + // the zero-conf watch confirms instantly + alice ! WatchFundingConfirmedTriggered(BlockHeight(0), 0, fundingTx) + awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + } + + test("recv WatchFundingConfirmedTriggered (fundee)") { f => + import f._ + // we create a new listener that registers after alice has published the funding tx + val listener = TestProbe() + bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished]) + bob.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) + // make bob send a ChannelReady msg + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) + val txPublished = listener.expectMsgType[TransactionPublished] + assert(txPublished.tx == fundingTx) + assert(txPublished.miningFee == 0.sat) // bob is fundee + assert(listener.expectMsgType[TransactionConfirmed].tx == fundingTx) + awaitCond(bob.stateName == WAIT_FOR_CHANNEL_READY) + bob2blockchain.expectMsgType[WatchFundingLost] + val channelReady = bob2alice.expectMsgType[ChannelReady] + // we always send an alias + assert(channelReady.alias_opt.isDefined) + } + test("recv WatchFundingConfirmedTriggered (bad funding pubkey script)") { f => import f._ val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get From 572136dd9feafc298f262a161a28d255db20bbeb Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 8 Jun 2022 15:17:03 +0200 Subject: [PATCH 37/55] tentative race condition fix in MessageIntegrationSpec --- .../scala/fr/acinq/eclair/TestUtils.scala | 22 +++++++++++++++++-- .../integration/MessageIntegrationSpec.scala | 4 +++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala index de8c278355..3c1ba4ccd7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala @@ -16,12 +16,13 @@ package fr.acinq.eclair -import akka.actor.ActorRef -import akka.event.DiagnosticLoggingAdapter +import akka.actor.{ActorRef, ActorSystem} +import akka.event.{DiagnosticLoggingAdapter, EventStream} import akka.testkit.{TestActor, TestProbe} import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.io.Peer import fr.acinq.eclair.wire.protocol.LightningMessage +import org.scalatest.concurrent.Eventually.eventually import java.io.File import java.net.ServerSocket @@ -91,4 +92,21 @@ object TestUtils { def realScid(l: Long): RealShortChannelId = ShortChannelId(l).toReal + /** + * Subscribing to [[EventStream]] is asynchronous, which can lead to race conditions. + * + * We use a dummy event subscription and poll until we receive a message, and rely on the fact that + * [[EventStream]] is an actor which means that all previous subscriptions have been taken into account. + */ + def waitEventStreamSynced(eventStream: EventStream)(implicit system: ActorSystem): Unit = { + val listener = TestProbe() + case class DummyEvent() + eventStream.subscribe(listener.ref, classOf[DummyEvent]) + eventually { + eventStream.publish(DummyEvent()) + assert(listener.msgAvailable) + } + + } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala index 836470bf9b..6c19262e89 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala @@ -24,6 +24,7 @@ import akka.util.Timeout import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.Transaction import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} +import fr.acinq.eclair.TestUtils.waitEventStreamSynced import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{Watch, WatchFundingConfirmed} import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient @@ -303,11 +304,12 @@ class MessageIntegrationSpec extends IntegrationSpec { val probe = TestProbe() val eventListener = TestProbe() nodes("C").system.eventStream.subscribe(eventListener.ref, classOf[OnionMessages.ReceiveMessage]) + waitEventStreamSynced(nodes("C").system.eventStream) alice.sendOnionMessage(nodes("B").nodeParams.nodeId :: Nil, Left(nodes("C").nodeParams.nodeId), None, hex"7300").pipeTo(probe.ref) assert(probe.expectMsgType[SendOnionMessageResponse].sent) val r = eventListener.expectMsgType[OnionMessages.ReceiveMessage](max = 60 seconds) - assert(r.pathId == None) + assert(r.pathId.isEmpty) assert(r.finalPayload.records.unknown.toSet == Set(GenericTlv(UInt64(115), hex""))) } From a426e1b95508683190df461bdaa388fe3f416eb5 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 8 Jun 2022 16:42:56 +0200 Subject: [PATCH 38/55] emit TransactionConfirmed when funding txis deeply confirmed for zero-conf channels --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 6 +++++- .../channel/fsm/ChannelOpenSingleFunder.scala | 4 ++-- .../states/ChannelStateTestsHelperMethods.scala | 4 ++++ .../channel/states/e/NormalStateSpec.scala | 17 +++++++++++------ 4 files changed, 22 insertions(+), 9 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 5ff0fc0303..aac0fef154 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 @@ -613,9 +613,13 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(c: CurrentFeerates, d: DATA_NORMAL) => handleCurrentFeerate(c, d) - case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, _), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => + case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => val realShortChannelId = ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt) log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=$realShortChannelId") + if (d.realShortChannelId_opt.isEmpty) { + // this is a zero-conf channel and it is the first time we know for sure that the funding tx has been confirmed + context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) + } if (!d.realShortChannelId_opt.contains(realShortChannelId)) { log.info(s"setting final real scid: old=${d.realShortChannelId_opt.getOrElse("empty")} new=$realShortChannelId") // we announce the new shortChannelId diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index d123a22144..a4acc64aa7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -361,8 +361,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { Try(Transaction.correctlySpends(commitments.fullySignedLocalCommitTx(keyManager).tx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) match { case Success(_) => blockchain ! WatchFundingLost(self, commitments.commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks) - if (!d.commitments.localParams.isInitiator) context.system.eventStream.publish(TransactionPublished(commitments.channelId, remoteNodeId, fundingTx, 0 sat, "funding")) - context.system.eventStream.publish(TransactionConfirmed(commitments.channelId, remoteNodeId, fundingTx)) + if (!d.commitments.localParams.isInitiator) context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, fundingTx, 0 sat, "funding")) val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelConfig) val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) deferred.foreach(self ! _) @@ -376,6 +375,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { } else { log.info(s"channel was confirmed at blockHeight=$blockHeight txIndex=$txIndex") + context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) Some(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) } // the alias will use in our channel_update message, the goal is to be able to use our channel diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 1c5fa9f68c..569d276ab3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -178,6 +178,10 @@ trait ChannelStateTestsBase extends Assertions with Eventually { val channelType = ChannelTypes.defaultFromFeatures(aliceInitFeatures, bobInitFeatures, channelFlags.announceChannel) + // those features can only be enabled with AnchorOutputsZeroFeeHtlcTxs, this is to prevent incompatible test configurations + if (tags.contains(ChannelStateTestsTags.ZeroConf)) assert(tags.contains(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), "invalid test configuration") + if (tags.contains(ChannelStateTestsTags.ScidAlias)) assert(channelType.features.contains(Features.ScidAlias), "invalid test configuration") + implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global val aliceParams = Alice.channelParams .modify(_.initFeatures).setTo(aliceInitFeatures) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index a51b47181a..d98110c6ed 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -41,7 +41,7 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.DirectedHtlc.{incoming, outgoing} import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, TemporaryNodeFailure, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc, Warning} +import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, TemporaryNodeFailure, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc, Warning} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} import scodec.bits._ @@ -3463,16 +3463,21 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with channelUpdateListener.expectNoMessage(1 second) } - test("recv WatchFundingDeeplyBuriedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.ZeroConf)) { f => + test("recv WatchFundingDeeplyBuriedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ - val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get - // existing funding tx coordinates - val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) - alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) + // we create a new listener that registers after alice has published the funding tx + val listener = TestProbe() + alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) + // zero-conf channel : the funding tx isn't confirmed + assert(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42000), 42, null) + val realShortChannelId = ShortChannelId(BlockHeight(42000), 42, 0) // update data with real short channel id awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(realShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one alice2bob.expectNoMessage() + // this is the first time we know the funding tx has been confirmed + listener.expectMsgType[TransactionConfirmed] // we don't re-publish the same channel_update if there was no change channelUpdateListener.expectNoMessage(1 second) } From 34cc748521bc77482e52c27c4a351efd794fc780 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 8 Jun 2022 17:55:12 +0200 Subject: [PATCH 39/55] clean up the router scid map --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 3 +- .../fr/acinq/eclair/router/Validation.scala | 16 +++++----- .../router/ChannelRouterIntegrationSpec.scala | 29 +++++++++++++++++-- 3 files changed, 37 insertions(+), 11 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 aac0fef154..75ea292b50 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 @@ -1636,13 +1636,14 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } emitEvent_opt.foreach { case EmitLocalChannelUpdate(reason, d, sendToPeer) => - log.info(s"emitting channel update: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.realShortChannelId_opt} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) + log.info(s"emitting channel update event: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.realShortChannelId_opt} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) val lcu = LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments) context.system.eventStream.publish(lcu) if (sendToPeer) { send(d.channelUpdate) } case EmitLocalChannelDown(d) => + log.debug(s"emitting channel down event") val lcd = LocalChannelDown(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId) context.system.eventStream.publish(lcd) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index e195c24a61..8ec6014085 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -178,7 +178,7 @@ object Validation { log.debug("adding public channel channelId={} realScid={} localChannel={} publicChannel={}", channelId, ann.shortChannelId, privChan_opt.isDefined, pubChan) // if this is a local channel graduating from private to public, we need to update the graph because the edge // identifiers change from alias to real scid, and we can also populate the metadata - val graph1 = d.privateChannels.get(pubChan.channelId) match { + val graph1 = privChan_opt match { case Some(privateChannel) => log.debug("updating the graph for shortChannelId={}", pubChan.shortChannelId) // mutable variable is simpler here @@ -201,6 +201,8 @@ object Validation { channels = d.channels + (pubChan.shortChannelId -> pubChan), // we remove the corresponding unannounced channel that we may have until now privateChannels = d.privateChannels - pubChan.channelId, + // we also remove the scid -> channelId mappings + scid2PrivateChannels = d.scid2PrivateChannels - pubChan.shortChannelId -- privChan_opt.map(_.localAlias), // we also add the newly validated channels to the rebroadcast queue rebroadcast = d.rebroadcast.copy( // we rebroadcast the channel to our peers @@ -517,23 +519,23 @@ object Validation { def handleLocalChannelDown(d: Data, localNodeId: PublicKey, lcd: LocalChannelDown)(implicit log: LoggingAdapter): Data = { import lcd.{channelId, remoteNodeId} + log.debug("handleLocalChannelDown lcd={}", lcd) + val scid2PrivateChannels1 = d.scid2PrivateChannels - lcd.localAlias -- lcd.realShortChannelId_opt // a local channel has permanently gone down if (lcd.realShortChannelId_opt.exists(d.channels.contains)) { // the channel was public, we will receive (or have already received) a WatchEventSpentBasic event, that will trigger a clean up of the channel // so let's not do anything here - d + d.copy(scid2PrivateChannels = scid2PrivateChannels1) } else if (d.privateChannels.contains(lcd.channelId)) { // the channel was private or public-but-not-yet-announced, let's do the clean up val localAlias = d.privateChannels(channelId).localAlias log.info("removing private local channel and channel_update for channelId={} localAlias={}", channelId, localAlias) - val desc1 = ChannelDesc(localAlias, localNodeId, remoteNodeId) - val desc2 = ChannelDesc(localAlias, remoteNodeId, localNodeId) // we remove the corresponding updates from the graph val graphWithBalances1 = d.graphWithBalances - .removeEdge(desc1) - .removeEdge(desc2) + .removeEdge(ChannelDesc(localAlias, localNodeId, remoteNodeId)) + .removeEdge(ChannelDesc(localAlias, remoteNodeId, localNodeId)) // and we remove the channel and channel_update from our state - d.copy(privateChannels = d.privateChannels - channelId, graphWithBalances = graphWithBalances1) + d.copy(privateChannels = d.privateChannels - channelId, scid2PrivateChannels = scid2PrivateChannels1, graphWithBalances = graphWithBalances1) } else { d } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 920e9dfbaa..6ffcc4de1a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -2,13 +2,13 @@ package fr.acinq.eclair.router import akka.actor.ActorSystem import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.WatchFundingDeeplyBuriedTriggered -import fr.acinq.eclair.channel.DATA_NORMAL +import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{WatchExternalChannelSpent, WatchExternalChannelSpentTriggered, WatchFundingDeeplyBuriedTriggered} import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} +import fr.acinq.eclair.channel.{CMD_CLOSE, DATA_NORMAL} import fr.acinq.eclair.io.Peer.PeerRoutingMessage import fr.acinq.eclair.router.Graph.GraphStructure.GraphEdge import fr.acinq.eclair.router.Router._ -import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate} +import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate, Shutdown} import fr.acinq.eclair.{BlockHeight, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -98,6 +98,9 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu router.stateData.privateChannels.isEmpty && router.stateData.channels.size == 1 } + // router has cleaned up the scid mapping + assert(router.stateData.scid2PrivateChannels.isEmpty) + // alice and bob won't send their channel_update directly to each other because the channel has been announced // but we can get the update from their data awaitCond { @@ -156,6 +159,26 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) } + // channel closes + channels.alice ! CMD_CLOSE(TestProbe().ref, scriptPubKey = None, feerates = None) + channels.alice2bob.expectMsgType[Shutdown] + channels.alice2bob.forward(channels.bob) + channels.bob2alice.expectMsgType[Shutdown] + channels.bob2alice.forward(channels.alice) + if (testTags.contains(ChannelStateTestsTags.ChannelsPublic)) { + // if the channel was public, the router asked the watcher to watch the funding tx and will be notified + val watchSpentBasic = channels.alice2blockchain.expectMsgType[WatchExternalChannelSpent] + watchSpentBasic.replyTo ! WatchExternalChannelSpentTriggered(watchSpentBasic.shortChannelId) + } + // router cleans up the channel + awaitAssert { + assert(router.stateData.nodes == Map.empty) + assert(router.stateData.channels == Map.empty) + assert(router.stateData.privateChannels == Map.empty) + assert(router.stateData.scid2PrivateChannels == Map.empty) + assert(router.stateData.graphWithBalances.graph.edgeSet().isEmpty) + //assert(router.stateData.graphWithBalances.graph.vertexSet().isEmpty) // TODO: bug! + } } test("private local channel") { f => From 896c4e572b6d2deeb8dbb7d20b7d553d559ca544 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 9 Jun 2022 13:49:35 +0200 Subject: [PATCH 40/55] increase timeout to encrypt bitcoin wallet in tests --- .../eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala index c37464d932..e9a8e7c740 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala @@ -61,7 +61,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A val bitcoinClient = new BitcoinCoreClient(bitcoinrpcclient) val walletPassword = Random.alphanumeric.take(8).mkString sender.send(bitcoincli, BitcoinReq("encryptwallet", walletPassword)) - sender.expectMsgType[JString] + sender.expectMsgType[JString](60 seconds) restartBitcoind(sender) val pubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(randomKey().publicKey, randomKey().publicKey))) From 31799b7ae5bd3d889dfeca996c7fcbd92d8971bb Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 9 Jun 2022 14:42:38 +0200 Subject: [PATCH 41/55] group all short ids in a class --- .../fr/acinq/eclair/channel/ChannelData.scala | 28 +++- .../acinq/eclair/channel/ChannelEvents.scala | 21 +-- .../fr/acinq/eclair/channel/Helpers.scala | 8 +- .../fr/acinq/eclair/channel/Register.scala | 2 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 138 +++++++++--------- .../channel/fsm/ChannelOpenSingleFunder.scala | 29 ++-- .../acinq/eclair/json/JsonSerializers.scala | 22 ++- .../eclair/payment/relay/ChannelRelayer.scala | 14 +- .../eclair/router/RouteCalculation.scala | 4 +- .../scala/fr/acinq/eclair/router/Router.scala | 6 +- .../fr/acinq/eclair/router/Validation.scala | 30 ++-- .../channel/version0/ChannelCodecs0.scala | 6 +- .../channel/version1/ChannelCodecs1.scala | 8 +- .../channel/version2/ChannelCodecs2.scala | 8 +- .../channel/version3/ChannelCodecs3.scala | 20 +-- .../eclair/wire/protocol/CommonCodecs.scala | 15 +- .../c/WaitForChannelReadyStateSpec.scala | 14 +- .../c/WaitForFundingConfirmedStateSpec.scala | 2 +- .../channel/states/e/NormalStateSpec.scala | 38 ++--- .../fr/acinq/eclair/db/ChannelsDbSpec.scala | 3 +- .../integration/PaymentIntegrationSpec.scala | 4 +- .../basic/TwoNodesIntegrationSpec.scala | 10 +- .../basic/ZeroConfAliasIntegrationSpec.scala | 25 ++-- .../basic/fixtures/MinimalNodeFixture.scala | 20 +-- .../eclair/json/JsonSerializersSpec.scala | 14 ++ .../payment/relay/ChannelRelayerSpec.scala | 40 ++--- .../eclair/payment/relay/RelayerSpec.scala | 5 +- .../acinq/eclair/router/BaseRouterSpec.scala | 32 ++-- .../router/ChannelRouterIntegrationSpec.scala | 12 +- .../fr/acinq/eclair/router/RouterSpec.scala | 14 +- .../internal/channel/ChannelCodecsSpec.scala | 8 +- .../channel/version3/ChannelCodecs3Spec.scala | 8 +- 32 files changed, 326 insertions(+), 282 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index d4687bd306..c47848e730 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -427,16 +427,32 @@ final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, deferred: Option[ChannelReady], lastSent: Either[FundingCreated, FundingSigned]) extends PersistentChannelData final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, - realShortChannelId_opt: Option[RealShortChannelId], - localAlias: LocalAlias, + shortIds: ShortIds, lastSent: ChannelReady) extends PersistentChannelData +sealed trait RealScidStatus { def toOption: Option[RealShortChannelId] } +object RealScidStatus { + case class Temporary(realScid: RealShortChannelId) extends RealScidStatus { override def toOption: Option[RealShortChannelId] = Some(realScid) } + case class Final(realScid: RealShortChannelId) extends RealScidStatus { override def toOption: Option[RealShortChannelId] = Some(realScid) } + case object Unknown extends RealScidStatus { override def toOption: Option[RealShortChannelId] = None } +} + +/** + * Short identifiers for the channel + * + * @param real the real scid, it may change if a reorg happens before the channel reaches 6 conf + * @param localAlias we must remember the alias that we sent to our peer because we use it to: + * - identify incoming [[ChannelUpdate]] + * - route outgoing payments to that channel + * @param remoteAlias_opt we only remember the last alias received from our peer, we use this to generate + * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] + */ +case class ShortIds(real: RealScidStatus, + localAlias: LocalAlias, + remoteAlias_opt: Option[ShortChannelId]) final case class DATA_NORMAL(commitments: Commitments, - realShortChannelId_opt: Option[RealShortChannelId], - buried: Boolean, + shortIds: ShortIds, channelAnnouncement: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, - localAlias: LocalAlias, - remoteAlias_opt: Option[ShortChannelId], localShutdown: Option[Shutdown], remoteShutdown: Option[Shutdown], closingFeerates: Option[ClosingFeerates]) extends PersistentChannelData diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index c1bd4531f8..85d54d3599 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -46,17 +46,10 @@ case class ChannelIdAssigned(channel: ActorRef, remoteNodeId: PublicKey, tempora /** * This event will be sent whenever a new scid is assigned to the channel, be it a real, local alias or remote alias. - * - * @param realShortChannelId_opt the real scid, it can change in case of a reorg before the channel reaches 6 conf - * @param localAlias we must remember the alias that we sent to our peer because we use it to: - * - identify incoming [[ChannelUpdate]] - * - route outgoing payments to that channel - * @param remoteAlias_opt we only remember the last alias received from our peer, we use this to generate - * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] */ -case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteAlias_opt: Option[ShortChannelId], remoteNodeId: PublicKey) extends ChannelEvent +case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, shortIds: ShortIds, remoteNodeId: PublicKey) extends ChannelEvent -case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent { +case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, shortIds: ShortIds, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent { /** * We always map the local alias because we must always be able to route based on it * However we only map the real scid if option_scid_alias (TODO: rename to option_scid_privacy) is disabled @@ -64,16 +57,16 @@ case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, realSh def scidsForRouting: Seq[ShortChannelId] = { commitments match { case c: Commitments => - val realScid_opt = if (c.channelFeatures.hasFeature(Features.ScidAlias)) None else realShortChannelId_opt - realScid_opt.toSeq :+ localAlias - case _ => Seq(localAlias) // TODO: ugly + val realScid_opt = if (c.channelFeatures.hasFeature(Features.ScidAlias)) None else shortIds.real.toOption + realScid_opt.toSeq :+ shortIds.localAlias + case _ => Seq(shortIds.localAlias) // TODO: ugly } } } case class ChannelUpdateParametersChanged(channel: ActorRef, channelId: ByteVector32, remoteNodeId: PublicKey, channelUpdate: ChannelUpdate) extends ChannelEvent -case class LocalChannelDown(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, remoteNodeId: PublicKey) extends ChannelEvent +case class LocalChannelDown(channel: ActorRef, channelId: ByteVector32, shortIds: ShortIds, remoteNodeId: PublicKey) extends ChannelEvent case class ChannelStateChanged(channel: ActorRef, channelId: ByteVector32, peer: ActorRef, remoteNodeId: PublicKey, previousState: ChannelState, currentState: ChannelState, commitments_opt: Option[AbstractCommitments]) extends ChannelEvent @@ -89,7 +82,7 @@ case class TransactionPublished(channelId: ByteVector32, remoteNodeId: PublicKey case class TransactionConfirmed(channelId: ByteVector32, remoteNodeId: PublicKey, tx: Transaction) extends ChannelEvent // NB: this event is only sent when the channel is available. -case class AvailableBalanceChanged(channel: ActorRef, channelId: ByteVector32, realShortChannelId_opt: Option[RealShortChannelId], localAlias: LocalAlias, commitments: AbstractCommitments) extends ChannelEvent +case class AvailableBalanceChanged(channel: ActorRef, channelId: ByteVector32, shortIds: ShortIds, commitments: AbstractCommitments) extends ChannelEvent case class ChannelPersisted(channel: ActorRef, remoteNodeId: PublicKey, channelId: ByteVector32, data: PersistentChannelData) extends ChannelEvent diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 0932acbb67..a2649ef69e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -199,14 +199,14 @@ object Helpers { * - min_depth > 0 : use real scid (may change if reorg between min_depth and 6 conf) * - min_depth = 0 (zero-conf) : unsupported */ - def scidForChannelUpdate(channelAnnouncement_opt: Option[ChannelAnnouncement], realShortChannelId_opt: Option[ShortChannelId], remoteAlias_opt: Option[ShortChannelId])(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { + def scidForChannelUpdate(channelAnnouncement_opt: Option[ChannelAnnouncement], shortIds: ShortIds)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { channelAnnouncement_opt.map(_.shortChannelId) // we use the real "final" scid when it is publicly announced - .orElse(remoteAlias_opt) // otherwise the remote alias - .orElse(realShortChannelId_opt) // if we don't have a remote alias, we use the real scid (which could change because the funding tx possibly has less than 6 confs here) + .orElse(shortIds.remoteAlias_opt) // otherwise the remote alias + .orElse(shortIds.real.toOption) // if we don't have a remote alias, we use the real scid (which could change because the funding tx possibly has less than 6 confs here) .getOrElse(throw new RuntimeException("this is a zero-conf channel and no alias was provided in channel_ready")) // if we don't have a real scid, it means this is a zero-conf channel and our peer must have sent an alias } - def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, realShortChannelId_opt = d.realShortChannelId_opt, remoteAlias_opt = d.remoteAlias_opt) + def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, d.shortIds) /** * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala index 57c906a1f6..c385f352ac 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala @@ -50,7 +50,7 @@ class Register extends Actor with ActorLogging { case scidAssigned: ShortChannelIdAssigned => // We map all known scids (real or alias) to the channel_id. The relayer is in charge of deciding whether a real // scid can be used or not for routing (see option_scid_privacy), but the register is neutral. - val m = (scidAssigned.realShortChannelId_opt.toSeq :+ scidAssigned.localAlias).map(_ -> scidAssigned.channelId).toMap + val m = (scidAssigned.shortIds.real.toOption.toSeq :+ scidAssigned.shortIds.localAlias).map(_ -> scidAssigned.channelId).toMap context become main(channels, shortIds ++ m, channelsTo) case Terminated(actor) if channels.values.toSet.contains(actor) => 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 75ea292b50..351e3683cc 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 @@ -299,7 +299,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case normal: DATA_NORMAL => watchFundingTx(data.commitments) - context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, realShortChannelId_opt = normal.realShortChannelId_opt, localAlias = normal.localAlias, remoteAlias_opt = normal.remoteAlias_opt, remoteNodeId = remoteNodeId)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, normal.channelId, normal.shortIds, remoteNodeId)) // we check the configuration because the values for channel_update may have changed while eclair was down val fees = getRelayFees(nodeParams, remoteNodeId, data.commitments) @@ -355,7 +355,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendAdd(d.commitments, c, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf) match { case Right((commitments1, add)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending add case Left(cause) => handleAddHtlcCommandError(c, cause, Some(d.channelUpdate)) } @@ -370,7 +370,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFulfill(d.commitments, c) match { case Right((commitments1, fulfill)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fulfill case Left(cause) => // we acknowledge the command right away in case of failure @@ -390,7 +390,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFail(d.commitments, c, nodeParams.privateKey) match { case Right((commitments1, fail)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fail case Left(cause) => // we acknowledge the command right away in case of failure @@ -401,7 +401,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFailMalformed(d.commitments, c) match { case Right((commitments1, fail)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fail case Left(cause) => // we acknowledge the command right away in case of failure @@ -424,7 +424,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Commitments.sendFee(d.commitments, c, nodeParams.onChainFeeConf) match { case Right((commitments1, fee)) => if (c.commit) self ! CMD_SIGN() - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) handleCommandSuccess(c, d.copy(commitments = commitments1)) sending fee case Left(cause) => handleCommandError(cause, c) } @@ -481,7 +481,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } if (d.commitments.availableBalanceForSend != commitments1.availableBalanceForSend) { // we send this event only when our balance changes - context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.realShortChannelId_opt, d.localAlias, commitments1)) + context.system.eventStream.publish(AvailableBalanceChanged(self, d.channelId, d.shortIds, commitments1)) } context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1)) stay() using d.copy(commitments = commitments1) storing() sending revocation @@ -614,18 +614,19 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(c: CurrentFeerates, d: DATA_NORMAL) => handleCurrentFeerate(c, d) case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => - val realShortChannelId = ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt) - log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=$realShortChannelId") - if (d.realShortChannelId_opt.isEmpty) { + val finalRealShortId = RealScidStatus.Final(ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt)) + val shortIds1 = d.shortIds.copy(real = finalRealShortId) + log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=${finalRealShortId.realScid}") + if (d.shortIds.real == RealScidStatus.Unknown) { // this is a zero-conf channel and it is the first time we know for sure that the funding tx has been confirmed context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) } - if (!d.realShortChannelId_opt.contains(realShortChannelId)) { - log.info(s"setting final real scid: old=${d.realShortChannelId_opt.getOrElse("empty")} new=$realShortChannelId") + if (!d.shortIds.real.toOption.contains(finalRealShortId.realScid)) { + log.info(s"setting final real scid: old=${d.shortIds.real} new=${finalRealShortId}") // we announce the new shortChannelId - context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, realShortChannelId_opt = Some(realShortChannelId), localAlias = d.localAlias, remoteAlias_opt = d.remoteAlias_opt, remoteNodeId = remoteNodeId)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortIds1, remoteNodeId)) } - val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.channelAnnouncement, realShortChannelId_opt = Some(realShortChannelId), remoteAlias_opt = d.remoteAlias_opt) + val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.channelAnnouncement, shortIds1) // if the shortChannelId is different from the one we had before, we need to re-announce it val channelUpdate1 = if (d.channelUpdate.shortChannelId != scidForChannelUpdate) { log.info(s"using new scid in channel_update: old=${d.channelUpdate.shortChannelId} new=$scidForChannelUpdate") @@ -634,50 +635,49 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } else d.channelUpdate val localAnnSigs_opt = if (d.commitments.announceChannel) { // if channel is public we need to send our announcement_signatures in order to generate the channel_announcement - Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, realShortChannelId)) + Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, finalRealShortId.realScid)) } else None // we use goto() instead of stay() because we want to fire transitions - goto(NORMAL) using d.copy(realShortChannelId_opt = Some(realShortChannelId), buried = true, channelUpdate = channelUpdate1) storing() sending localAnnSigs_opt.toSeq + goto(NORMAL) using d.copy(shortIds = shortIds1, channelUpdate = channelUpdate1) storing() sending localAnnSigs_opt.toSeq case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_NORMAL) if d.commitments.announceChannel => // channels are publicly announced if both parties want it (defined as feature bit) - if (d.buried) { - // must be defined if tx is buried - val shortChannelId = d.realShortChannelId_opt.get - // we are aware that the channel has reached enough confirmations - // we already had sent our announcement_signatures but we don't store them so we need to recompute it - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, shortChannelId) - d.channelAnnouncement match { - case None => - require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") - log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}") - import d.commitments.{localParams, remoteParams} - val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) - val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, fundingPubKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) - if (!Announcements.checkSigs(channelAnn)) { - handleLocalError(InvalidAnnouncementSignatures(d.channelId, remoteAnnSigs), d, Some(remoteAnnSigs)) - } else { - // we generate a new channel_update because the scid used may change if we were previously using an alias - val scidForChannelUpdate = Helpers.scidForChannelUpdate(Some(channelAnn), realShortChannelId_opt = d.realShortChannelId_opt, remoteAlias_opt = d.remoteAlias_opt) - val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) - // we use goto() instead of stay() because we want to fire transitions - goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate) storing() - } - case Some(_) => - // they have sent their announcement sigs, but we already have a valid channel announcement - // this can happen if our announcement_signatures was lost during a disconnection - // specs says that we "MUST respond to the first announcement_signatures message after reconnection with its own announcement_signatures message" - // current implementation always replies to announcement_signatures, not only the first time - // TODO: we should only be nice once, current behaviour opens way to DOS, but this should be handled higher in the stack anyway - log.info("re-sending our announcement sigs") - stay() sending localAnnSigs - } - } else { - // our watcher didn't notify yet that the tx has reached ANNOUNCEMENTS_MINCONF confirmations, let's delay remote's message - // note: no need to persist their message, in case of disconnection they will resend it - log.debug("received remote announcement signatures, delaying") - context.system.scheduler.scheduleOnce(5 seconds, self, remoteAnnSigs) - stay() + d.shortIds.real match { + case RealScidStatus.Final(realScid) => + // we are aware that the channel has reached enough confirmations + // we already had sent our announcement_signatures but we don't store them so we need to recompute it + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, realScid) + d.channelAnnouncement match { + case None => + require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") + log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}") + import d.commitments.{localParams, remoteParams} + val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) + val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, remoteParams.nodeId, fundingPubKey.publicKey, remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) + if (!Announcements.checkSigs(channelAnn)) { + handleLocalError(InvalidAnnouncementSignatures(d.channelId, remoteAnnSigs), d, Some(remoteAnnSigs)) + } else { + // we generate a new channel_update because the scid used may change if we were previously using an alias + val scidForChannelUpdate = Helpers.scidForChannelUpdate(Some(channelAnn), d.shortIds) + val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) + // we use goto() instead of stay() because we want to fire transitions + goto(NORMAL) using d.copy(channelAnnouncement = Some(channelAnn), channelUpdate = channelUpdate) storing() + } + case Some(_) => + // they have sent their announcement sigs, but we already have a valid channel announcement + // this can happen if our announcement_signatures was lost during a disconnection + // specs says that we "MUST respond to the first announcement_signatures message after reconnection with its own announcement_signatures message" + // current implementation always replies to announcement_signatures, not only the first time + // TODO: we should only be nice once, current behaviour opens way to DOS, but this should be handled higher in the stack anyway + log.info("re-sending our announcement sigs") + stay() sending localAnnSigs + } + case _ => + // our watcher didn't notify yet that the tx has reached ANNOUNCEMENTS_MINCONF confirmations, let's delay remote's message + // note: no need to persist their message, in case of disconnection they will resend it + log.debug("received remote announcement signatures, delaying") + context.system.scheduler.scheduleOnce(5 seconds, self, remoteAnnSigs) + stay() } case Event(c: CMD_UPDATE_RELAY_FEE, d: DATA_NORMAL) => @@ -1378,24 +1378,18 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val sendQueue = sendQueue :+ localShutdown } - if (!d.buried) { - // even if we were just disconnected/reconnected, we need to put back the watch because the event may have been - // fired while we were in OFFLINE (if not, the operation is idempotent anyway) - blockchain ! WatchFundingDeeplyBuried(self, d.commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) - } else { - // channel has been buried enough, should we (re)send our announcement sigs? - d.channelAnnouncement match { - case None if !d.commitments.announceChannel => - // that's a private channel, nothing to do - () - case None => + d.shortIds.real match { + case RealScidStatus.Final(realShortChannelId) => + // should we (re)send our announcement sigs? + if (d.commitments.announceChannel && d.channelAnnouncement.isEmpty) { // BOLT 7: a node SHOULD retransmit the announcement_signatures message if it has not received an announcement_signatures message - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, d.realShortChannelId_opt.get) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, realShortChannelId) sendQueue = sendQueue :+ localAnnSigs - case Some(_) => - // channel was already announced, nothing to do - () - } + } + case _ => + // even if we were just disconnected/reconnected, we need to put back the watch because the event may have been + // fired while we were in OFFLINE (if not, the operation is idempotent anyway) + blockchain ! WatchFundingDeeplyBuried(self, d.commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) } if (d.commitments.announceChannel) { @@ -1636,15 +1630,15 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } emitEvent_opt.foreach { case EmitLocalChannelUpdate(reason, d, sendToPeer) => - log.info(s"emitting channel update event: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.realShortChannelId_opt} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) - val lcu = LocalChannelUpdate(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments) + log.info(s"emitting channel update event: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.shortIds.real} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) + val lcu = LocalChannelUpdate(self, d.channelId, d.shortIds, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments) context.system.eventStream.publish(lcu) if (sendToPeer) { send(d.channelUpdate) } case EmitLocalChannelDown(d) => log.debug(s"emitting channel down event") - val lcd = LocalChannelDown(self, d.channelId, d.realShortChannelId_opt, d.localAlias, d.commitments.remoteParams.nodeId) + val lcd = LocalChannelDown(self, d.channelId, d.shortIds, d.commitments.remoteParams.nodeId) context.system.eventStream.publish(lcd) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index a4acc64aa7..5bbdbc8b5e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -366,17 +366,17 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) deferred.foreach(self ! _) // this is the real scid, it might change when the funding tx gets deeply buried (if there was a reorg in the meantime) - val realShortChannelId_opt = if (blockHeight == BlockHeight(0)) { + val realShortChannelIdStatus = if (blockHeight == BlockHeight(0)) { // If we are using zero-conf then the transaction may not have been confirmed yet, that's why the block // height is zero. In some cases (e.g. we were down for some time) the tx may actually have confirmed, in // that case we will have a real scid even if the channel is zero-conf log.info("skipping funding tx confirmation") - None + RealScidStatus.Unknown } else { log.info(s"channel was confirmed at blockHeight=$blockHeight txIndex=$txIndex") context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) - Some(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) + RealScidStatus.Temporary(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) } // the alias will use in our channel_update message, the goal is to be able to use our channel // as soon as it reaches NORMAL state, and before it is announced on the network @@ -389,9 +389,10 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { ) // we announce our identifiers as early as we can - context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = None, remoteNodeId = remoteNodeId)) + val shortIds = ShortIds(real = realShortChannelIdStatus, localAlias = localAlias, remoteAlias_opt = channelReady.alias_opt) + context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortIds, remoteNodeId = remoteNodeId)) - goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, channelReady) storing() sending channelReady + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = shortIds, channelReady) storing() sending channelReady case Failure(t) => log.error(t, s"rejecting channel with invalid funding tx: ${fundingTx.bin}") goto(CLOSED) @@ -427,22 +428,22 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { }) when(WAIT_FOR_CHANNEL_READY)(handleExceptions { - case Event(channelReady: ChannelReady, d@DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt, localAlias, _)) => - val remoteAlias_opt = channelReady.alias_opt - remoteAlias_opt.foreach { remoteAlias => + case Event(channelReady: ChannelReady, d: DATA_WAIT_FOR_CHANNEL_READY) => + val shortIds1 = d.shortIds.copy(remoteAlias_opt = channelReady.alias_opt) + shortIds1.remoteAlias_opt.foreach { remoteAlias => log.info("received remoteAlias={}", remoteAlias) - context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, realShortChannelId_opt = realShortChannelId_opt, localAlias = localAlias, remoteAlias_opt = Some(remoteAlias), remoteNodeId = remoteNodeId)) + context.system.eventStream.publish(ShortChannelIdAssigned(self, d.commitments.channelId, shortIds = shortIds1, remoteNodeId = remoteNodeId)) } // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced - val scidForChannelUpdate = Helpers.scidForChannelUpdate(channelAnnouncement_opt = None, realShortChannelId_opt = realShortChannelId_opt, remoteAlias_opt = remoteAlias_opt) + val scidForChannelUpdate = Helpers.scidForChannelUpdate(channelAnnouncement_opt = None, shortIds1) log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) - val relayFees = getRelayFees(nodeParams, remoteNodeId, commitments) - val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, relayFees.feeBase, relayFees.feeProportionalMillionths, commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) + val relayFees = getRelayFees(nodeParams, remoteNodeId, d.commitments) + val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, nodeParams.channelConf.expiryDelta, d.commitments.remoteParams.htlcMinimum, relayFees.feeBase, relayFees.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) // we need to periodically re-send channel updates, otherwise channel will be considered stale and get pruned by network context.system.scheduler.scheduleWithFixedDelay(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, delay = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh)) // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) - blockchain ! WatchFundingDeeplyBuried(self, commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) - goto(NORMAL) using DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), realShortChannelId_opt = realShortChannelId_opt, buried = false, None, initialChannelUpdate, localAlias = localAlias, remoteAlias_opt = remoteAlias_opt, None, None, None) storing() + blockchain ! WatchFundingDeeplyBuried(self, d.commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) + goto(NORMAL) using DATA_NORMAL(d.commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), shortIds = shortIds1, None, initialChannelUpdate, None, None, None) storing() case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_CHANNEL_READY) if d.commitments.announceChannel => log.debug("received remote announcement signatures, delaying") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 4a7c91b160..260168894b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -36,7 +36,7 @@ import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.MessageOnionCodecs.blindedRouteCodec import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, LocalAlias, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature, channel} import org.json4s import org.json4s.JsonAST._ import org.json4s.jackson.Serialization @@ -443,10 +443,14 @@ private[json] case class MessageReceivedJson(pathId: Option[ByteVector], encoded object OnionMessageReceivedSerializer extends ConvertClassSerializer[OnionMessages.ReceiveMessage](m => MessageReceivedJson(m.pathId, m.finalPayload.replyPath.map(route => blindedRouteCodec.encode(route.blindedRoute).require.bytes.toHex), m.finalPayload.replyPath.map(_.blindedRoute), m.finalPayload.records.unknown.map(tlv => tlv.tag.toString -> tlv.value).toMap)) // @formatter:on -case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { - val reverse: Map[String, Class[_]] = custom.map(_.swap) +// @formatter:off +/** this is cosmetic, just to not have a '_opt' field in json, which will only appear if the option is defined anyway */ +private case class ShortIdsJson(real: RealScidStatus, localAlias: LocalAlias, remoteAlias: Option[ShortChannelId]) +object ShortIdsSerializer extends ConvertClassSerializer[ShortIds](s => ShortIdsJson(s.real, s.localAlias, s.remoteAlias_opt)) +// @formatter:on - override def typeHintFieldName: String = "type" +case class CustomTypeHints(custom: Map[Class[_], String], override val typeHintFieldName: String = "type") extends TypeHints { + val reverse: Map[String, Class[_]] = custom.map(_.swap) override val hints: List[Class[_]] = custom.keys.toList @@ -503,6 +507,12 @@ object CustomTypeHints { classOf[DATA_CLOSING], classOf[DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT] ), typeHintFieldName = "type") + + val realScidStatuses: CustomTypeHints = CustomTypeHints(Map( + classOf[RealScidStatus.Unknown.type] -> "unknown", + classOf[RealScidStatus.Temporary] -> "temporary", + classOf[RealScidStatus.Final] -> "final", + ), typeHintFieldName = "status") } object JsonSerializers { @@ -516,6 +526,7 @@ object JsonSerializers { CustomTypeHints.onionMessageEvent + CustomTypeHints.channelSources + CustomTypeHints.channelStates + + CustomTypeHints.realScidStatuses + ByteVectorSerializer + ByteVector32Serializer + ByteVector64Serializer + @@ -562,6 +573,7 @@ object JsonSerializers { GlobalBalanceSerializer + PeerInfoSerializer + PaymentFailedSummarySerializer + - OnionMessageReceivedSerializer + OnionMessageReceivedSerializer + + ShortIdsSerializer } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala index 486944c2f5..320ead3ab2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala @@ -91,8 +91,8 @@ object ChannelRelayer { replyTo ! Relayer.OutgoingChannels(selected.toSeq) Behaviors.same - case WrappedLocalChannelUpdate(lcu@LocalChannelUpdate(_, channelId, realShortChannelId_opt, localAlias, remoteNodeId, _, channelUpdate, commitments)) => - context.log.debug(s"updating local channel info for channelId=$channelId realScid=$realShortChannelId_opt localAlias=$localAlias remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) + case WrappedLocalChannelUpdate(lcu@LocalChannelUpdate(_, channelId, shortIds, remoteNodeId, _, channelUpdate, commitments)) => + context.log.debug(s"updating local channel info for channelId=$channelId realScid=${shortIds.real} localAlias=${shortIds.localAlias} remoteNodeId=$remoteNodeId channelUpdate={} commitments={}", channelUpdate, commitments) val prevChannelUpdate = channels.get(channelId).map(_.channelUpdate) val channel = Relayer.OutgoingChannel(remoteNodeId, channelUpdate, prevChannelUpdate, commitments) val channels1 = channels + (channelId -> channel) @@ -102,17 +102,17 @@ object ChannelRelayer { val node2channels1 = node2channels.addOne(remoteNodeId, channelId) apply(nodeParams, register, channels1, scid2channels1, node2channels1) - case WrappedLocalChannelDown(LocalChannelDown(_, channelId, _, localAlias, remoteNodeId)) => - context.log.debug(s"removed local channel info for channelId=$channelId localAlias=$localAlias") + case WrappedLocalChannelDown(LocalChannelDown(_, channelId, shortIds, remoteNodeId)) => + context.log.debug(s"removed local channel info for channelId=$channelId localAlias=${shortIds.localAlias}") val channels1 = channels - channelId - val scid2Channels1 = scid2channels - localAlias + val scid2Channels1 = scid2channels - shortIds.localAlias val node2channels1 = node2channels.subtractOne(remoteNodeId, channelId) apply(nodeParams, register, channels1, scid2Channels1, node2channels1) - case WrappedAvailableBalanceChanged(AvailableBalanceChanged(_, channelId, _, localAlias, commitments)) => + case WrappedAvailableBalanceChanged(AvailableBalanceChanged(_, channelId, shortIds, commitments)) => val channels1 = channels.get(channelId) match { case Some(c: Relayer.OutgoingChannel) => - context.log.debug(s"available balance changed for channelId=$channelId localAlias=$localAlias availableForSend={} availableForReceive={}", commitments.availableBalanceForSend, commitments.availableBalanceForReceive) + context.log.debug(s"available balance changed for channelId=$channelId localAlias=${shortIds.localAlias} availableForSend={} availableForReceive={}", commitments.availableBalanceForSend, commitments.availableBalanceForReceive) channels + (channelId -> c.copy(commitments = commitments)) case None => channels // we only consider the balance if we have the channel_update } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala index 5951c26298..200a81ed2e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/RouteCalculation.scala @@ -71,8 +71,8 @@ object RouteCalculation { case _ => None } case Some(c: PrivateChannel) => currentNode match { - case c.nodeId1 => Some(ChannelDesc(c.localAlias, c.nodeId1, c.nodeId2)) - case c.nodeId2 => Some(ChannelDesc(c.localAlias, c.nodeId2, c.nodeId1)) + case c.nodeId1 => Some(ChannelDesc(c.shortIds.localAlias, c.nodeId1, c.nodeId2)) + case c.nodeId2 => Some(ChannelDesc(c.shortIds.localAlias, c.nodeId2, c.nodeId1)) case _ => None } case None => assistedChannels.get(shortChannelId).flatMap(c => currentNode match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 90d9b84c91..0752b0c087 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -342,7 +342,7 @@ object Router { } def apply(u: ChannelUpdate, pc: PrivateChannel): ChannelDesc = { // the least significant bit tells us if it is node1 or node2 - if (u.channelFlags.isNode1) ChannelDesc(pc.localAlias, pc.nodeId1, pc.nodeId2) else ChannelDesc(pc.localAlias, pc.nodeId2, pc.nodeId1) + if (u.channelFlags.isNode1) ChannelDesc(pc.shortIds.localAlias, pc.nodeId1, pc.nodeId2) else ChannelDesc(pc.shortIds.localAlias, pc.nodeId2, pc.nodeId1) } } case class ChannelMeta(balance1: MilliSatoshi, balance2: MilliSatoshi) @@ -379,7 +379,7 @@ object Router { case Right(rcu) => updateChannelUpdateSameSideAs(rcu.channelUpdate) } } - case class PrivateChannel(localAlias: LocalAlias, channelId: ByteVector32, localNodeId: PublicKey, remoteNodeId: PublicKey, update_1_opt: Option[ChannelUpdate], update_2_opt: Option[ChannelUpdate], meta: ChannelMeta) extends KnownChannel { + case class PrivateChannel(channelId: ByteVector32, shortIds: ShortIds, localNodeId: PublicKey, remoteNodeId: PublicKey, update_1_opt: Option[ChannelUpdate], update_2_opt: Option[ChannelUpdate], meta: ChannelMeta) extends KnownChannel { val (nodeId1, nodeId2) = if (Announcements.isNode1(localNodeId, remoteNodeId)) (localNodeId, remoteNodeId) else (remoteNodeId, localNodeId) val capacity: Satoshi = (meta.balance1 + meta.balance2).truncateToSatoshi @@ -404,7 +404,7 @@ object Router { case (Some(localUpdate), Some(remoteUpdate)) => // this is tricky: for incoming payments we need the *remote alias*, we can find it in the channel_update that we sent them Some(ExtraHop(remoteNodeId, localUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) - case (_, Some(remoteUpdate)) if remoteUpdate.shortChannelId != localAlias => + case (_, Some(remoteUpdate)) if remoteUpdate.shortChannelId != shortIds.localAlias => // they are using a real scid (otherwise it would match our local alias, we can use it in the routing hint) Some(ExtraHop(remoteNodeId, remoteUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) case _ => None diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 8ec6014085..00a31db4da 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -202,7 +202,7 @@ object Validation { // we remove the corresponding unannounced channel that we may have until now privateChannels = d.privateChannels - pubChan.channelId, // we also remove the scid -> channelId mappings - scid2PrivateChannels = d.scid2PrivateChannels - pubChan.shortChannelId -- privChan_opt.map(_.localAlias), + scid2PrivateChannels = d.scid2PrivateChannels - pubChan.shortChannelId -- privChan_opt.map(_.shortIds.localAlias), // we also add the newly validated channels to the rebroadcast queue rebroadcast = d.rebroadcast.copy( // we rebroadcast the channel to our peers @@ -302,7 +302,7 @@ object Validation { def handleChannelUpdate(d: Data, db: NetworkDb, routerConf: RouterConf, update: Either[LocalChannelUpdate, RemoteChannelUpdate], wasStashed: Boolean = false)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors val (pc_opt: Option[KnownChannel], u: ChannelUpdate, origins: Set[GossipOrigin]) = update match { - case Left(lcu) => (d.resolve(lcu.channelId, lcu.realShortChannelId_opt), lcu.channelUpdate, Set(LocalGossip)) + case Left(lcu) => (d.resolve(lcu.channelId, lcu.shortIds.real.toOption), lcu.channelUpdate, Set(LocalGossip)) case Right(rcu) => rcu.origins.collect { case RemoteGossip(peerConnection, _) if !wasStashed => // stashed changes have already been acknowledged @@ -462,22 +462,22 @@ object Validation { */ def handleShortChannelIdAssigned(d: Data, localNodeId: PublicKey, scia: ShortChannelIdAssigned)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - // NB: we don't map remote aliases because they are decided our peer and could overlap with ours - val mappings = scia.realShortChannelId_opt match { - case Some(realScid) => Map(realScid -> scia.channelId, scia.localAlias -> scia.channelId) - case None => Map(scia.localAlias -> scia.channelId) + // NB: we don't map remote aliases because they are decided by our peer and could overlap with ours + val mappings = scia.shortIds.real.toOption match { + case Some(realScid) => Map(realScid -> scia.channelId, scia.shortIds.localAlias -> scia.channelId) + case None => Map(scia.shortIds.localAlias -> scia.channelId) } log.debug("handleShortChannelIdAssigned scia={} mappings={}", scia, mappings) val d1 = d.copy(scid2PrivateChannels = d.scid2PrivateChannels ++ mappings) - d1.resolve(scia.channelId, scia.realShortChannelId_opt) match { + d1.resolve(scia.channelId, scia.shortIds.real.toOption) match { case Some(_) => // channel is known, nothing more to do d1 case None => // this is a local channel that hasn't yet been announced (maybe it is a private channel or maybe it is a public // channel that doesn't yet have 6 confirmations), we create a corresponding private channel - val pc = PrivateChannel(scia.localAlias, scia.channelId, localNodeId, scia.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)) - log.debug("adding unannounced local channel to remote={} channelId={} localAlias={}", scia.remoteNodeId, scia.channelId, scia.localAlias) + val pc = PrivateChannel(scia.channelId, scia.shortIds, localNodeId, scia.remoteNodeId, None, None, ChannelMeta(0 msat, 0 msat)) + log.debug("adding unannounced local channel to remote={} channelId={} localAlias={}", scia.remoteNodeId, scia.channelId, scia.shortIds.localAlias) d1.copy(privateChannels = d1.privateChannels + (scia.channelId -> pc)) } } @@ -486,7 +486,7 @@ object Validation { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors import nodeParams.db.{network => db} log.debug("handleLocalChannelUpdate lcu={}", lcu) - d.resolve(lcu.channelId, lcu.realShortChannelId_opt) match { + d.resolve(lcu.channelId, lcu.shortIds.real.toOption) match { case Some(publicChannel: PublicChannel) => // this a known public channel, we can process the channel_update log.debug("this is a known public channel, processing channel_update publicChannel={}", publicChannel) @@ -512,7 +512,7 @@ object Validation { } case None => // should never happen, we log a warning and handle the update, it will be rejected since there is no related channel - log.warning("unrecognized local chanel update for channelId={} localAlias={}", lcu.channelId, lcu.localAlias) + log.warning("unrecognized local chanel update for channelId={} localAlias={}", lcu.channelId, lcu.shortIds.localAlias) handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) } } @@ -520,15 +520,15 @@ object Validation { def handleLocalChannelDown(d: Data, localNodeId: PublicKey, lcd: LocalChannelDown)(implicit log: LoggingAdapter): Data = { import lcd.{channelId, remoteNodeId} log.debug("handleLocalChannelDown lcd={}", lcd) - val scid2PrivateChannels1 = d.scid2PrivateChannels - lcd.localAlias -- lcd.realShortChannelId_opt + val scid2PrivateChannels1 = d.scid2PrivateChannels - lcd.shortIds.localAlias -- lcd.shortIds.real.toOption // a local channel has permanently gone down - if (lcd.realShortChannelId_opt.exists(d.channels.contains)) { + if (lcd.shortIds.real.toOption.exists(d.channels.contains)) { // the channel was public, we will receive (or have already received) a WatchEventSpentBasic event, that will trigger a clean up of the channel // so let's not do anything here d.copy(scid2PrivateChannels = scid2PrivateChannels1) } else if (d.privateChannels.contains(lcd.channelId)) { // the channel was private or public-but-not-yet-announced, let's do the clean up - val localAlias = d.privateChannels(channelId).localAlias + val localAlias = d.privateChannels(channelId).shortIds.localAlias log.info("removing private local channel and channel_update for channelId={} localAlias={}", channelId, localAlias) // we remove the corresponding updates from the graph val graphWithBalances1 = d.graphWithBalances @@ -542,7 +542,7 @@ object Validation { } def handleAvailableBalanceChanged(d: Data, e: AvailableBalanceChanged)(implicit log: LoggingAdapter): Data = { - val (publicChannels1, graphWithBalances1) = e.realShortChannelId_opt.flatMap(d.channels.get) match { + val (publicChannels1, graphWithBalances1) = e.shortIds.real.toOption.flatMap(d.channels.get) match { case Some(pc) => val pc1 = pc.updateBalances(e.commitments) log.debug("public channel balance updated: {}", pc1) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 793c2f2aee..6a2b8f6c62 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -374,7 +374,7 @@ private[channel] object ChannelCodecs0 { ("shortChannelId" | realshortchannelid) :: ("lastSent" | channelReadyCodec)).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId), localAlias = shortChannelId.toAlias, lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val shutdownCodec: Codec[Shutdown] = ( @@ -393,7 +393,7 @@ private[channel] object ChannelCodecs0 { ("remoteShutdown" | optional(bool, shutdownCodec)) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( @@ -406,7 +406,7 @@ private[channel] object ChannelCodecs0 { ("remoteShutdown" | optional(bool, shutdownCodec)) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index 02882eeb0a..e114ca14b3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -241,15 +241,15 @@ private[channel] object ChannelCodecs1 { val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool8) :: ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: @@ -257,7 +257,7 @@ private[channel] object ChannelCodecs1 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index c41d9f26be..1a725b18f3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -276,15 +276,15 @@ private[channel] object ChannelCodecs2 { val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool8) :: ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: @@ -292,7 +292,7 @@ private[channel] object ChannelCodecs2 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index 6c0d4daf8b..c7152c5c19 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -325,21 +325,20 @@ private[channel] object ChannelCodecs3 { val DATA_WAIT_FOR_CHANNEL_READY_COMPAT_01_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), localAlias = shortChannelId.toAlias, lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( ("commitments" | commitmentsCodec) :: - ("realShortChannelId_opt" | optional(bool8, realshortchannelid)) :: - ("localAlias" | localalias) :: + ("shortIds" | shortids) :: ("lastSent" | lengthDelimited(channelReadyCodec))).as[DATA_WAIT_FOR_CHANNEL_READY] val DATA_NORMAL_COMPAT_02_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool8) :: ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: @@ -347,12 +346,12 @@ private[channel] object ChannelCodecs3 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_COMPAT_07_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("shortChannelId" | shortchannelid) :: + ("shortChannelId" | realshortchannelid) :: ("buried" | bool8) :: ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: @@ -360,17 +359,14 @@ private[channel] object ChannelCodecs3 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | optional(bool8, closingFeeratesCodec))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, realShortChannelId_opt = Some(shortChannelId.toReal), buried = buried, channelAnnouncement, channelUpdate, localAlias = shortChannelId.toAlias, remoteAlias_opt = None, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( ("commitments" | commitmentsCodec) :: - ("realShortChannelId_opt" | optional(bool8, realshortchannelid)) :: - ("buried" | bool8) :: + ("shortids" | shortids) :: ("channelAnnouncement" | optional(bool8, lengthDelimited(channelAnnouncementCodec))) :: ("channelUpdate" | lengthDelimited(channelUpdateCodec)) :: - ("localAlias" | discriminated[LocalAlias].by(uint16).typecase(1, localalias)) :: // forward-compatible with listOfN(uint16, localalias) in case we want to store a list of local aliases later - ("remoteAlias" | optional(bool8, shortchannelid)) :: ("localShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | optional(bool8, closingFeeratesCodec))).as[DATA_NORMAL] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 2efa06c9cb..3265e4cab5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -18,10 +18,10 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transaction} -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64} import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.channel.ChannelFlags +import fr.acinq.eclair.channel.{ChannelFlags, RealScidStatus, ShortIds} import fr.acinq.eclair.crypto.Mac32 +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, channel} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -136,6 +136,17 @@ object CommonCodecs { val localalias: Codec[LocalAlias] = shortchannelid.narrow[LocalAlias](scid => Attempt.successful(scid.toAlias), scid => scid) + val realShortChannelIdStatus: Codec[RealScidStatus] = discriminated[RealScidStatus].by(uint8) + .typecase(0, provide(RealScidStatus.Unknown)) + .typecase(1, realshortchannelid.as[channel.RealScidStatus.Temporary]) + .typecase(2, realshortchannelid.as[channel.RealScidStatus.Final]) + + val shortids: Codec[ShortIds] = ( + ("real" | realShortChannelIdStatus) :: + ("localAlias" | discriminated[LocalAlias].by(uint16).typecase(1, localalias)) :: // forward-compatible with listOfN(uint16, localalias) in case we want to store a list of local aliases later + ("remoteAlias_opt" | optional(bool8, shortchannelid)) + ).as[ShortIds] + val privateKey: Codec[PrivateKey] = Codec[PrivateKey]( (priv: PrivateKey) => bytes(32).encode(priv.value), (wire: BitVector) => bytes(32).decode(wire).map(_.map(b => PrivateKey(b))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 87bac3dc40..22834b04ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{MilliSatoshiLong, ShortChannelId, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{MilliSatoshiLong, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -88,7 +88,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady") { f => import f._ // we have a real scid at this stage, because this isn't a zero-conf channel - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.nonEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate @@ -103,7 +103,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady (no alias)") { f => import f._ // we have a real scid at this stage, because this isn't a zero-conf channel - val realScid = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.get + val realScid = alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid val channelReady = bob2alice.expectMsgType[ChannelReady] val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) @@ -119,7 +119,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady (zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate @@ -134,7 +134,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady (zero-conf, no alias)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) @@ -148,7 +148,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady (public)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ // we have a real scid at this stage, because this isn't a zero-conf channel - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.nonEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.channelFlags.announceChannel) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) @@ -164,7 +164,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu test("recv ChannelReady (public, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => import f._ // zero-conf channel: we don't have a real scid - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 7c26e85ae3..f71bbab9aa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -173,7 +173,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF // the zero-conf watch confirms instantly alice ! WatchFundingConfirmedTriggered(BlockHeight(0), 0, fundingTx) awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) } test("recv WatchFundingConfirmedTriggered (fundee)") { f => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index d98110c6ed..0987e2018d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3402,14 +3402,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingDeeplyBuriedTriggered (public channel)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ - val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(annSigs.shortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) // public channel: we prefer the real scid alias and it hasn't changed, so we don't send a new channel_update alice2bob.expectNoMessage(1 second) // we don't re-publish the same channel_update if there was no change @@ -3419,7 +3419,7 @@ 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 - assert(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) // funding tx coordinates (unknown before) val (blockHeight, txIndex) = (BlockHeight(400000), 42) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) @@ -3427,14 +3427,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(annSigs.shortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures channelUpdateListener.expectNoMessage(1 second) } test("recv WatchFundingDeeplyBuriedTriggered (public channel, short channel id changed)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ - val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].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) @@ -3444,19 +3444,19 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigs.shortChannelId == newRealShortChannelId) // update data with real short channel id - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(newRealShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId)) // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures channelUpdateListener.expectNoMessage(1 second) } test("recv WatchFundingDeeplyBuriedTriggered (private channel)") { f => import f._ - val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) // update data with real short channel id - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(realShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one alice2bob.expectNoMessage() // we don't re-publish the same channel_update if there was no change @@ -3469,11 +3469,11 @@ 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 - assert(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42000), 42, null) val realShortChannelId = ShortChannelId(BlockHeight(42000), 42, 0) // update data with real short channel id - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(realShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one alice2bob.expectNoMessage() // this is the first time we know the funding tx has been confirmed @@ -3484,7 +3484,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingDeeplyBuriedTriggered (private channel, short channel id changed)") { f => import f._ - val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get + val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].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) @@ -3492,7 +3492,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice ! WatchFundingDeeplyBuriedTriggered(blockHeight1, txIndex1, null) val newRealShortChannelId = ShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) // update data with real short channel id - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.contains(newRealShortChannelId) && alice.stateData.asInstanceOf[DATA_NORMAL].buried) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one alice2bob.expectNoMessage() // we don't re-publish the same channel_update if there was no change @@ -3502,7 +3502,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv AnnouncementSignatures", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val realShortChannelId = initialState.realShortChannelId_opt.get + val realShortChannelId = initialState.shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) @@ -3514,14 +3514,14 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) // actual test starts here bob2alice.forward(alice) - awaitCond({ + awaitAssert { val normal = alice.stateData.asInstanceOf[DATA_NORMAL] - normal.realShortChannelId_opt.contains(annSigsA.shortChannelId) && normal.buried && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId - }) + assert(normal.shortIds.real == RealScidStatus.Final(annSigsA.shortChannelId) && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId) + } // we use the real scid instead of remote alias as soon as the channel is announced val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate] assert(lcu.channelUpdate.shortChannelId == realShortChannelId) - assert(lcu.channelAnnouncement_opt == Some(channelAnn)) + assert(lcu.channelAnnouncement_opt.contains(channelAnn)) // we don't send directly the channel_update to our peer, public announcements are handled by the router alice2bob.expectNoMessage(100 millis) } @@ -3529,7 +3529,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv AnnouncementSignatures (re-send)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val realShortChannelId = initialState.realShortChannelId_opt.get + val realShortChannelId = initialState.shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) @@ -3540,7 +3540,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import initialState.commitments.{localParams, remoteParams} val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement == Some(channelAnn)) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.contains(channelAnn)) // actual test starts here // simulate bob re-sending its sigs diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala index 801988a4a1..b75e8443b0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala @@ -20,6 +20,7 @@ import com.softwaremill.quicklens._ import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.eclair.TestDatabases.{TestPgDatabases, TestSqliteDatabases, migrationCheck} import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.channel.RealScidStatus import fr.acinq.eclair.db.ChannelsDbSpec.{getPgTimestamp, getTimestamp, testCases} import fr.acinq.eclair.db.DbEventHandler.ChannelEvent import fr.acinq.eclair.db.jdbc.JdbcUtils.using @@ -61,7 +62,7 @@ class ChannelsDbSpec extends AnyFunSuite { val channel1 = ChannelCodecsSpec.normal val channel2a = ChannelCodecsSpec.normal.modify(_.commitments.channelId).setTo(randomBytes32()) - val channel2b = channel2a.modify(_.realShortChannelId_opt).setTo(Some(realScid(189371))) + val channel2b = channel2a.modify(_.shortIds.real).setTo(RealScidStatus.Final(realScid(189371))) val commitNumber = 42 val paymentHash1 = ByteVector32.Zeroes diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index e1df7768a2..e0d7bcca0b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -179,11 +179,11 @@ class PaymentIntegrationSpec extends IntegrationSpec { val shortIdBC = sender.expectMsgType[Iterable[ChannelAnnouncement]].find(c => Set(c.nodeId1, c.nodeId2) == Set(nodes("B").nodeParams.nodeId, nodes("C").nodeParams.nodeId)).get.shortChannelId // we also need the full commitment nodes("B").register ! Register.ForwardShortId(sender.ref, shortIdBC, CMD_GET_CHANNEL_INFO(ActorRef.noSender)) - val commitmentBC = sender.expectMsgType[RES_GET_CHANNEL_INFO].data.asInstanceOf[DATA_NORMAL].commitments + val normalBC = sender.expectMsgType[RES_GET_CHANNEL_INFO].data.asInstanceOf[DATA_NORMAL] // we then forge a new channel_update for B-C... val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.channelConf.expiryDelta + 1, nodes("C").nodeParams.channelConf.htlcMinimum, nodes("B").nodeParams.relayParams.publicChannelFees.feeBase, nodes("B").nodeParams.relayParams.publicChannelFees.feeProportionalMillionths, 500000000 msat) // ...and notify B's relayer - nodes("B").system.eventStream.publish(LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, Some(shortIdBC), null, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC)) + nodes("B").system.eventStream.publish(LocalChannelUpdate(system.deadLetters, normalBC.channelId, normalBC.shortIds, normalBC.commitments.remoteParams.nodeId, normalBC.channelAnnouncement, channelUpdateBC, normalBC.commitments)) // we retrieve a payment hash from D val amountMsat = 4200000.msat sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), Left("1 coffee"))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala index 707317fcd3..89598db9e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/TwoNodesIntegrationSpec.scala @@ -1,7 +1,7 @@ package fr.acinq.eclair.integration.basic import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} -import fr.acinq.eclair.channel.{DATA_NORMAL, NORMAL} +import fr.acinq.eclair.channel.{DATA_NORMAL, NORMAL, RealScidStatus} import fr.acinq.eclair.integration.basic.fixtures.TwoNodesFixture import fr.acinq.eclair.testutils.FixtureSpec import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong} @@ -61,8 +61,8 @@ class TwoNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { val channelId = openChannel(alice, bob, 100_000 sat).channelId confirmChannel(alice, bob, channelId, BlockHeight(420_000), 21) confirmChannelDeep(alice, bob, channelId, BlockHeight(420_000), 21) - assert(getChannelData(alice, channelId).asInstanceOf[DATA_NORMAL].buried) - assert(getChannelData(bob, channelId).asInstanceOf[DATA_NORMAL].buried) + assert(getChannelData(alice, channelId).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Final]) + assert(getChannelData(bob, channelId).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Final]) } test("open a channel alice-bob and confirm deeply (autoconfirm)") { f => @@ -72,8 +72,8 @@ class TwoNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { connect(alice, bob) val channelId = openChannel(alice, bob, 100_000 sat).channelId eventually { - assert(getChannelData(alice, channelId).asInstanceOf[DATA_NORMAL].buried) - assert(getChannelData(bob, channelId).asInstanceOf[DATA_NORMAL].buried) + assert(getChannelData(alice, channelId).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Final]) + assert(getChannelData(bob, channelId).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Final]) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index 2bfcf84bcb..ebc2823a4c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -4,7 +4,7 @@ import com.softwaremill.quicklens._ import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.{ScidAlias, ZeroConf} -import fr.acinq.eclair.channel.DATA_NORMAL +import fr.acinq.eclair.channel.{DATA_NORMAL, RealScidStatus} import fr.acinq.eclair.integration.basic.fixtures.ThreeNodesFixture import fr.acinq.eclair.payment.PaymentSent import fr.acinq.eclair.testutils.FixtureSpec @@ -74,7 +74,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic: Boolean, bcZeroConf: Boolean, bcScidAlias: Boolean, - bcHasRealScid: Boolean, paymentWorksWithoutHint: Boolean, paymentWorksWithHint_opt: Option[Boolean], paymentWorksWithRealScidHint_opt: Option[Boolean], @@ -87,10 +86,13 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf) == bcZeroConf) assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ScidAlias) == bcScidAlias) assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFlags.announceChannel == bcPublic) - assert(getChannelData(alice, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_ab).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined) - assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined == bcHasRealScid) - assert(getChannelData(carol, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isDefined == bcHasRealScid) + if (deepConfirm) { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Final]) + } else if (bcZeroConf) { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) + } else { + assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) + } } if (bcPublic && deepConfirm) { @@ -122,10 +124,10 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience if (paymentWorksWithRealScidHint_opt.contains(true)) { // if alice uses the real scid instead of the b-c alias, it still works - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.get)) } else if (paymentWorksWithRealScidHint_opt.contains(false)) { intercept[AssertionError] { - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].realShortChannelId_opt.get)) + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.get)) } } else { // skipped @@ -138,7 +140,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = false, bcScidAlias = false, - bcHasRealScid = true, // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works @@ -157,7 +158,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = false, bcScidAlias = true, - bcHasRealScid = true, // a-b and b-c are in NORMAL and have real scids (the funding tx has reached min_depth) paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias @@ -170,7 +170,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = true, bcScidAlias = false, - bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet @@ -183,7 +182,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = true, bcScidAlias = false, - bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = None // skipped, see below @@ -200,7 +198,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = true, bcScidAlias = true, - bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias @@ -213,7 +210,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = true, bcZeroConf = true, bcScidAlias = false, - bcHasRealScid = false, // a-b has reached min_depth and has a real scid, b-c is in NORMAL state too, but the funding tx isn't confirmed (zero-conf): it doesn't have a real scid paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet @@ -226,7 +222,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = true, bcZeroConf = true, bcScidAlias = false, - bcHasRealScid = true, // both channels have real scids because they are deeply confirmed, even the zeroconf channel paymentWorksWithoutHint = true, paymentWorksWithHint_opt = None, // there is no routing hints for public channels paymentWorksWithRealScidHint_opt = None // there is no routing hints for public channels diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 2c279453d0..b069290d02 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -160,7 +160,7 @@ object MinimalNodeFixture extends Assertions { node.wallet.funded(fundingTxid) } - def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): Option[RealShortChannelId] = { + def confirmChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): Option[RealScidStatus.Temporary] = { assert(getChannelState(node1, channelId) == WAIT_FOR_FUNDING_CONFIRMED) val data1Before = getChannelData(node1, channelId).asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED] val fundingTx = data1Before.fundingTx.get @@ -184,14 +184,14 @@ object MinimalNodeFixture extends Assertions { // hasn't been confirmed yet and doesn't have a real scid None } else { - assert(data1After.realShortChannelId_opt.isDefined && data2After.realShortChannelId_opt.isDefined) - assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) - assert(!data1After.buried && !data2After.buried) - Some(data1After.realShortChannelId_opt.get) + val realScid1 = data1After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] + val realScid2 = data2After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] + assert(realScid1 == realScid2) + Some(realScid1) } } - def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealShortChannelId = { + def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealScidStatus.Final = { assert(getChannelState(node1, channelId) == NORMAL) val data1Before = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val fundingTxid = data1Before.commitments.commitInput.outPoint.txid @@ -208,10 +208,10 @@ object MinimalNodeFixture extends Assertions { val data1After = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val data2After = getChannelData(node2, channelId).asInstanceOf[DATA_NORMAL] - assert(data1After.realShortChannelId_opt.get == data2After.realShortChannelId_opt.get) - assert(data1After.buried && data2After.buried) - - data1After.realShortChannelId_opt.get + val realScid1 = data1After.shortIds.real.asInstanceOf[RealScidStatus.Final] + val realScid2 = data2After.shortIds.real.asInstanceOf[RealScidStatus.Final] + assert(realScid1 == realScid2) + realScid1 } /** Utility method to make sure that the channel has processed all previous messages */ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index a86d2e6bb3..2c859a8b3b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -306,6 +306,20 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { JsonSerializers.serialization.write(map)(JsonSerializers.formats) shouldBe s"""{"e2fc57221cfb1942224082174022f3f70a32005aa209956f9c94c6903f7669ff":"ok","8e3ec6e16436b7dc61b86340192603d05f16d4f8e06c8aaa02fbe2ad63209af3":"cannot execute command=CMD_UPDATE_RELAY_FEE in state=CLOSING","74ca7a86e52d597aa2248cd2ff3b24428ede71345262be7fb31afddfe18dc0d8":"channel 74ca7a86e52d597aa2248cd2ff3b24428ede71345262be7fb31afddfe18dc0d8 not found"}""" } + test("serialize short ids") { + val testCases = Map( + ShortIds(real = RealScidStatus.Unknown, localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = Some(ShortChannelId("7x7x7"))) -> + """{"real":{"status":"unknown"},"localAlias":"1x2x3","remoteAlias":"7x7x7"}""", + ShortIds(real = RealScidStatus.Temporary(ShortChannelId("500000x42x1").toReal), localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = None) -> + """{"real":{"status":"temporary","realScid":"500000x42x1"},"localAlias":"1x2x3"}""", + ShortIds(real = RealScidStatus.Final(ShortChannelId("500000x42x1").toReal), localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = None) -> + """{"real":{"status":"final","realScid":"500000x42x1"},"localAlias":"1x2x3"}""", + ) + for ((obj, json) <- testCases) { + JsonSerializers.serialization.write(obj)(JsonSerializers.formats) shouldBe json + } + } + /** utility method that strips line breaks in the expected json */ def assertJsonEquals(actual: String, expected: String) = { val cleanedExpected = expected diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index 2f53c48740..fbc506c7b7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -20,6 +20,7 @@ import akka.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe} import akka.actor.typed import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.adapter.TypedActorRefOps +import com.softwaremill.quicklens.ModifyPimp import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong} @@ -128,7 +129,6 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a test("relay with new real scid after reorg") { f => import f._ - import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv._ // initial channel update val lcu1 = createLocalUpdate(channelId1) @@ -140,7 +140,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a // reorg happens val realScid1AfterReorg = ShortChannelId(111112).toReal - val lcu2 = createLocalUpdate(channelId1).copy(realShortChannelId_opt = Some(realScid1AfterReorg)) + val lcu2 = createLocalUpdate(channelId1).modify(_.shortIds.real).setTo(RealScidStatus.Final(realScid1AfterReorg)) val payload2 = RelayLegacyPayload(realScid1AfterReorg, outgoingAmount, outgoingExpiry) val r2 = createValidIncomingPacket(payload2) channelRelayer ! WrappedLocalChannelUpdate(lcu2) @@ -148,7 +148,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a // both old and new real scids work channelRelayer ! Relay(r1) expectFwdAdd(register, lcu1.channelId, outgoingAmount, outgoingExpiry) - // new real scid works + // new real scid works channelRelayer ! Relay(r2) expectFwdAdd(register, lcu2.channelId, outgoingAmount, outgoingExpiry) } @@ -231,7 +231,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val payload = RelayLegacyPayload(realScid1, outgoingAmount, outgoingExpiry) val r = createValidIncomingPacket(payload) val u = createLocalUpdate(channelId1) - val d = LocalChannelDown(null, channelId = channelIds(realScid1), Some(realScid1.toReal), null, outgoingNodeId) + val d = LocalChannelDown(null, channelId1, createShortIds(channelId1), outgoingNodeId) channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! WrappedLocalChannelDown(d) @@ -372,7 +372,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val channelId = randomBytes32() val update = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey(), remoteNodeId, shortChannelId, CltvExpiryDelta(10), 100 msat, 1000 msat, 100, capacity.toMilliSatoshi) val commitments = PaymentPacketSpec.makeCommitments(channelId, availableBalanceForSend, testCapacity = capacity) - LocalChannelUpdate(null, channelId, Some(shortChannelId), null, remoteNodeId, None, update, commitments) + val shortIds = ShortIds(real = RealScidStatus.Final(shortChannelId), localAlias = ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + LocalChannelUpdate(null, channelId, shortIds, remoteNodeId, None, update, commitments) } val (a, b) = (randomKey().publicKey, randomKey().publicKey) @@ -515,8 +516,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a import f._ val channelId_ab = randomBytes32() val channelId_bc = randomBytes32() - val alias_ab = ShortChannelId.generateLocalAlias() - val alias_bc = ShortChannelId.generateLocalAlias() + val shortIds_ab = ShortIds(RealScidStatus.Final(channelUpdate_ab.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + val shortIds_bc = ShortIds(RealScidStatus.Final(channelUpdate_bc.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) val a = PaymentPacketSpec.a val sender = TestProbe[Relayer.OutgoingChannels]() @@ -527,8 +528,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channels } - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, -2000 msat, 300000 msat))) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_bc, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, shortIds_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, -2000 msat, 300000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_bc, shortIds_bc, c, None, channelUpdate_bc, makeCommitments(channelId_bc, 400000 msat, -5000 msat))) val channels1 = getOutgoingChannels(true) assert(channels1.size == 2) @@ -537,22 +538,22 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a assert(channels1.last.channelUpdate == channelUpdate_bc) assert(channels1.last.toChannelBalance == Relayer.ChannelBalance(c, channelUpdate_bc.shortChannelId, 400000 msat, 0 msat, isPublic = false, isEnabled = true)) - channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, makeCommitments(channelId_bc, 200000 msat, 500000 msat))) + channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_bc, shortIds_ab, makeCommitments(channelId_bc, 200000 msat, 500000 msat))) val channels2 = getOutgoingChannels(true) assert(channels2.last.commitments.availableBalanceForReceive == 500000.msat && channels2.last.commitments.availableBalanceForSend == 200000.msat) - channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) - channelRelayer ! WrappedLocalChannelDown(LocalChannelDown(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), alias_ab, c)) + channelRelayer ! WrappedAvailableBalanceChanged(AvailableBalanceChanged(null, channelId_ab, shortIds_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelDown(LocalChannelDown(null, channelId_bc, shortIds_ab, c)) val channels3 = getOutgoingChannels(true) assert(channels3.size == 1 && channels3.head.commitments.availableBalanceForSend == 100000.msat) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab.copy(channelFlags = ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = true)), makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, shortIds_ab, a, None, channelUpdate_ab.copy(channelFlags = ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = true)), makeCommitments(channelId_ab, 100000 msat, 200000 msat))) val channels4 = getOutgoingChannels(true) assert(channels4.isEmpty) val channels5 = getOutgoingChannels(false) assert(channels5.size == 1) - channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, Some(channelUpdate_ab.shortChannelId.toReal), alias_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) + channelRelayer ! WrappedLocalChannelUpdate(LocalChannelUpdate(null, channelId_ab, shortIds_ab, a, None, channelUpdate_ab, makeCommitments(channelId_ab, 100000 msat, 200000 msat))) val channels6 = getOutgoingChannels(true) assert(channels6.size == 1) } @@ -590,13 +591,18 @@ object ChannelRelayerSpec { ChannelRelayPacket(add_ab, payload, emptyOnionPacket) } - def createLocalUpdate(channelId: ByteVector32, channelUpdateScid_opt: Option[ShortChannelId] = None, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100, optionScidAlias: Boolean = false): LocalChannelUpdate = { + def createShortIds(channelId: ByteVector32) = { val realScid = channelIds.collectFirst { case (realScid: RealShortChannelId, cid) if cid == channelId => realScid }.get val localAlias = channelIds.collectFirst { case (localAlias: LocalAlias, cid) if cid == channelId => localAlias }.get - val channelUpdateScid = channelUpdateScid_opt.getOrElse(realScid) + ShortIds(real = RealScidStatus.Final(realScid), localAlias, remoteAlias_opt = None) + } + + def createLocalUpdate(channelId: ByteVector32, channelUpdateScid_opt: Option[ShortChannelId] = None, balance: MilliSatoshi = 10000000 msat, capacity: Satoshi = 500000 sat, enabled: Boolean = true, htlcMinimum: MilliSatoshi = 0 msat, timestamp: TimestampSecond = 0 unixsec, feeBaseMsat: MilliSatoshi = 1000 msat, feeProportionalMillionths: Long = 100, optionScidAlias: Boolean = false): LocalChannelUpdate = { + val shortIds = createShortIds(channelId) + val channelUpdateScid = channelUpdateScid_opt.getOrElse(shortIds.real.toOption.get) val update = ChannelUpdate(ByteVector64(randomBytes(64)), Block.RegtestGenesisBlock.hash, channelUpdateScid, timestamp, ChannelUpdate.ChannelFlags(isNode1 = true, isEnabled = enabled), CltvExpiryDelta(100), htlcMinimum, feeBaseMsat, feeProportionalMillionths, Some(capacity.toMilliSatoshi)) val channelFeatures = if (optionScidAlias) ChannelFeatures(ScidAlias) else ChannelFeatures() val commitments = PaymentPacketSpec.makeCommitments(channelId, testAvailableBalanceForSend = balance, testCapacity = capacity, channelFeatures = channelFeatures) - LocalChannelUpdate(null, channelId, realShortChannelId_opt = Some(realScid), localAlias = localAlias, outgoingNodeId, None, update, commitments) + LocalChannelUpdate(null, channelId, shortIds, outgoingNodeId, None, update, commitments) } } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala index e6fc5e212c..13c63e2b4d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala @@ -31,7 +31,7 @@ import fr.acinq.eclair.payment.PaymentPacketSpec._ import fr.acinq.eclair.payment.relay.Relayer._ import fr.acinq.eclair.payment.{OutgoingPaymentPacket, PaymentPacketSpec} import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate -import fr.acinq.eclair.router.Router.{ChannelHop, NodeHop} +import fr.acinq.eclair.router.Router.NodeHop import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{NodeParams, TestConstants, randomBytes32, _} import org.scalatest.concurrent.PatienceConfiguration @@ -78,7 +78,8 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat assert(sender.expectMessageType[Relayer.OutgoingChannels].channels.isEmpty) // We publish a channel update, that should be picked up by the channel relayer - system.eventStream ! EventStream.Publish(LocalChannelUpdate(null, channelId_bc, Some(channelUpdate_bc.shortChannelId.toReal), null, c, None, channelUpdate_bc, makeCommitments(channelId_bc))) + val shortIds_bc = ShortIds(RealScidStatus.Final(channelUpdate_bc.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + system.eventStream ! EventStream.Publish(LocalChannelUpdate(null, channelId_bc, shortIds_bc, c, None, channelUpdate_bc, makeCommitments(channelId_bc))) eventually(PatienceConfiguration.Timeout(30 seconds), PatienceConfiguration.Interval(1 second)) { childActors.channelRelayer ! ChannelRelayer.GetOutgoingChannels(sender.ref.toClassic, GetOutgoingChannels()) val channels = sender.expectMessageType[Relayer.OutgoingChannels].channels diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index e2d9a1d8e1..9ec52c8ee9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, SatoshiLong, Transaction, TxOut} import fr.acinq.eclair.TestConstants.Alice import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult, WatchExternalChannelSpent} -import fr.acinq.eclair.channel.{CommitmentsSpec, LocalChannelUpdate, ShortChannelIdAssigned} +import fr.acinq.eclair.channel.{CommitmentsSpec, LocalChannelUpdate, RealScidStatus, ShortChannelIdAssigned, ShortIds} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager} import fr.acinq.eclair.io.Peer.PeerRoutingMessage @@ -33,7 +33,7 @@ import fr.acinq.eclair.router.BaseRouterSpec.channelAnnouncement import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{TestKitBaseClass, randomKey, _} +import fr.acinq.eclair._ import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike import scodec.bits.ByteVector @@ -73,18 +73,21 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val node_g = makeNodeAnnouncement(priv_g, "node-G", Color(30, 10, -50), Nil, Features.empty) val node_h = makeNodeAnnouncement(priv_h, "node-H", Color(30, 10, -50), Nil, Features.empty) - val scid_ab = ShortChannelId(BlockHeight(420000), 1, 0) - val scid_bc = ShortChannelId(BlockHeight(420000), 2, 0) - val scid_cd = ShortChannelId(BlockHeight(420000), 3, 0) - val scid_ef = ShortChannelId(BlockHeight(420000), 4, 0) - val scid_ag_private = ShortChannelId(BlockHeight(420000), 5, 0) - val scid_gh = ShortChannelId(BlockHeight(420000), 6, 0) + val scid_ab = ShortChannelId(BlockHeight(420000), 1, 0).toReal + val scid_bc = ShortChannelId(BlockHeight(420000), 2, 0).toReal + val scid_cd = ShortChannelId(BlockHeight(420000), 3, 0).toReal + val scid_ef = ShortChannelId(BlockHeight(420000), 4, 0).toReal + val scid_ag_private = ShortChannelId(BlockHeight(420000), 5, 0).toReal + val scid_gh = ShortChannelId(BlockHeight(420000), 6, 0).toReal val channelId_ag_private = randomBytes32() val alias_ab = ShortChannelId.generateLocalAlias() val alias_ag_private = ShortChannelId.generateLocalAlias() + val scids_ab = ShortIds(RealScidStatus.Final(scid_ab), alias_ab, None) + val scids_ag_private = ShortIds(RealScidStatus.Final(scid_ag_private), alias_ag_private, None) + val chan_ab = channelAnnouncement(scid_ab, priv_a, priv_b, priv_funding_a, priv_funding_b) val chan_bc = channelAnnouncement(scid_bc, priv_b, priv_c, priv_funding_b, priv_funding_c) val chan_cd = channelAnnouncement(scid_cd, priv_c, priv_d, priv_funding_c, priv_funding_d) @@ -99,8 +102,8 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val update_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, scid_cd, CltvExpiryDelta(3), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 4, htlcMaximumMsat = htlcMaximum) val update_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, scid_ef, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = htlcMaximum) val update_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, scid_ef, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = htlcMaximum) - val update_ag_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, alias_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) - val update_ga_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, a, alias_ag_private, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) + val update_ag_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, scids_ag_private.localAlias, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) + val update_ga_private = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, a, scids_ag_private.localAlias, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) val update_gh = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_g, h, scid_gh, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) val update_hg = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_h, g, scid_gh, CltvExpiryDelta(7), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 10, htlcMaximumMsat = htlcMaximum) @@ -115,8 +118,9 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi assert(ChannelDesc(update_bc, chan_bc) == ChannelDesc(chan_bc.shortChannelId, b, c)) assert(ChannelDesc(update_cd, chan_cd) == ChannelDesc(chan_cd.shortChannelId, c, d)) assert(ChannelDesc(update_ef, chan_ef) == ChannelDesc(chan_ef.shortChannelId, e, f)) - assert(ChannelDesc(update_ag_private, PrivateChannel(alias_ag_private, channelId_ag_private, a, g, None, None, ChannelMeta(1000 msat, 2000 msat))) == ChannelDesc(alias_ag_private, a, g)) - assert(ChannelDesc(update_ag_private, PrivateChannel(alias_ag_private, channelId_ag_private, g, a, None, None, ChannelMeta(2000 msat, 1000 msat))) == ChannelDesc(alias_ag_private, a, g)) + val privateChannel_ag = PrivateChannel(channelId_ag_private, scids_ag_private, a, g, None, None, ChannelMeta(1000 msat, 1000 msat)) + assert(ChannelDesc(update_ag_private, privateChannel_ag) == ChannelDesc(scids_ag_private.localAlias, a, g)) + assert(ChannelDesc(update_ga_private, privateChannel_ag) == ChannelDesc(scids_ag_private.localAlias, g, a)) assert(ChannelDesc(update_gh, chan_gh) == ChannelDesc(chan_gh.shortChannelId, g, h)) // let's set up the router @@ -154,8 +158,8 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_gh)) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, update_hg)) // then private channels - sender.send(router, ShortChannelIdAssigned(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, remoteAlias_opt = None, remoteNodeId = g)) - sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, g, None, update_ag_private, CommitmentsSpec.makeCommitments(30000000 msat, 8000000 msat, a, g, announceChannel = false))) + sender.send(router, ShortChannelIdAssigned(sender.ref, channelId_ag_private, scids_ag_private, remoteNodeId = g)) + sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, scids_ag_private, g, None, update_ag_private, CommitmentsSpec.makeCommitments(30000000 msat, 8000000 msat, a, g, announceChannel = false))) // watcher receives the get tx requests assert(watcher.expectMsgType[ValidateRequest].ann == chan_ab) assert(watcher.expectMsgType[ValidateRequest].ann == chan_bc) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 6ffcc4de1a..55c5f8b522 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -54,18 +54,18 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(privateChannel.update_1_opt.isDefined) assert(privateChannel.update_2_opt.isEmpty) // alice will only have a real scid if this is not a zeroconf channel - assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].realShortChannelId_opt.isEmpty == f.testTags.contains(ChannelStateTestsTags.ZeroConf)) - assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.isDefined) + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.toOption.isEmpty == f.testTags.contains(ChannelStateTestsTags.ZeroConf)) + assert(channels.alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.remoteAlias_opt.isDefined) // alice uses bob's alias for her channel update - assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.localAlias) - assert(privateChannel.update_1_opt.get.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].remoteAlias_opt.get) + assert(privateChannel.update_1_opt.get.shortChannelId != privateChannel.shortIds.localAlias) + assert(privateChannel.update_1_opt.get.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.remoteAlias_opt.get) // alice and bob send their channel_updates using remote alias when they go to NORMAL state val aliceChannelUpdate1 = channels.alice2bob.expectMsgType[ChannelUpdate] val bobChannelUpdate1 = channels.bob2alice.expectMsgType[ChannelUpdate] // alice's channel_update uses bob's alias, and vice versa - assert(aliceChannelUpdate1.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].localAlias) - assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].localAlias) + assert(aliceChannelUpdate1.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias) + assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias) // channel_updates are handled by the peer connection and sent to the router val peerConnection = TestProbe() router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 115c2482fa..58dbe5447d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -24,7 +24,7 @@ import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Block, SatoshiLong, Transaction, TxOut} import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ -import fr.acinq.eclair.channel.{AvailableBalanceChanged, CommitmentsSpec, LocalChannelUpdate} +import fr.acinq.eclair.channel.{AvailableBalanceChanged, CommitmentsSpec, LocalChannelUpdate, RealScidStatus, ShortIds} import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer.PeerRoutingMessage import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop @@ -390,7 +390,7 @@ class RouterSpec extends BaseRouterSpec { assert(res.routes.head.hops.last.nextNodeId == h) val channelUpdate_ag1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, g, alias_ag_private, CltvExpiryDelta(7), 0 msat, 10 msat, 10, htlcMaximum, enable = false) - sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, Some(scid_ag_private), alias_ag_private, g, None, channelUpdate_ag1, CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, g, announceChannel = false))) + sender.send(router, LocalChannelUpdate(sender.ref, channelId_ag_private, scids_ag_private, g, None, channelUpdate_ag1, CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, g, announceChannel = false))) sender.send(router, RouteRequest(a, h, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsg(Failure(RouteNotFound)) } @@ -411,7 +411,7 @@ class RouterSpec extends BaseRouterSpec { sender.send(router, RouteRequest(a, b, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsgType[RouteResponse] val commitments1 = CommitmentsSpec.makeCommitments(10000000 msat, 20000000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments1)) + sender.send(router, LocalChannelUpdate(sender.ref, null, scids_ab, b, Some(chan_ab), update_ab, commitments1)) sender.send(router, RouteRequest(a, b, 12000000 msat, Long.MaxValue.msat, routeParams = DEFAULT_ROUTE_PARAMS)) sender.expectMsg(Failure(BalanceTooLow)) sender.send(router, RouteRequest(a, b, 12000000 msat, Long.MaxValue.msat, allowMultiPart = true, routeParams = DEFAULT_ROUTE_PARAMS)) @@ -624,7 +624,7 @@ class RouterSpec extends BaseRouterSpec { // When the local channel comes back online, it will send a LocalChannelUpdate to the router. val balances = Set[Option[MilliSatoshi]](Some(10000 msat), Some(15000 msat)) val commitments = CommitmentsSpec.makeCommitments(10000 msat, 15000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments)) + sender.send(router, LocalChannelUpdate(sender.ref, null, scids_ab, b, Some(chan_ab), update_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -647,7 +647,7 @@ class RouterSpec extends BaseRouterSpec { // Then we update the balance without changing the contents of the channel update; the graph should still be updated. val balances = Set[Option[MilliSatoshi]](Some(11000 msat), Some(14000 msat)) val commitments = CommitmentsSpec.makeCommitments(11000 msat, 14000 msat, a, b, announceChannel = true) - sender.send(router, LocalChannelUpdate(sender.ref, null, Some(scid_ab), alias_ab, b, Some(chan_ab), update_ab, commitments)) + sender.send(router, LocalChannelUpdate(sender.ref, null, scids_ab, b, Some(chan_ab), update_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -665,7 +665,7 @@ class RouterSpec extends BaseRouterSpec { // When HTLCs are relayed through the channel, balance changes are sent to the router. val balances = Set[Option[MilliSatoshi]](Some(12000 msat), Some(13000 msat)) val commitments = CommitmentsSpec.makeCommitments(12000 msat, 13000 msat, a, b, announceChannel = true) - sender.send(router, AvailableBalanceChanged(sender.ref, null, Some(scid_ab), alias_ab, commitments)) + sender.send(router, AvailableBalanceChanged(sender.ref, null, scids_ab, commitments)) sender.send(router, GetRoutingState) val channel_ab = sender.expectMsgType[RoutingState].channels.find(_.ann == chan_ab).get assert(Set(channel_ab.meta_opt.map(_.balance1), channel_ab.meta_opt.map(_.balance2)) == balances) @@ -683,7 +683,7 @@ class RouterSpec extends BaseRouterSpec { // Private channels should also update the graph when HTLCs are relayed through them. val balances = Set(33000000 msat, 5000000 msat) val commitments = CommitmentsSpec.makeCommitments(33000000 msat, 5000000 msat, a, g, announceChannel = false) - sender.send(router, AvailableBalanceChanged(sender.ref, channelId_ag_private, None, alias_ag_private, commitments)) + sender.send(router, AvailableBalanceChanged(sender.ref, channelId_ag_private, scids_ab, commitments)) sender.send(router, Router.GetRouterData) val data = sender.expectMsgType[Data] val channel_ag = data.privateChannels(channelId_ag_private) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index 02a74574f2..e68eee3486 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -136,11 +136,11 @@ class ChannelCodecsSpec extends AnyFunSuite { // this test makes sure that we actually produce the same objects than previous versions of eclair val refs = Map( hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B134000456E4167E3C0EB8C856C79CA31C97C0AA0000000000000222000000012A05F2000000000000028F5C000000000000000102D0001E000BD48A2402E80B723C42EE3E42938866EC6686ABB7ABF64380000000C501A7F2974C5074E9E10DBB3F0D9B8C40932EC63ABC610FAD7EB6B21C6D081A459B000000000000011E80000001EEFFFE5C00000000000147AE00000000000001F403F000F18146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB20131AD64F76FAF90CD7DE26892F1BDAB82FB9E02EF6538D82FF4204B5348F02AE081A5388E9474769D69C4F60A763AE0CCDB5228A06281DE64408871A927297FDFD8818B6383985ABD4F0AC22E73791CF3A4D63C592FA2648242D34B8334B1539E823381BB1F1404C37D9C2318F5FC6B1BF7ECF5E6835B779E3BE09BADCF6DF1F51DCFBC80000000C0808000000000000EFD80000000007F00000000061A0A4880000001EDE5F3C3801203B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E808000000015FFFFFF800000000011001029DFB814F6502A68D6F83B6049E3D2948A2080084083750626532FDB437169C20023A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A95700AD0100000000008083B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E80800000001961B4C001618F8180000000001100102E648BA30998A28C02C2DFD9DDCD0E0BA064DA199C55186485AFAB296B94E704426FFE00000000000B000A67D9B9FAADB91650E0146B1F742E5C16006708890200239822011026A6925C659D006FEB42D639F1E42DD13224EE49AA34E71B612CF96DB66A8CD4011032C22F653C54CC5E41098227427650644266D80DED45B7387AE0FFC10E529C4680A418228110807CB47D9C1A14CB832FB361C398EA672C9542F34A90BAD4288FA6AC5FC9E9845C01101CF71CAE9252D389135D8C606225DCF1E0333CCDF1FAE84B74FC5D3D440C25F880A3A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A9573D7C531000000000000000000F3180000000007F00000001EDE5F3C380000000061A0A48D64CA627B243AD5915A2E5D0BAD026762028DDF3304992B83A26D6C11735FC5F01ED56D769BDE7F6A068AF1A4BCFDF950321F3A4744B01B1DDC7498677F112AE1A80000000000000000000000000000000000000658000000000000819800040D37301C10C9419287E9A3B704EB6D7F45CC145DD77DCE8A63B0A47C8AB67467D800901DCE3C8B05A891E56F2BAF1B82405ABD8640B759AEEBD939B976D42C311758F40400000000AFFFFFFC00000000008800814EFDC0A7B2815346B7C1DB024F1E94A451040042041BA83132997EDA1B8B4E10011D48840A33BCFBC0833F6825A4ABF0A78E2B11D5B2981CD958EA4C881204247273416D90840D9834A03892A6C59DCA9B990600A5C65882972A8A7AF7E0CE7975C031846AE78D4AB8002000EC0003FFFFFFFF86801076D98A575A4CDFD0E3F44D1BB3CD3BBAF3BD04C38FED439ED90D88DF932A9296801A80007FFFFFFFF4008136A9D5896669E8724C5120FB6B36C241EF3CEF68AE0316161F04A9EE3EAFF36000FC0003FFFFFFFF86780106E4B5CC4155733A2427082907338051A5DA1E7CA6432840A5528ECAFFA3FB628801B80007FFFFFFFF10020CA4E125E9126107745D4354D4187ABCDE323117857A1DCEB7CCF60B2AAFA80C6003A0000FFFFFFFFE1C0080981575FD981A73A848CC0243CB467BF451F6811DAF4D71CAD8CE8B1E96DB190C01000003FFFFFFFF867400814C747E0FD8290BE8A3B8B3F73015A261479A71780CD3A0A9270234E4B394409C00D80003FFFFFFFF90020E1B9C9B10A97F15F5E1BB27FC8AC670DF8DADEAE4EDFAFB23BDD0AC705FDF51600340000FFFFFFFFF0020AD2581F3494A17B0BE3F63516D53F028A204FD3156D8B21AA4E57A8738D2062080007FFFFFFFF0CE83B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E0B8C1E00000B8000FA46CC2C7E9AB4A37C64216CD65C944E6D73998419D1A1AD2827AB6BC85B32280230764E374064EC82A3751E789607E23BEAE93FB0EDDD5E7FA803767079662E80EAEF384E2AFCB68049D9DC246119E77BD2ED4112330760CAB6CD3671CFCE006C584B9C95E0B554261E00154D40806EA694F44751B328A9291BAD124EFD5664280936EC92D27B242737E7E3E83B4704BA367B7DA5108F2F6EDFB1C38EE721A369E77EED71B12090BAEAAAC322C1457E31AB0C4DE5D9351943F10FD747742616A1AABD09F680B37D4105A8872695EE9B97FAB8985FAA9D747D45046229BF265CEEB300A40FE23040C5F335E0515496C58EE47418B72331FCC6F47A31A9B33B8E000008692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002069FCA5D3141D3A78436ECFC366E31024CBB18EAF1843EB5FADAC871B42069166C0726710955E3AD621072FCBDFCB90D79E5B1951A5EE01DB533B72429F84E2562680519DE7DE0419FB412D255F853C71588EAD94C0E6CAC7526440902123939A0B6C806CC1A501C495362CEE54DCC830052E32C414B95453D7BF0673CBAE018C23573C69C694A8F88483050257A7366B838489731E5776B6FA0F02573401176D3E7FAEEF11E95A671420586631255F51A0EC2CF4D4D9F69D587712070FE1FB9316B71868692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002BA11BBBA0202012000000000000007D0000007D0000000C800000007CFFFF83000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"1513532x23x1","buried":true,"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"1513532x23x1"}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1513532x23x1"},"localAlias":"1513532x23x1"},"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B1340004D443ECE9D9C43A11A19B554BAAA6AD150000000000000222000000003B9ACA0000000000000249F000000000000000010090001E800BD48A22F4C80A42CC8BB29A764DBAEFC95674931FBE9A4380000000C50134D4A745996002F219B5FDBA1E045374DF589ECA06ABE23CECAE47343E65EDCF800000000000011E80000001BA90824000000000000124F800000000000001F4038500F1810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E2266201E8BFEEEEED725775B8116F6F82CF8E87835A5B45B184E56F272AD70D6078118601E06212B8C8F2E25B73EE7974FDCDF007E389B437BBFE238CCC3F3BF7121B6C5E81AA8589D21E9584B24A11F3ABBA5DAD48D121DD63C57A69CD767119C05DA159CB81A649D8CC0E136EB8DFBD2268B69DCA86F8CE4A604235A03D9D37AE7B07FC563F80000000C080800000000000271C000000000177000000002808B14600000001970039BA00123767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB08800000000015E070F20000000000110010584241B5FB364208F6E64A80D1166DAD866186B10C015ED0283FF1C308C2105A0023A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA95700AD81000000000080B767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB0880000000003E7AEDC0011ABE8A00000000001100101A9CE4B6AEF469590BC7BCC51DCEEAE9C86084055A63CC01E443C733FBE400B9B5B16800000000000B000A5E5700106D1A7097E4DE87EBAF1F8F2773842FA482002418228110805E84989A81F51ABD9D11889AE43E68FAD93659DEC019F1B8C0ADBF15A57B118B81101DCC1256F9306439AD3962C043FC47A5179CAAA001CCB23342BE0E8D92E4022780A4182281108074F306DA3751B84EC5FFB155BDCA7B8E02208BBDBC8D4F3327ABA557BF27CD1701102EF4AC8CC92F469DA9642D4D4162BC545F8B34ADE15B7D6F99808AA22B086B0180A3A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA9576F8099900000000000000000271C00000000017700000001970039BA000000002808B14648CE00AE97051EE10A3C361263F81A98165CE4AA7BA076933D4266E533585F24815C15DEACF0691332B38ECF23EC39982C5C978C748374A01BA9B30D501EE4F26E8000000000000000000000000000000000001224000000000000004B800040A911C460F1467952E3B99BED072F81BFB4454FF389636DCB399FE6A78113C28580091BB3F87A7806AF4FEF920BBF794391A1ECFC7D7632E98245D2BAF3870050558440000000000AF0387900000000000880082C2120DAFD9B21047B732540688B36D6C330C3588600AF68141FF8E18461082D0011D488408570D7C50EB7AB7C042AF13382F8C8DD83E6A7121A5E2DD8B4C73F2C407113310840EF456FD0886E454A6C5CF4F7B0B5D742CC143E47C157EF87E03434BEAB81337ED4AB8001C00F40003FFFFFFFEC7200403248A1D44DFA3AC9EC237D452C936400CAA86E9517CCCF2A8F77B7493CD70B6A00780001FFFFFFFF63A0041826829646B907A97FBD1455EA8673A12B8E7AA6EA790F7802E955CE3B69DE57E006E0001FFFFFFFF640081E51EB1F91218821E680B50E4B22DF8B094385BD33ACAE36BFC9E8C2F5AD2DA5400EC0003FFFFFFFEC7801047C26AD5435658D063EBCF73A5D0EEFE73ED6B73426246E8DFB3A21D1C4C7465001900007FFFFFFFE0040B115AC58BAAA900195893EA3B2AB408D2AD348AD047E3B6CB15E599625E38608006A0001FFFFFFFF7002033C39A21A38BB61F6FB33623771A9356D8885B7C12C939C770C939EF826286C200360000FFFFFFFFB4008104EF4271064A0973B053727C3E67352D00E25CAEED944F50782449CEAE8F50960001FFFFFFFF6390DD9FC3D3C0357A7F7C905DFBCA1C8D0F67E3EBB1974C122E95D79C380282AC222B21FA0007920001295AA1FB77029F7620A90EF7AE6A6CD31E4588B93264A7ADB76152D535C52E90B9E1B7C2376DABA316A6290F1A9730D4E5E44D0B1CB0EE6A795702E6A6BCDFCDA1A4BFEBFC134AB8847A5187ECE761D75D3CCB904274875680F51984800000000AC87E8001E480002E884D2A8080804800000000000001F4000001F40000003200000001BF08EB000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"1413373x969x0","buried":true,"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"1413373x969x0"}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1413373x969x0"},"localAlias":"1413373x969x0"},"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"0200020000000303933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400098c4b989bbdced820a77a7186c2320e7d176a5c8b5c16d6ac2af3889d6bc8bf8080000001000000000000022200000004a817c80000000000000249f0000000000000000102d0001eff1600148061b7fbd2d84ed1884177ea785faecb2080b10302e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b300000004080aa982027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8000000000000023d000000037521048000000000000249f00000000000000001070a01e302eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b7503c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a5700000004808a52a1010000000000000004000000001046000000037e11d6000000000000000000245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aefd013b020000000001015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61040047304402207f8c1936d0a50671c993890f887c78c6019abc2a2e8018899dcdc0e891fd2b090220046b56afa2cb7e9470073c238654ecf584bcf5c00b96b91e38335a70e2739ec901483045022100871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c0220119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b01475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aed7782c20000000000000000000040000000010460000000000000000000000037e11d600b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d802e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a000000000000000000000000000000000000000000000000000000000000ff03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52ae0001003e0000fffffffffffc0080474b8cf7bb98217dd8dc475cb7c057a3465d466728978bbb909d0a05d4ae7bbe0001fffffffffff85986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b1eedce0000010000fffffd01ae98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be54920134196992f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef09bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce0000010000027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b803933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13402eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d88710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce000001000060e6eb14010100900000000000000001000003e800000064000000037e11d6000000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"realShortChannelId_opt":"2026958x1x0","buried":true,"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}},"localAlias":"2026958x1x0"}""" + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"2026958x1x0"},"localAlias":"2026958x1x0"},"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" ) refs.foreach { case (oldbin, refjson) => @@ -329,7 +329,7 @@ object ChannelCodecsSpec { commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init) - DATA_NORMAL(commitments, Some(realScid(42)), buried = true, None, channelUpdate, ShortChannelId.generateLocalAlias(), None, None, None, None) + DATA_NORMAL(commitments, ShortIds(RealScidStatus.Final(realScid(42)), ShortChannelId.generateLocalAlias(), None), None, channelUpdate, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index cf5ab464c5..349f199b5e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -254,8 +254,8 @@ class ChannelCodecs3Spec extends AnyFunSuite { { val bin = hex"0001000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae00000000000000075bcd1541dd93fe76288e183498a7d57065cf472a4413c643730f9c117ca806be6b57f75303a153d48f51439f168ca85ef2fd6f3e0642fc7132f7a530cd50d880ec05cb4c17" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY] - assert(data.localAlias == ShortChannelId(123456789L)) - assert(data.realShortChannelId_opt == Some(ShortChannelId(123456789L))) + assert(data.shortIds.localAlias == ShortChannelId(123456789L)) + assert(data.shortIds.real == RealScidStatus.Temporary(ShortChannelId(123456789L).toReal)) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0010")) // NB: 01 -> 10 } @@ -263,8 +263,8 @@ class ChannelCodecs3Spec extends AnyFunSuite { { val bin = hex"0007000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000000000000000002aff008821c93fbf280b2391826bc70ae858cf815d4afc1816f85445364f188e635d4ae64dfe1c58bedb017dd6f267452444d991b66fcfc638396f72fa6926f69d6125ff01010101010101010101010101010101010101010101010101010101010101010000000000022cd962975eec0101002a000000000000000f0000023f0000003500000003e8000000000000" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] - assert(data.localAlias == ShortChannelId(42)) - assert(data.realShortChannelId_opt == Some(ShortChannelId(42))) + assert(data.shortIds.localAlias == ShortChannelId(42)) + assert(data.shortIds.real == RealScidStatus.Final(ShortChannelId(42).toReal)) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0009")) } From 0db76d4eb56cb3355ed8e294c609c5095dfecb65 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 9 Jun 2022 15:07:22 +0200 Subject: [PATCH 42/55] fix scaladoc warnings --- .../scala/fr/acinq/eclair/channel/fsm/Channel.scala | 10 ++++------ .../fr/acinq/eclair/crypto/TransportHandler.scala | 2 ++ .../fr/acinq/eclair/wire/protocol/FailureMessage.scala | 2 ++ 3 files changed, 8 insertions(+), 6 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 351e3683cc..5a310235ce 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 @@ -1601,16 +1601,14 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } sealed trait EmitLocalChannelEvent - /** + /* * This event is for: - * - the router: so that it knows about this channel to find routes - * - the relayers: so they can learn about the channel scid/alias and route to it + * - the router: so it knows about this channel to find routes + * - the relayer: so it learns about the channel real scid/alias and can route to it * - the peer: so they can "learn the other end's forwarding parameters" (BOLT 7) - * - * @param sendToPeer indicates whether we should send the channel_update directly to our peer */ case class EmitLocalChannelUpdate(reason: String, d: DATA_NORMAL, sendToPeer: Boolean) extends EmitLocalChannelEvent - /** + /* * When a channel that could previously be used to relay payments starts closing, we advertise the fact that this * channel can't be used for payments anymore. If the channel is private we don't really need to tell the * counterparty because it is already aware that the channel is being closed diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala index fe836fd088..ac6003d0aa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/TransportHandler.scala @@ -410,6 +410,7 @@ object TransportHandler { case class Encryptor(state: CipherState) { /** * see BOLT #8 + * {{{ * +------------------------------- * |2-byte encrypted message length| * +------------------------------- @@ -425,6 +426,7 @@ object TransportHandler { * | 16-byte MAC of the | * | lightning message | * +------------------------------- + * }}} * * @param plaintext plaintext * @return a (cipherstate, ciphertext) tuple where ciphertext is encrypted according to BOLT #8 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/FailureMessage.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/FailureMessage.scala index 07969828bb..1e9612c22a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/FailureMessage.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/FailureMessage.scala @@ -139,9 +139,11 @@ object FailureMessageCodecs { /** * An onion-encrypted failure from an intermediate node: + * {{{ * +----------------+----------------------------------+-----------------+----------------------+-----+ * | HMAC(32 bytes) | failure message length (2 bytes) | failure message | pad length (2 bytes) | pad | * +----------------+----------------------------------+-----------------+----------------------+-----+ + * }}} * with failure message length + pad length = 256 */ def failureOnionCodec(mac: Mac32): Codec[FailureMessage] = CommonCodecs.prependmac( From 957f014d5dd101b03618a55da3cd792d61c058e9 Mon Sep 17 00:00:00 2001 From: pm47 Date: Thu, 9 Jun 2022 16:40:36 +0200 Subject: [PATCH 43/55] fix error: knownDirectSubclasses of RealShortChannelId observed before subclass registered --- .../src/main/scala/fr/acinq/eclair/ShortChannelId.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index 7d8a0846d9..97bcc016f3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -16,8 +16,8 @@ package fr.acinq.eclair -sealed trait RealShortChannelId extends ShortChannelId -sealed trait LocalAlias extends ShortChannelId +trait RealShortChannelId extends ShortChannelId +trait LocalAlias extends ShortChannelId /** * A short channel id uniquely identifies a channel by the coordinates of its funding tx output in the blockchain. From db232e166f848c8965c90895fcd0adc95885ece7 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 10 Jun 2022 13:31:27 +0200 Subject: [PATCH 44/55] convert scid traits to case classes --- .../fr/acinq/eclair/ShortChannelId.scala | 72 +++++++++++-------- .../fr/acinq/eclair/channel/ChannelData.scala | 6 +- .../acinq/eclair/channel/ChannelEvents.scala | 2 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 2 +- .../channel/fsm/ChannelOpenSingleFunder.scala | 4 +- .../fr/acinq/eclair/db/pg/PgNetworkDb.scala | 5 +- .../eclair/db/sqlite/SqliteNetworkDb.scala | 5 +- .../acinq/eclair/json/JsonSerializers.scala | 4 +- .../scala/fr/acinq/eclair/router/Graph.scala | 14 ++-- .../scala/fr/acinq/eclair/router/Router.scala | 12 ++-- .../acinq/eclair/router/StaleChannels.scala | 2 +- .../scala/fr/acinq/eclair/router/Sync.scala | 2 +- .../fr/acinq/eclair/router/Validation.scala | 10 +-- .../channel/version0/ChannelCodecs0.scala | 8 +-- .../channel/version1/ChannelCodecs1.scala | 6 +- .../channel/version2/ChannelCodecs2.scala | 6 +- .../channel/version3/ChannelCodecs3.scala | 8 +-- .../eclair/wire/protocol/ChannelTlv.scala | 6 +- .../eclair/wire/protocol/CommonCodecs.scala | 12 ++-- .../wire/protocol/LightningMessageTypes.scala | 5 +- .../fr/acinq/eclair/EclairImplSpec.scala | 15 ++-- .../fr/acinq/eclair/ShortChannelIdSpec.scala | 25 +++---- .../scala/fr/acinq/eclair/TestUtils.scala | 2 - .../blockchain/bitcoind/ZmqWatcherSpec.scala | 15 ++-- .../channel/states/e/NormalStateSpec.scala | 8 +-- .../fr/acinq/eclair/db/ChannelsDbSpec.scala | 5 +- .../fr/acinq/eclair/db/NetworkDbSpec.scala | 21 +++--- .../basic/ThreeNodesIntegrationSpec.scala | 4 +- .../basic/fixtures/MinimalNodeFixture.scala | 4 +- .../acinq/eclair/io/PeerConnectionSpec.scala | 4 +- .../eclair/json/JsonSerializersSpec.scala | 12 ++-- .../eclair/payment/PaymentLifecycleSpec.scala | 2 +- .../payment/relay/ChannelRelayerSpec.scala | 30 ++++---- .../eclair/payment/relay/RelayerSpec.scala | 2 +- .../AnnouncementsBatchValidationSpec.scala | 8 +-- .../eclair/router/AnnouncementsSpec.scala | 6 +- .../acinq/eclair/router/BaseRouterSpec.scala | 12 ++-- .../router/ChannelRangeQueriesSpec.scala | 6 +- .../eclair/router/RouteCalculationSpec.scala | 14 ++-- .../fr/acinq/eclair/router/RouterSpec.scala | 28 ++++---- .../acinq/eclair/router/RoutingSyncSpec.scala | 14 ++-- .../internal/channel/ChannelCodecsSpec.scala | 10 +-- .../channel/version3/ChannelCodecs3Spec.scala | 6 +- .../protocol/ExtendedQueriesCodecsSpec.scala | 18 ++--- .../protocol/LightningMessageCodecsSpec.scala | 36 +++++----- .../fr/acinq/eclair/router/FrontRouter.scala | 6 +- .../acinq/eclair/router/FrontRouterSpec.scala | 8 +-- .../api/serde/FormParamExtractors.scala | 2 +- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 6 +- 49 files changed, 266 insertions(+), 254 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index 97bcc016f3..56d6d492d8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -16,57 +16,71 @@ package fr.acinq.eclair -trait RealShortChannelId extends ShortChannelId -trait LocalAlias extends ShortChannelId +import fr.acinq.eclair.ShortChannelId.toShortId -/** - * A short channel id uniquely identifies a channel by the coordinates of its funding tx output in the blockchain. - * See BOLT 7: https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#requirements - */ -case class ShortChannelId(private val id: Long) extends Ordered[ShortChannelId] { - - def toLong: Long = id - - /** Careful: only call this if you are sure that this scid is actually a real scid */ - def toReal: RealShortChannelId = new ShortChannelId(id) with RealShortChannelId - - /** Careful: only call this if you are sure that this scid is actually a local alias */ - def toAlias: LocalAlias = new ShortChannelId(id) with LocalAlias - - def blockHeight = ShortChannelId.blockHeight(this) - - override def toString: String = { +// @formatter:off +sealed trait ShortChannelId extends Ordered[ShortChannelId] { + def toLong: Long + // we use an unsigned long comparison here + override def compare(that: ShortChannelId): Int = (this.toLong + Long.MinValue).compareTo(that.toLong + Long.MinValue) + override def hashCode(): Int = toLong.hashCode() + override def equals(obj: Any): Boolean = obj match { + case scid: ShortChannelId => this.toLong.equals(scid.toLong) + case _ => super.equals(obj) + } + def toCoordinatesString: String = { val TxCoordinates(blockHeight, txIndex, outputIndex) = ShortChannelId.coordinates(this) s"${blockHeight.toLong}x${txIndex}x$outputIndex" } - - // we use an unsigned long comparison here - override def compare(that: ShortChannelId): Int = (this.id + Long.MinValue).compareTo(that.id + Long.MinValue) + def toHex: String = s"0x${toLong.toHexString}" +} +/** Sometimes we don't know what a scid really is */ +case class UnspecifiedShortChannelId(private val id: Long) extends ShortChannelId { + override def toLong: Long = id + override def toString: String = toCoordinatesString // for backwards compatibility, because ChannelUpdate have an unspecified scid +} +case class RealShortChannelId private (private val id: Long) extends ShortChannelId { + override def toLong: Long = id + override def toString: String = toCoordinatesString + def blockHeight: BlockHeight = ShortChannelId.blockHeight(this) + def outputIndex: Int = ShortChannelId.outputIndex(this) } +case class Alias(private val id: Long) extends ShortChannelId { + override def toLong: Long = id + override def toString: String = toHex +} +// @formatter:on object ShortChannelId { - def apply(s: String): ShortChannelId = s.split("x").toList match { - case blockHeight :: txIndex :: outputIndex :: Nil => ShortChannelId(toShortId(blockHeight.toInt, txIndex.toInt, outputIndex.toInt)) + case blockHeight :: txIndex :: outputIndex :: Nil => UnspecifiedShortChannelId(toShortId(blockHeight.toInt, txIndex.toInt, outputIndex.toInt)) case _ => throw new IllegalArgumentException(s"Invalid short channel id: $s") } - def apply(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int): RealShortChannelId = ShortChannelId(toShortId(blockHeight.toInt, txIndex, outputIndex)).toReal + def apply(l: Long): ShortChannelId = UnspecifiedShortChannelId(l) def toShortId(blockHeight: Int, txIndex: Int, outputIndex: Int): Long = ((blockHeight & 0xFFFFFFL) << 40) | ((txIndex & 0xFFFFFFL) << 16) | (outputIndex & 0xFFFFL) + def generateLocalAlias(): Alias = Alias(System.nanoTime()) // TODO: fixme (duplicate, etc.) + @inline - def blockHeight(shortChannelId: ShortChannelId): BlockHeight = BlockHeight((shortChannelId.id >> 40) & 0xFFFFFF) + def blockHeight(shortChannelId: ShortChannelId): BlockHeight = BlockHeight((shortChannelId.toLong >> 40) & 0xFFFFFF) @inline - def txIndex(shortChannelId: ShortChannelId): Int = ((shortChannelId.id >> 16) & 0xFFFFFF).toInt + def txIndex(shortChannelId: ShortChannelId): Int = ((shortChannelId.toLong >> 16) & 0xFFFFFF).toInt @inline - def outputIndex(shortChannelId: ShortChannelId): Int = (shortChannelId.id & 0xFFFF).toInt + def outputIndex(shortChannelId: ShortChannelId): Int = (shortChannelId.toLong & 0xFFFF).toInt def coordinates(shortChannelId: ShortChannelId): TxCoordinates = TxCoordinates(blockHeight(shortChannelId), txIndex(shortChannelId), outputIndex(shortChannelId)) +} - def generateLocalAlias(): LocalAlias = new ShortChannelId(System.nanoTime()) with LocalAlias // TODO: fixme (duplicate, etc.) +/** + * A real short channel id uniquely identifies a channel by the coordinates of its funding tx output in the blockchain. + * See BOLT 7: https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#requirements + */ +object RealShortChannelId { + def apply(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int): RealShortChannelId = RealShortChannelId(toShortId(blockHeight.toInt, txIndex, outputIndex)) } case class TxCoordinates(blockHeight: BlockHeight, txIndex: Int, outputIndex: Int) \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index c47848e730..095d377be3 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, UInt64} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, Alias, MilliSatoshi, RealShortChannelId, ShortChannelId, UInt64} import scodec.bits.ByteVector import java.util.UUID @@ -447,8 +447,8 @@ object RealScidStatus { * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] */ case class ShortIds(real: RealScidStatus, - localAlias: LocalAlias, - remoteAlias_opt: Option[ShortChannelId]) + localAlias: Alias, + remoteAlias_opt: Option[Alias]) final case class DATA_NORMAL(commitments: Commitments, shortIds: ShortIds, channelAnnouncement: Option[ChannelAnnouncement], diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index 85d54d3599..910a731407 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.channel import akka.actor.ActorRef import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, Transaction} -import fr.acinq.eclair.{BlockHeight, Features, LocalAlias, RealShortChannelId, ShortChannelId} +import fr.acinq.eclair.{BlockHeight, Features, Alias, RealShortChannelId, ShortChannelId} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.Helpers.Closing.ClosingType import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} 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 5a310235ce..106a48ad6f 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 @@ -614,7 +614,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(c: CurrentFeerates, d: DATA_NORMAL) => handleCurrentFeerate(c, d) case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => - val finalRealShortId = RealScidStatus.Final(ShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt)) + val finalRealShortId = RealScidStatus.Final(RealShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt)) val shortIds1 = d.shortIds.copy(real = finalRealShortId) log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=${finalRealShortId.realScid}") if (d.shortIds.real == RealScidStatus.Unknown) { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 5bbdbc8b5e..d9275cdc2e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.TxOwner import fr.acinq.eclair.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelReady, ChannelReadyTlv, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream} -import fr.acinq.eclair.{BlockHeight, Features, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} +import fr.acinq.eclair.{BlockHeight, Features, Alias, RealShortChannelId, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} import scodec.bits.ByteVector import scala.concurrent.duration.DurationInt @@ -376,7 +376,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { else { log.info(s"channel was confirmed at blockHeight=$blockHeight txIndex=$txIndex") context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) - RealScidStatus.Temporary(ShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) + RealScidStatus.Temporary(RealShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) } // the alias will use in our channel_update message, the goal is to be able to use our channel // as soon as it reaches NORMAL state, and before it is announced on the network diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala index 37273908d1..c44ca7c892 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgNetworkDb.scala @@ -224,12 +224,13 @@ class PgNetworkDb(implicit ds: DataSource) extends NetworkDb with Logging { })")) { statement => shortChannelIds + .map(_.toLong) .grouped(batchSize) .foreach { group => - val padded = group.toArray.padTo(batchSize, ShortChannelId(0L)) + val padded = group.toArray.padTo(batchSize, 0L) for (i <- 0 until batchSize) { - statement.setLong(1 + i, padded(i).toLong) // index for jdbc parameters starts at 1 + statement.setLong(1 + i, padded(i)) // index for jdbc parameters starts at 1 } statement.executeUpdate() } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala index 6e6ea16089..b05e743ffb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala @@ -143,11 +143,12 @@ class SqliteNetworkDb(val sqlite: Connection) extends NetworkDb with Logging { val batchSize = 100 using(sqlite.prepareStatement(s"DELETE FROM channels WHERE short_channel_id IN (${List.fill(batchSize)("?").mkString(",")})")) { statement => shortChannelIds + .map(_.toLong) .grouped(batchSize) .foreach { group => - val padded = group.toArray.padTo(batchSize, ShortChannelId(0L)) + val padded = group.toArray.padTo(batchSize, 0L) for (i <- 0 until batchSize) { - statement.setLong(1 + i, padded(i).toLong) // index for jdbc parameters starts at 1 + statement.setLong(1 + i, padded(i)) // index for jdbc parameters starts at 1 } statement.executeUpdate() } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 260168894b..a03abfb410 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -36,7 +36,7 @@ import fr.acinq.eclair.transactions.DirectedHtlc import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.MessageOnionCodecs.blindedRouteCodec import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, LocalAlias, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature, channel} +import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Feature, FeatureSupport, Alias, MilliSatoshi, ShortChannelId, TimestampMilli, TimestampSecond, UInt64, UnknownFeature, channel} import org.json4s import org.json4s.JsonAST._ import org.json4s.jackson.Serialization @@ -445,7 +445,7 @@ object OnionMessageReceivedSerializer extends ConvertClassSerializer[OnionMessag // @formatter:off /** this is cosmetic, just to not have a '_opt' field in json, which will only appear if the option is defined anyway */ -private case class ShortIdsJson(real: RealScidStatus, localAlias: LocalAlias, remoteAlias: Option[ShortChannelId]) +private case class ShortIdsJson(real: RealScidStatus, localAlias: Alias, remoteAlias: Option[ShortChannelId]) object ShortIdsSerializer extends ConvertClassSerializer[ShortIds](s => ShortIdsJson(s.real, s.localAlias, s.remoteAlias_opt)) // @formatter:on diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 5876a1a3a9..8b091bc3db 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -18,12 +18,11 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Btc, MilliBtc, Satoshi, SatoshiLong} -import fr.acinq.eclair.RealShortChannelId -import fr.acinq.eclair._ import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge} import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.wire.protocol.ChannelUpdate +import fr.acinq.eclair.{RealShortChannelId, _} import scala.annotation.tailrec import scala.collection.immutable.SortedMap @@ -311,8 +310,15 @@ object Graph { import RoutingHeuristics._ // Every edge is weighted by funding block height where older blocks add less weight. The window considered is 1 year. - val channelBlockHeight = ShortChannelId.coordinates(edge.desc.shortChannelId).blockHeight - val ageFactor = normalize(channelBlockHeight.toDouble, min = (currentBlockHeight - BLOCK_TIME_ONE_YEAR).toDouble, max = currentBlockHeight.toDouble) + val ageFactor = edge.desc.shortChannelId match { + case real: RealShortChannelId => + val channelBlockHeight = ShortChannelId.coordinates(real).blockHeight + normalize(channelBlockHeight.toDouble, min = (currentBlockHeight - BLOCK_TIME_ONE_YEAR).toDouble, max = currentBlockHeight.toDouble) + // for local channels or route hints we don't easily have access to the channel block height, but we want to + // give them the best score anyway + case _: Alias => 1 + case _: UnspecifiedShortChannelId => 1 + } // Every edge is weighted by channel capacity, larger channels add less weight val edgeMaxCapacity = edge.capacity.toMilliSatoshi diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 0752b0c087..273d72a99b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -24,7 +24,6 @@ import akka.event.Logging.MDC import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi} import fr.acinq.eclair.Logs.LogCategory -import fr.acinq.eclair.ShortChannelId.outputIndex import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{ValidateResult, WatchExternalChannelSpent, WatchExternalChannelSpentTriggered} @@ -294,9 +293,6 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm object Router { - val shortChannelIdKey = Context.key[ShortChannelId]("shortChannelId", ShortChannelId(0)) - val remoteNodeIdKey = Context.key[String]("remoteNodeId", "unknown") - def props(nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], initialized: Option[Promise[Done]] = None) = Props(new Router(nodeParams, watcher, initialized)) case class SearchBoundaries(maxFeeFlat: MilliSatoshi, @@ -364,7 +360,7 @@ object Router { val nodeId1: PublicKey = ann.nodeId1 val nodeId2: PublicKey = ann.nodeId2 def shortChannelId: RealShortChannelId = ann.shortChannelId - def channelId: ByteVector32 = toLongId(fundingTxid.reverse, outputIndex(ann.shortChannelId)) + def channelId: ByteVector32 = toLongId(fundingTxid.reverse, ann.shortChannelId.outputIndex) def getNodeIdSameSideAs(u: ChannelUpdate): PublicKey = if (u.channelFlags.isNode1) ann.nodeId1 else ann.nodeId2 def getChannelUpdateSameSideAs(u: ChannelUpdate): Option[ChannelUpdate] = if (u.channelFlags.isNode1) update_1_opt else update_2_opt def getBalanceSameSideAs(u: ChannelUpdate): Option[MilliSatoshi] = if (u.channelFlags.isNode1) meta_opt.map(_.balance1) else meta_opt.map(_.balance2) @@ -655,18 +651,18 @@ object Router { rebroadcast: Rebroadcast, awaiting: Map[ChannelAnnouncement, Seq[GossipOrigin]], // note: this is a seq because we want to preserve order: first actor is the one who we need to send a tcp-ack when validation is done privateChannels: Map[ByteVector32, PrivateChannel], // indexed by channel id - scid2PrivateChannels: Map[ShortChannelId, ByteVector32], // real scid or alias to channel_id, only to be used for private channels + scid2PrivateChannels: Map[Long, ByteVector32], // real scid or alias to channel_id, only to be used for private channels excludedChannels: Set[ChannelDesc], // those channels are temporarily excluded from route calculation, because their node returned a TemporaryChannelFailure graphWithBalances: GraphWithBalanceEstimates, sync: Map[PublicKey, Syncing] // keep tracks of channel range queries sent to each peer. If there is an entry in the map, it means that there is an ongoing query for which we have not yet received an 'end' message ) { def resolve(scid: ShortChannelId): Option[KnownChannel] = { // let's assume this is a real scid - channels.get(scid.toReal) match { + channels.get(RealShortChannelId(scid.toLong)) match { case Some(publicChannel) => Some(publicChannel) case None => // maybe it's an alias or a real scid - scid2PrivateChannels.get(scid).flatMap(privateChannels.get) match { + scid2PrivateChannels.get(scid.toLong).flatMap(privateChannels.get) match { case Some(privateChannel) => Some(privateChannel) case None => None } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala index 91ce0d13f2..8905989562 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/StaleChannels.scala @@ -21,7 +21,7 @@ import akka.event.LoggingAdapter import fr.acinq.eclair.db.NetworkDb import fr.acinq.eclair.router.Router.{ChannelDesc, Data, PublicChannel, hasChannels} import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} -import fr.acinq.eclair.{BlockHeight, ShortChannelId, TimestampSecond, TxCoordinates} +import fr.acinq.eclair.{BlockHeight, RealShortChannelId, ShortChannelId, TimestampSecond, TxCoordinates} import scala.collection.mutable import scala.concurrent.duration._ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala index fe5d03cdc3..4eadbb4988 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Sync.scala @@ -219,7 +219,7 @@ object Sync { /** * Filters channels that we want to send to nodes asking for a channel range */ - def keep(firstBlock: BlockHeight, numberOfBlocks: Long, id: ShortChannelId): Boolean = { + def keep(firstBlock: BlockHeight, numberOfBlocks: Long, id: RealShortChannelId): Boolean = { val height = id.blockHeight height >= firstBlock && height < (firstBlock + numberOfBlocks) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index 00a31db4da..f9f3c651f0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -202,7 +202,7 @@ object Validation { // we remove the corresponding unannounced channel that we may have until now privateChannels = d.privateChannels - pubChan.channelId, // we also remove the scid -> channelId mappings - scid2PrivateChannels = d.scid2PrivateChannels - pubChan.shortChannelId -- privChan_opt.map(_.shortIds.localAlias), + scid2PrivateChannels = d.scid2PrivateChannels - pubChan.shortChannelId.toLong -- privChan_opt.map(_.shortIds.localAlias.toLong), // we also add the newly validated channels to the rebroadcast queue rebroadcast = d.rebroadcast.copy( // we rebroadcast the channel to our peers @@ -420,7 +420,7 @@ object Validation { } case None if db.isPruned(u.shortChannelId) && !StaleChannels.isStale(u) => // only public channels are pruned - val realShortChannelId = u.shortChannelId.toReal + val realShortChannelId = RealShortChannelId(u.shortChannelId.toLong) // the channel was recently pruned, but if we are here, it means that the update is not stale so this is the case // of a zombie channel coming back from the dead. they probably sent us a channel_announcement right before this update, // but we ignored it because the channel was in the 'pruned' list. Now that we know that the channel is alive again, @@ -464,8 +464,8 @@ object Validation { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors // NB: we don't map remote aliases because they are decided by our peer and could overlap with ours val mappings = scia.shortIds.real.toOption match { - case Some(realScid) => Map(realScid -> scia.channelId, scia.shortIds.localAlias -> scia.channelId) - case None => Map(scia.shortIds.localAlias -> scia.channelId) + case Some(realScid) => Map(realScid.toLong -> scia.channelId, scia.shortIds.localAlias.toLong -> scia.channelId) + case None => Map(scia.shortIds.localAlias.toLong -> scia.channelId) } log.debug("handleShortChannelIdAssigned scia={} mappings={}", scia, mappings) val d1 = d.copy(scid2PrivateChannels = d.scid2PrivateChannels ++ mappings) @@ -520,7 +520,7 @@ object Validation { def handleLocalChannelDown(d: Data, localNodeId: PublicKey, lcd: LocalChannelDown)(implicit log: LoggingAdapter): Data = { import lcd.{channelId, remoteNodeId} log.debug("handleLocalChannelDown lcd={}", lcd) - val scid2PrivateChannels1 = d.scid2PrivateChannels - lcd.shortIds.localAlias -- lcd.shortIds.real.toOption + val scid2PrivateChannels1 = d.scid2PrivateChannels - lcd.shortIds.localAlias.toLong -- lcd.shortIds.real.toOption.map(_.toLong) // a local channel has permanently gone down if (lcd.shortIds.real.toOption.exists(d.channels.contains)) { // the channel was public, we will receive (or have already received) a WatchEventSpentBasic event, that will trigger a clean up of the channel diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 6a2b8f6c62..b2c491c0fd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0.{HtlcTxAndSi import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, combinedFeaturesCodec} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, TimestampSecond} +import fr.acinq.eclair.{BlockHeight, Alias, TimestampSecond} import scodec.Codec import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -374,7 +374,7 @@ private[channel] object ChannelCodecs0 { ("shortChannelId" | realshortchannelid) :: ("lastSent" | channelReadyCodec)).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val shutdownCodec: Codec[Shutdown] = ( @@ -393,7 +393,7 @@ private[channel] object ChannelCodecs0 { ("remoteShutdown" | optional(bool, shutdownCodec)) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( @@ -406,7 +406,7 @@ private[channel] object ChannelCodecs0 { ("remoteShutdown" | optional(bool, shutdownCodec)) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index e114ca14b3..384947d98f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.internal.channel.version1 import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{ByteVector32, OutPoint, Transaction, TxOut} -import fr.acinq.eclair.BlockHeight +import fr.acinq.eclair.{Alias, BlockHeight} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ @@ -244,7 +244,7 @@ private[channel] object ChannelCodecs1 { ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( @@ -257,7 +257,7 @@ private[channel] object ChannelCodecs1 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index 1a725b18f3..0e1c29770c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.internal.channel.version2 import fr.acinq.bitcoin.scalacompat.DeterministicWallet.{ExtendedPrivateKey, KeyPath} import fr.acinq.bitcoin.scalacompat.{OutPoint, Transaction, TxOut} -import fr.acinq.eclair.BlockHeight +import fr.acinq.eclair.{Alias, BlockHeight} import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ @@ -279,7 +279,7 @@ private[channel] object ChannelCodecs2 { ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( @@ -292,7 +292,7 @@ private[channel] object ChannelCodecs2 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_SHUTDOWN_Codec: Codec[DATA_SHUTDOWN] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index c7152c5c19..8d239dd94b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage -import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, LocalAlias, PermanentChannelFeature} +import fr.acinq.eclair.{BlockHeight, FeatureSupport, Features, Alias, PermanentChannelFeature} import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ import scodec.{Attempt, Codec} @@ -328,7 +328,7 @@ private[channel] object ChannelCodecs3 { ("shortChannelId" | realshortchannelid) :: ("lastSent" | lengthDelimited(channelReadyCodec))).map { case commitments :: shortChannelId :: lastSent :: HNil => - DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), lastSent = lastSent) + DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = ShortIds(real = RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), lastSent = lastSent) }.decodeOnly val DATA_WAIT_FOR_CHANNEL_READY_Codec: Codec[DATA_WAIT_FOR_CHANNEL_READY] = ( @@ -346,7 +346,7 @@ private[channel] object ChannelCodecs3 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | provide(Option.empty[ClosingFeerates]))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_COMPAT_07_Codec: Codec[DATA_NORMAL] = ( @@ -359,7 +359,7 @@ private[channel] object ChannelCodecs3 { ("remoteShutdown" | optional(bool8, lengthDelimited(shutdownCodec))) :: ("closingFeerates" | optional(bool8, closingFeeratesCodec))).map { case commitments :: shortChannelId :: buried :: channelAnnouncement :: channelUpdate :: localShutdown :: remoteShutdown :: closingFeerates :: HNil => - DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = shortChannelId.toAlias, remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) + DATA_NORMAL(commitments, shortIds = ShortIds(real = if (buried) RealScidStatus.Final(shortChannelId) else RealScidStatus.Temporary(shortChannelId), localAlias = Alias(shortChannelId.toLong), remoteAlias_opt = None), channelAnnouncement, channelUpdate, localShutdown, remoteShutdown, closingFeerates) }.decodeOnly val DATA_NORMAL_Codec: Codec[DATA_NORMAL] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala index d2476819b1..27a32898f7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/ChannelTlv.scala @@ -21,7 +21,7 @@ import fr.acinq.eclair.channel.{ChannelType, ChannelTypes} import fr.acinq.eclair.wire.protocol.ChannelTlv.ChannelTypeTlv import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.TlvCodecs.tlvStream -import fr.acinq.eclair.{FeatureSupport, Features, ShortChannelId, UInt64} +import fr.acinq.eclair.{Alias, FeatureSupport, Features, ShortChannelId, UInt64} import scodec.Codec import scodec.bits.ByteVector import scodec.codecs._ @@ -112,9 +112,9 @@ sealed trait ChannelReadyTlv extends Tlv object ChannelReadyTlv { - case class ShortChannelIdTlv(alias: ShortChannelId) extends ChannelReadyTlv + case class ShortChannelIdTlv(alias: Alias) extends ChannelReadyTlv - val channelAliasTlvCodec: Codec[ShortChannelIdTlv] = variableSizeBytesLong(varintoverflow, "alias" | shortchannelid).as[ShortChannelIdTlv] + val channelAliasTlvCodec: Codec[ShortChannelIdTlv] = variableSizeBytesLong(varintoverflow, "alias" | alias).as[ShortChannelIdTlv] val channelReadyTlvCodec: Codec[TlvStream[ChannelReadyTlv]] = tlvStream(discriminated[ChannelReadyTlv].by(varint) .typecase(UInt64(1), channelAliasTlvCodec) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 3265e4cab5..4b3e402f54 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transa import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, RealScidStatus, ShortIds} import fr.acinq.eclair.crypto.Mac32 -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, LocalAlias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, channel} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, UnspecifiedShortChannelId, Alias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, channel} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -130,11 +130,11 @@ object CommonCodecs { // number of bytes we can just skip to the next field val listofnodeaddresses: Codec[List[NodeAddress]] = variableSizeBytes(uint16, list(nodeaddress)) - val shortchannelid: Codec[ShortChannelId] = int64.xmap(l => ShortChannelId(l), s => s.toLong) + val shortchannelid: Codec[ShortChannelId] = int64.xmap(l => UnspecifiedShortChannelId(l), s => s.toLong) - val realshortchannelid: Codec[RealShortChannelId] = shortchannelid.narrow[RealShortChannelId](scid => Attempt.successful(scid.toReal), scid => scid) + val realshortchannelid: Codec[RealShortChannelId] = shortchannelid.narrow[RealShortChannelId](scid => Attempt.successful(RealShortChannelId(scid.toLong)), scid => scid) - val localalias: Codec[LocalAlias] = shortchannelid.narrow[LocalAlias](scid => Attempt.successful(scid.toAlias), scid => scid) + val alias: Codec[Alias] = shortchannelid.narrow[Alias](scid => Attempt.successful(Alias(scid.toLong)), scid => scid) val realShortChannelIdStatus: Codec[RealScidStatus] = discriminated[RealScidStatus].by(uint8) .typecase(0, provide(RealScidStatus.Unknown)) @@ -143,8 +143,8 @@ object CommonCodecs { val shortids: Codec[ShortIds] = ( ("real" | realShortChannelIdStatus) :: - ("localAlias" | discriminated[LocalAlias].by(uint16).typecase(1, localalias)) :: // forward-compatible with listOfN(uint16, localalias) in case we want to store a list of local aliases later - ("remoteAlias_opt" | optional(bool8, shortchannelid)) + ("localAlias" | discriminated[Alias].by(uint16).typecase(1, alias)) :: // forward-compatible with listOfN(uint16, aliashortchannelid) in case we want to store a list of local aliases later + ("remoteAlias_opt" | optional(bool8, alias)) ).as[ShortIds] val privateKey: Codec[PrivateKey] = Codec[PrivateKey]( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index 4da90219f8..7d8aa9f3e2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -20,12 +20,11 @@ import com.google.common.base.Charsets import com.google.common.net.InetAddresses import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, ScriptWitness, Transaction} -import fr.acinq.eclair.RealShortChannelId +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelType} import fr.acinq.eclair.payment.relay.Relayer import fr.acinq.eclair.wire.protocol.ChannelReadyTlv.ShortChannelIdTlv -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, ShortChannelId, TimestampSecond, UInt64} import scodec.bits.ByteVector import java.net.{Inet4Address, Inet6Address, InetAddress} @@ -230,7 +229,7 @@ case class FundingSigned(channelId: ByteVector32, case class ChannelReady(channelId: ByteVector32, nextPerCommitmentPoint: PublicKey, tlvStream: TlvStream[ChannelReadyTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId { - val alias_opt: Option[ShortChannelId] = tlvStream.get[ShortChannelIdTlv].map(_.alias) + val alias_opt: Option[Alias] = tlvStream.get[ShortChannelIdTlv].map(_.alias) } case class Shutdown(channelId: ByteVector32, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala index 3fda4e6d99..ed46c2e0d8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala @@ -16,8 +16,8 @@ package fr.acinq.eclair -import akka.actor.{ActorRef, Status} import akka.actor.typed.scaladsl.adapter.{ClassicActorRefOps, actorRefAdapter} +import akka.actor.{ActorRef, Status} import akka.pattern.pipe import akka.testkit.TestProbe import akka.util.Timeout @@ -25,7 +25,6 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, SatoshiLong} import fr.acinq.eclair.ApiTypes.{ChannelIdentifier, ChannelNotFound} import fr.acinq.eclair.TestConstants._ -import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.DummyOnChainWallet import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw} import fr.acinq.eclair.channel._ @@ -209,11 +208,11 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I ) val (a, b, c, d, e) = (a_priv.publicKey, b_priv.publicKey, c_priv.publicKey, d_priv.publicKey, e_priv.publicKey) - val ann_ab = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(1), a, b, a, b, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_ae = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(4), a, e, a, e, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_bc = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(2), b, c, b, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_cd = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(3), c, d, c, d, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) - val ann_ec = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(7), e, c, e, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ab = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(1), a, b, a, b, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ae = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(4), a, e, a, e, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_bc = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(2), b, c, b, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_cd = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(3), c, d, c, d, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val ann_ec = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(7), e, c, e, c, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) assert(Announcements.isNode1(a, b)) assert(Announcements.isNode1(b, c)) @@ -637,7 +636,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I val eclair = new EclairImpl(kit) eclair.channelBalances().pipeTo(sender.ref) - relayer.expectMsg(GetOutgoingChannels(enabledOnly=false)) + relayer.expectMsg(GetOutgoingChannels(enabledOnly = false)) eclair.usableBalances().pipeTo(sender.ref) relayer.expectMsg(GetOutgoingChannels()) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala index 136839af93..90dd3f0a0c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala @@ -26,22 +26,22 @@ class ShortChannelIdSpec extends AnyFunSuite { test("handle values from 0 to 0xffffffffffff") { val expected = Map( - TxCoordinates(BlockHeight(0), 0, 0) -> ShortChannelId(0), - TxCoordinates(BlockHeight(42000), 27, 3) -> ShortChannelId(0x0000a41000001b0003L), - TxCoordinates(BlockHeight(1258612), 63, 0) -> ShortChannelId(0x13347400003f0000L), - TxCoordinates(BlockHeight(0xffffff), 0x000000, 0xffff) -> ShortChannelId(0xffffff000000ffffL), - TxCoordinates(BlockHeight(0x000000), 0xffffff, 0xffff) -> ShortChannelId(0x000000ffffffffffL), - TxCoordinates(BlockHeight(0xffffff), 0xffffff, 0x0000) -> ShortChannelId(0xffffffffffff0000L), - TxCoordinates(BlockHeight(0xffffff), 0xffffff, 0xffff) -> ShortChannelId(0xffffffffffffffffL) + TxCoordinates(BlockHeight(0), 0, 0) -> RealShortChannelId(0), + TxCoordinates(BlockHeight(42000), 27, 3) -> RealShortChannelId(0x0000a41000001b0003L), + TxCoordinates(BlockHeight(1258612), 63, 0) -> RealShortChannelId(0x13347400003f0000L), + TxCoordinates(BlockHeight(0xffffff), 0x000000, 0xffff) -> RealShortChannelId(0xffffff000000ffffL), + TxCoordinates(BlockHeight(0x000000), 0xffffff, 0xffff) -> RealShortChannelId(0x000000ffffffffffL), + TxCoordinates(BlockHeight(0xffffff), 0xffffff, 0x0000) -> RealShortChannelId(0xffffffffffff0000L), + TxCoordinates(BlockHeight(0xffffff), 0xffffff, 0xffff) -> RealShortChannelId(0xffffffffffffffffL) ) for ((coord, shortChannelId) <- expected) { - assert(shortChannelId == ShortChannelId(coord.blockHeight, coord.txIndex, coord.outputIndex)) + assert(shortChannelId == RealShortChannelId(coord.blockHeight, coord.txIndex, coord.outputIndex)) assert(coord == ShortChannelId.coordinates(shortChannelId)) } } test("human readable format as per spec") { - assert(ShortChannelId(0x0000a41000001b0003L).toString == "42000x27x3") + assert(RealShortChannelId(0x0000a41000001b0003L).toString == "42000x27x3") } test("parse a short channel it") { @@ -60,9 +60,10 @@ class ShortChannelIdSpec extends AnyFunSuite { test("scids key space") { - val alias = ShortChannelId.generateLocalAlias() - val realScid = alias.toReal - val scid = ShortChannelId(realScid.toLong) + val id = 123456 + val alias = Alias(id) + val realScid = RealShortChannelId(id) + val scid = ShortChannelId(id) val m = Map(alias -> "alias", realScid -> "real", scid -> "unknown") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala index 3c1ba4ccd7..82825f9cee 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala @@ -90,8 +90,6 @@ object TestUtils { seedFile } - def realScid(l: Long): RealShortChannelId = ShortChannelId(l).toReal - /** * Subscribing to [[EventStream]] is asynchronous, which can lead to race conditions. * diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala index 6b53677fcf..544b9481db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcherSpec.scala @@ -22,7 +22,6 @@ import akka.actor.{ActorRef, Props, typed} import akka.pattern.pipe import akka.testkit.TestProbe import fr.acinq.bitcoin.scalacompat.{Block, Btc, MilliBtcDouble, OutPoint, SatoshiLong, Script, Transaction, TxOut} -import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.blockchain.WatcherSpec._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient @@ -30,7 +29,7 @@ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient.{FundTransactio import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.blockchain.{CurrentBlockHeight, NewTransaction} -import fr.acinq.eclair.{BlockHeight, ShortChannelId, TestConstants, TestKitBaseClass, randomBytes32, randomKey} +import fr.acinq.eclair.{BlockHeight, RealShortChannelId, TestConstants, TestKitBaseClass, randomBytes32, randomKey} import grizzled.slf4j.Logging import org.scalatest.BeforeAndAfterAll import org.scalatest.funsuite.AnyFunSuiteLike @@ -109,8 +108,8 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind val w1 = WatchFundingSpent(TestProbe().ref, txid, outputIndex, hints = Set.empty) val w2 = WatchFundingSpent(TestProbe().ref, txid, outputIndex, hints = Set.empty) - val w3 = WatchExternalChannelSpent(TestProbe().ref, txid, outputIndex, realScid(1)) - val w4 = WatchExternalChannelSpent(TestProbe().ref, randomBytes32(), 5, realScid(1)) + val w3 = WatchExternalChannelSpent(TestProbe().ref, txid, outputIndex, RealShortChannelId(1)) + val w4 = WatchExternalChannelSpent(TestProbe().ref, randomBytes32(), 5, RealShortChannelId(1)) val w5 = WatchFundingConfirmed(TestProbe().ref, txid, 3) // we test as if the collection was immutable @@ -211,7 +210,7 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind val (tx1, tx2) = createUnspentTxChain(tx, priv) val listener = TestProbe() - watcher ! WatchExternalChannelSpent(listener.ref, tx.txid, outputIndex, realScid(5)) + watcher ! WatchExternalChannelSpent(listener.ref, tx.txid, outputIndex, RealShortChannelId(5)) watcher ! WatchFundingSpent(listener.ref, tx.txid, outputIndex, Set.empty) listener.expectNoMessage(1 second) @@ -222,7 +221,7 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind probe.expectMsg(tx1.txid) // tx and tx1 aren't confirmed yet, but we trigger the WatchEventSpent when we see tx1 in the mempool. listener.expectMsgAllOf( - WatchExternalChannelSpentTriggered(realScid(5)), + WatchExternalChannelSpentTriggered(RealShortChannelId(5)), WatchFundingSpentTriggered(tx1) ) // Let's confirm tx and tx1: seeing tx1 in a block should trigger WatchEventSpent again, but not WatchEventSpentBasic @@ -268,8 +267,8 @@ class ZmqWatcherSpec extends TestKitBaseClass with AnyFunSuiteLike with Bitcoind listener.expectMsg(WatchOutputSpentTriggered(tx1)) watcher ! StopWatching(listener.ref) - watcher ! WatchExternalChannelSpent(listener.ref, tx1.txid, 0, realScid(1)) - listener.expectMsg(WatchExternalChannelSpentTriggered(realScid(1))) + watcher ! WatchExternalChannelSpent(listener.ref, tx1.txid, 0, RealShortChannelId(1)) + listener.expectMsg(WatchExternalChannelSpentTriggered(RealShortChannelId(1))) watcher ! WatchFundingSpent(listener.ref, tx1.txid, 0, Set.empty) listener.expectMsg(WatchFundingSpentTriggered(tx2)) }) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 0987e2018d..7abdf93f85 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3423,7 +3423,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // funding tx coordinates (unknown before) val (blockHeight, txIndex) = (BlockHeight(400000), 42) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) - val realShortChannelId = ShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) + val realShortChannelId = RealShortChannelId(blockHeight, txIndex, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state @@ -3440,7 +3440,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // new funding tx coordinates (there was a reorg) val (blockHeight1, txIndex1) = (blockHeight + 10, txIndex + 10) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight1, txIndex1, null) - val newRealShortChannelId = ShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) + val newRealShortChannelId = RealShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] assert(annSigs.shortChannelId == newRealShortChannelId) // update data with real short channel id @@ -3471,7 +3471,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // zero-conf channel : the funding tx isn't confirmed assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42000), 42, null) - val realShortChannelId = ShortChannelId(BlockHeight(42000), 42, 0) + 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 prefer the remote alias, so there is no change in the channel_update, and we don't send a new one @@ -3490,7 +3490,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // new funding tx coordinates (there was a reorg) val (blockHeight1, txIndex1) = (blockHeight + 10, txIndex + 10) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight1, txIndex1, null) - val newRealShortChannelId = ShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) + val newRealShortChannelId = RealShortChannelId(blockHeight1, txIndex1, alice.stateData.asInstanceOf[DATA_NORMAL].commitments.commitInput.outPoint.index.toInt) // update data with real short channel id awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala index b75e8443b0..1a8c6e75d4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala @@ -19,7 +19,6 @@ package fr.acinq.eclair.db import com.softwaremill.quicklens._ import fr.acinq.bitcoin.scalacompat.ByteVector32 import fr.acinq.eclair.TestDatabases.{TestPgDatabases, TestSqliteDatabases, migrationCheck} -import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.channel.RealScidStatus import fr.acinq.eclair.db.ChannelsDbSpec.{getPgTimestamp, getTimestamp, testCases} import fr.acinq.eclair.db.DbEventHandler.ChannelEvent @@ -30,7 +29,7 @@ import fr.acinq.eclair.db.sqlite.SqliteChannelsDb import fr.acinq.eclair.db.sqlite.SqliteUtils.ExtendedResultSet._ import fr.acinq.eclair.wire.internal.channel.ChannelCodecs.channelDataCodec import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec -import fr.acinq.eclair.{CltvExpiry, ShortChannelId, TestDatabases, randomBytes32} +import fr.acinq.eclair.{CltvExpiry, RealShortChannelId, TestDatabases, randomBytes32} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.ByteVector @@ -62,7 +61,7 @@ class ChannelsDbSpec extends AnyFunSuite { val channel1 = ChannelCodecsSpec.normal val channel2a = ChannelCodecsSpec.normal.modify(_.commitments.channelId).setTo(randomBytes32()) - val channel2b = channel2a.modify(_.shortIds.real).setTo(RealScidStatus.Final(realScid(189371))) + val channel2b = channel2a.modify(_.shortIds.real).setTo(RealScidStatus.Final(RealShortChannelId(189371))) val commitNumber = 42 val paymentHash1 = ByteVector32.Zeroes diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala index 02cfa815e6..3d56d767fb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/NetworkDbSpec.scala @@ -21,7 +21,6 @@ import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, import fr.acinq.eclair.FeatureSupport.Optional import fr.acinq.eclair.Features.VariableLengthOnion import fr.acinq.eclair.TestDatabases._ -import fr.acinq.eclair.TestUtils.realScid import fr.acinq.eclair.db.jdbc.JdbcUtils.using import fr.acinq.eclair.db.pg.PgNetworkDb import fr.acinq.eclair.db.sqlite.SqliteNetworkDb @@ -30,7 +29,7 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Router.PublicChannel import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelAnnouncementCodec, channelUpdateCodec, nodeAnnouncementCodec} import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, TestDatabases, randomBytes32, randomKey} +import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TestDatabases, randomBytes32, randomKey} import org.scalatest.funsuite.AnyFunSuite import scala.collection.{SortedMap, mutable} @@ -82,7 +81,7 @@ class NetworkDbSpec extends AnyFunSuite { forAllDbs { dbs => val db = dbs.network val sig = ByteVector64.Zeroes - val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(42), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) val txid = ByteVector32.fromValidHex("0001" * 16) db.addChannel(c, txid, Satoshi(42)) assert(db.listChannels() == SortedMap(c.shortChannelId -> PublicChannel(c, txid, Satoshi(42), None, None, None))) @@ -105,9 +104,9 @@ class NetworkDbSpec extends AnyFunSuite { val b = generatePubkeyHigherThan(a) val c = generatePubkeyHigherThan(b) - val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), a.publicKey, b.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) - val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(43), a.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) - val channel_3 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(44), b.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(42), a.publicKey, b.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(43), a.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) + val channel_3 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(44), b.publicKey, c.publicKey, randomKey().publicKey, randomKey().publicKey, sig, sig, sig, sig) val txid_1 = randomBytes32() val txid_2 = randomBytes32() @@ -195,7 +194,7 @@ class NetworkDbSpec extends AnyFunSuite { } } - val shortChannelIds: Seq[ShortChannelId] = (42 to (5000 + 42)).map(i => ShortChannelId(i)) + val shortChannelIds: Seq[RealShortChannelId] = (42 to (5000 + 42)).map(i => RealShortChannelId(i)) test("remove many channels") { forAllDbs { dbs => @@ -205,7 +204,7 @@ class NetworkDbSpec extends AnyFunSuite { val pub = priv.publicKey val capacity = 10000 sat - val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id.toReal, pub, pub, pub, pub, sig, sig, sig, sig)) + val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id, pub, pub, pub, pub, sig, sig, sig, sig)) val template = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv, pub, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true) val updates = shortChannelIds.map(id => template.copy(shortChannelId = id)) val txid = randomBytes32() @@ -223,9 +222,9 @@ class NetworkDbSpec extends AnyFunSuite { forAllDbs { dbs => val db = dbs.network - db.addToPruned(shortChannelIds.map(_.toReal)) + db.addToPruned(shortChannelIds) shortChannelIds.foreach { id => assert(db.isPruned(id)) } - db.removeFromPruned(realScid(5)) + db.removeFromPruned(RealShortChannelId(5)) assert(!db.isPruned(ShortChannelId(5))) } } @@ -382,7 +381,7 @@ object NetworkDbSpec { val channelTestCases: Seq[ChannelTestCase] = for (_ <- 0 until 10) yield { val a = randomKey() val b = generatePubkeyHigherThan(a) - val channel = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(Random.nextInt(1_000_000)).toReal, a.publicKey, a.publicKey, randomKey().publicKey, randomKey().publicKey, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) + val channel = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(Random.nextInt(1_000_000)), a.publicKey, a.publicKey, randomKey().publicKey, randomKey().publicKey, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes, ByteVector64.Zeroes) val channel_update_1_opt = if (Random.nextBoolean()) { Some(Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, a, b.publicKey, channel.shortChannelId, CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, Random.nextBoolean())) } else None diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala index a1bbda4deb..a1c8306f80 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ThreeNodesIntegrationSpec.scala @@ -46,9 +46,9 @@ class ThreeNodesIntegrationSpec extends FixtureSpec with IntegrationPatience { // alice now knows about bob-carol eventually { val routerData = getRouterData(alice) - //prettyPrint(routerData, alice, bob, carol) + prettyPrint(routerData, alice, bob, carol) assert(routerData.channels.size == 2) // 2 channels - assert(routerData.channels.values.flatMap(c => c.update_1_opt.toSeq ++ c.update_2_opt.toSeq).size == 3) // only 3 channel_updates because c->b is disabled (all funds on b) + assert(routerData.channels.values.flatMap(c => c.update_1_opt.toSeq ++ c.update_2_opt.toSeq).size == 4) // 2 channel_updates per channel } sendPayment(alice, carol, 100_000 msat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index b069290d02..4f4164552a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -245,7 +245,7 @@ object MinimalNodeFixture extends Assertions { val blockHeight = txId.take(3).toInt(signed = false) val txIndex = txId.takeRight(2).toInt(signed = false) val outputIndex = 0 // funding txs created by the dummy wallet used in tests only have one output - ShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex).toReal + RealShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex) } /** All known funding txs (we don't evaluate immediately because new ones could be created) */ @@ -259,7 +259,7 @@ object MinimalNodeFixture extends Assertions { case watch: ZmqWatcher.WatchFundingConfirmed => val realScid = if (watch.minDepth == 0) { // special case for zero-conf channels, this is what the real watcher does - ShortChannelId(BlockHeight(0), 0, 0).toReal + RealShortChannelId(BlockHeight(0), 0, 0) } else { deterministicShortId(watch.txId) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala index 1391164755..5985109de1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerConnectionSpec.scala @@ -23,7 +23,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.{BasicMultiPartPayment, ChannelRangeQueries, PaymentSecret, VariableLengthOnion} import fr.acinq.eclair.TestConstants._ -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.message.OnionMessages.{Recipient, buildMessage} @@ -334,7 +334,7 @@ class PeerConnectionSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wi val query = QueryShortChannelIds( Alice.nodeParams.chainHash, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(42000))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(42000))), TlvStream.empty) // make sure that routing messages go through diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 2c859a8b3b..0249b9a53f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -308,12 +308,12 @@ class JsonSerializersSpec extends AnyFunSuite with Matchers { test("serialize short ids") { val testCases = Map( - ShortIds(real = RealScidStatus.Unknown, localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = Some(ShortChannelId("7x7x7"))) -> - """{"real":{"status":"unknown"},"localAlias":"1x2x3","remoteAlias":"7x7x7"}""", - ShortIds(real = RealScidStatus.Temporary(ShortChannelId("500000x42x1").toReal), localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = None) -> - """{"real":{"status":"temporary","realScid":"500000x42x1"},"localAlias":"1x2x3"}""", - ShortIds(real = RealScidStatus.Final(ShortChannelId("500000x42x1").toReal), localAlias = ShortChannelId("1x2x3").toAlias, remoteAlias_opt = None) -> - """{"real":{"status":"final","realScid":"500000x42x1"},"localAlias":"1x2x3"}""", + ShortIds(real = RealScidStatus.Unknown, localAlias = Alias(0x4455), remoteAlias_opt = Some(Alias(0x88888888L))) -> + """{"real":{"status":"unknown"},"localAlias":"0x4455","remoteAlias":"0x88888888"}""", + ShortIds(real = RealScidStatus.Temporary(RealShortChannelId(BlockHeight(500000), 42, 1)), localAlias = Alias(0x4455), remoteAlias_opt = None) -> + """{"real":{"status":"temporary","realScid":"500000x42x1"},"localAlias":"0x4455"}""", + ShortIds(real = RealScidStatus.Final(RealShortChannelId(BlockHeight(500000), 42, 1)), localAlias = Alias(0x4455), remoteAlias_opt = None) -> + """{"real":{"status":"final","realScid":"500000x42x1"},"localAlias":"0x4455"}""", ) for ((obj, json) <- testCases) { JsonSerializers.serialization.write(obj)(JsonSerializers.formats) shouldBe json diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index b7d83a9d36..13a45c188b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -714,7 +714,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // | | and b -> h has fees = 0 // +---(5)--> g ---(6)--> h // and e --(4)--> f (we are a) - val channelId_bh = ShortChannelId(BlockHeight(420000), 100, 0).toReal + val channelId_bh = RealShortChannelId(BlockHeight(420000), 100, 0) val chan_bh = channelAnnouncement(channelId_bh, priv_b, priv_h, priv_funding_b, priv_funding_h) val channelUpdate_bh = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, h, channelId_bh, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 0 msat, feeProportionalMillionths = 0, htlcMaximumMsat = 500000000 msat) val channelUpdate_hb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_h, b, channelId_bh, CltvExpiryDelta(9), htlcMinimumMsat = 0 msat, feeBaseMsat = 10 msat, feeProportionalMillionths = 8, htlcMaximumMsat = 500000000 msat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index fbc506c7b7..b123d35c51 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong} import fr.acinq.eclair.Features.ScidAlias import fr.acinq.eclair.TestConstants.emptyOnionPacket -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel._ import fr.acinq.eclair.payment.IncomingPaymentPacket.ChannelRelayPacket @@ -139,7 +139,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a expectFwdAdd(register, lcu1.channelId, outgoingAmount, outgoingExpiry) // reorg happens - val realScid1AfterReorg = ShortChannelId(111112).toReal + val realScid1AfterReorg = RealShortChannelId(111112) val lcu2 = createLocalUpdate(channelId1).modify(_.shortIds.real).setTo(RealScidStatus.Final(realScid1AfterReorg)) val payload2 = RelayLegacyPayload(realScid1AfterReorg, outgoingAmount, outgoingExpiry) val r2 = createValidIncomingPacket(payload2) @@ -379,12 +379,12 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val (a, b) = (randomKey().publicKey, randomKey().publicKey) val channelUpdates = Map( - ShortChannelId(11111) -> dummyLocalUpdate(realScid(11111), a, 100000000 msat, 200000 sat), - ShortChannelId(12345) -> dummyLocalUpdate(realScid(12345), a, 10000000 msat, 200000 sat), - ShortChannelId(22222) -> dummyLocalUpdate(realScid(22222), a, 10000000 msat, 100000 sat), - ShortChannelId(22223) -> dummyLocalUpdate(realScid(22223), a, 9000000 msat, 50000 sat), - ShortChannelId(33333) -> dummyLocalUpdate(realScid(33333), a, 100000 msat, 50000 sat), - ShortChannelId(44444) -> dummyLocalUpdate(realScid(44444), b, 1000000 msat, 10000 sat), + ShortChannelId(11111) -> dummyLocalUpdate(RealShortChannelId(11111), a, 100000000 msat, 200000 sat), + ShortChannelId(12345) -> dummyLocalUpdate(RealShortChannelId(12345), a, 10000000 msat, 200000 sat), + ShortChannelId(22222) -> dummyLocalUpdate(RealShortChannelId(22222), a, 10000000 msat, 100000 sat), + ShortChannelId(22223) -> dummyLocalUpdate(RealShortChannelId(22223), a, 9000000 msat, 50000 sat), + ShortChannelId(33333) -> dummyLocalUpdate(RealShortChannelId(33333), a, 100000 msat, 50000 sat), + ShortChannelId(44444) -> dummyLocalUpdate(RealShortChannelId(44444), b, 1000000 msat, 10000 sat), ) channelUpdates.values.foreach(u => channelRelayer ! WrappedLocalChannelUpdate(u)) @@ -516,8 +516,8 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a import f._ val channelId_ab = randomBytes32() val channelId_bc = randomBytes32() - val shortIds_ab = ShortIds(RealScidStatus.Final(channelUpdate_ab.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) - val shortIds_bc = ShortIds(RealScidStatus.Final(channelUpdate_bc.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + val shortIds_ab = ShortIds(RealScidStatus.Final(RealShortChannelId(channelUpdate_ab.shortChannelId.toLong)), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + val shortIds_bc = ShortIds(RealScidStatus.Final(RealShortChannelId(channelUpdate_bc.shortChannelId.toLong)), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) val a = PaymentPacketSpec.a val sender = TestProbe[Relayer.OutgoingChannels]() @@ -568,11 +568,11 @@ object ChannelRelayerSpec { val outgoingExpiry: CltvExpiry = CltvExpiry(400000) val outgoingNodeId: PublicKey = randomKey().publicKey - val realScid1: RealShortChannelId = ShortChannelId(111111).toReal - val realScId2: RealShortChannelId = ShortChannelId(222222).toReal + val realScid1: RealShortChannelId = RealShortChannelId(111111) + val realScId2: RealShortChannelId = RealShortChannelId(222222) - val localAlias1: LocalAlias = ShortChannelId(111000).toAlias - val localAlias2: LocalAlias = ShortChannelId(222000).toAlias + val localAlias1: Alias = Alias(111000) + val localAlias2: Alias = Alias(222000) val remoteAlias1: ShortChannelId = ShortChannelId(111999) @@ -593,7 +593,7 @@ object ChannelRelayerSpec { def createShortIds(channelId: ByteVector32) = { val realScid = channelIds.collectFirst { case (realScid: RealShortChannelId, cid) if cid == channelId => realScid }.get - val localAlias = channelIds.collectFirst { case (localAlias: LocalAlias, cid) if cid == channelId => localAlias }.get + val localAlias = channelIds.collectFirst { case (localAlias: Alias, cid) if cid == channelId => localAlias }.get ShortIds(real = RealScidStatus.Final(realScid), localAlias, remoteAlias_opt = None) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala index 13c63e2b4d..89aaef7e17 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala @@ -78,7 +78,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat assert(sender.expectMessageType[Relayer.OutgoingChannels].channels.isEmpty) // We publish a channel update, that should be picked up by the channel relayer - val shortIds_bc = ShortIds(RealScidStatus.Final(channelUpdate_bc.shortChannelId.toReal), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + val shortIds_bc = ShortIds(RealScidStatus.Final(RealShortChannelId(channelUpdate_bc.shortChannelId.toLong)), ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) system.eventStream ! EventStream.Publish(LocalChannelUpdate(null, channelId_bc, shortIds_bc, c, None, channelUpdate_bc, makeCommitments(channelId_bc))) eventually(PatienceConfiguration.Timeout(30 seconds), PatienceConfiguration.Interval(1 second)) { childActors.channelRelayer ! ChannelRelayer.GetOutgoingChannels(sender.ref.toClassic, GetOutgoingChannels()) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala index 55aa7e6641..b5d7775a4f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala @@ -28,7 +28,7 @@ import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, Bitco import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshiLong, ShortChannelId, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshiLong, RealShortChannelId, ShortChannelId, randomKey} import org.json4s.JsonAST.JString import org.scalatest.funsuite.AnyFunSuite @@ -64,10 +64,10 @@ class AnnouncementsBatchValidationSpec extends AnyFunSuite { bitcoinClient.validate(announcements(0)).pipeTo(sender.ref) sender.expectMsgType[ValidateResult].fundingTx.isRight - bitcoinClient.validate(announcements(1).copy(shortChannelId = ShortChannelId(Long.MaxValue).toReal)).pipeTo(sender.ref) // invalid block height + bitcoinClient.validate(announcements(1).copy(shortChannelId = RealShortChannelId(Long.MaxValue))).pipeTo(sender.ref) // invalid block height sender.expectMsgType[ValidateResult].fundingTx.isRight - bitcoinClient.validate(announcements(2).copy(shortChannelId = ShortChannelId(BlockHeight(500), 1000, 0))).pipeTo(sender.ref) // invalid tx index + bitcoinClient.validate(announcements(2).copy(shortChannelId = RealShortChannelId(BlockHeight(500), 1000, 0))).pipeTo(sender.ref) // invalid tx index sender.expectMsgType[ValidateResult].fundingTx.isRight } @@ -101,7 +101,7 @@ object AnnouncementsBatchValidationSpec { def makeChannelAnnouncement(c: SimulatedChannel, bitcoinClient: BitcoinCoreClient)(implicit ec: ExecutionContext): ChannelAnnouncement = { val (blockHeight, txIndex) = Await.result(bitcoinClient.getTransactionShortId(c.fundingTx.txid), 10 seconds) - val shortChannelId = ShortChannelId(blockHeight, txIndex, c.fundingOutputIndex) + val shortChannelId = RealShortChannelId(blockHeight, txIndex, c.fundingOutputIndex) val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, shortChannelId, c.node1Key.publicKey, c.node2Key.publicKey, c.node1FundingKey.publicKey, c.node2FundingKey.publicKey, Features.empty) val channelAnnNodeSig1 = Announcements.signChannelAnnouncement(witness, c.node1Key) val channelAnnBitcoinSig1 = Announcements.signChannelAnnouncement(witness, c.node1FundingKey) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index 8f9145a4f1..b82360bc0f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.router import fr.acinq.bitcoin.scalacompat.Block import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.TestConstants.Alice -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.router.Announcements._ import fr.acinq.eclair.wire.protocol.ChannelUpdate.ChannelFlags @@ -44,12 +44,12 @@ class AnnouncementsSpec extends AnyFunSuite { test("create valid signed channel announcement") { val (node_a, node_b, bitcoin_a, bitcoin_b) = (randomKey(), randomKey(), randomKey(), randomKey()) - val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, realScid(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, Features.empty) + val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, RealShortChannelId(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, Features.empty) val node_a_sig = Announcements.signChannelAnnouncement(witness, node_a) val bitcoin_a_sig = Announcements.signChannelAnnouncement(witness, bitcoin_a) val node_b_sig = Announcements.signChannelAnnouncement(witness, node_b) val bitcoin_b_sig = Announcements.signChannelAnnouncement(witness, bitcoin_b) - val ann = makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, realScid(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig) + val ann = makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, RealShortChannelId(42), node_a.publicKey, node_b.publicKey, bitcoin_a.publicKey, bitcoin_b.publicKey, node_a_sig, node_b_sig, bitcoin_a_sig, bitcoin_b_sig) assert(checkSigs(ann)) assert(checkSigs(ann.copy(nodeId1 = randomKey().publicKey)) == false) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index 9ec52c8ee9..6cc08c32b2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -73,12 +73,12 @@ abstract class BaseRouterSpec extends TestKitBaseClass with FixtureAnyFunSuiteLi val node_g = makeNodeAnnouncement(priv_g, "node-G", Color(30, 10, -50), Nil, Features.empty) val node_h = makeNodeAnnouncement(priv_h, "node-H", Color(30, 10, -50), Nil, Features.empty) - val scid_ab = ShortChannelId(BlockHeight(420000), 1, 0).toReal - val scid_bc = ShortChannelId(BlockHeight(420000), 2, 0).toReal - val scid_cd = ShortChannelId(BlockHeight(420000), 3, 0).toReal - val scid_ef = ShortChannelId(BlockHeight(420000), 4, 0).toReal - val scid_ag_private = ShortChannelId(BlockHeight(420000), 5, 0).toReal - val scid_gh = ShortChannelId(BlockHeight(420000), 6, 0).toReal + val scid_ab = RealShortChannelId(BlockHeight(420000), 1, 0) + val scid_bc = RealShortChannelId(BlockHeight(420000), 2, 0) + val scid_cd = RealShortChannelId(BlockHeight(420000), 3, 0) + val scid_ef = RealShortChannelId(BlockHeight(420000), 4, 0) + val scid_ag_private = RealShortChannelId(BlockHeight(420000), 5, 0) + val scid_gh = RealShortChannelId(BlockHeight(420000), 6, 0) val channelId_ag_private = randomBytes32() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala index 15322ef18a..2baf31a3a8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala @@ -142,7 +142,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } else { outputIndex = outputIndex + 1 } - output += ShortChannelId(height, txIndex, outputIndex).toReal + output += RealShortChannelId(height, txIndex, outputIndex) } output.toList } @@ -190,7 +190,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { test("split short channel ids correctly (basic tests") { - def id(blockHeight: Int, txIndex: Int = 0, outputIndex: Int = 0) = ShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex).toReal + def id(blockHeight: Int, txIndex: Int = 0, outputIndex: Int = 0) = RealShortChannelId(BlockHeight(blockHeight), txIndex, outputIndex) // no ids to split { @@ -358,7 +358,7 @@ class ChannelRangeQueriesSpec extends AnyFunSuite { } test("encode maximum size reply_channel_range") { - val scids = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => ShortChannelId(i).toReal).toList + val scids = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => RealShortChannelId(i)).toList val timestamps = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => Timestamps(i.unixsec, (i + 1).unixsec)).toList val checksums = (1 to Sync.MAXIMUM_CHUNK_SIZE).map(i => Checksums(i, i + 1)).toList val reply = ReplyChannelRange(Block.RegtestGenesisBlock.hash, BlockHeight(0), 100, 0, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, scids), Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, timestamps)), Some(EncodedChecksums(checksums))) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index 2920c97ed7..aca83a802d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.router import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Satoshi, SatoshiLong} -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.payment.Bolt11Invoice.ExtraHop import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate @@ -30,7 +30,7 @@ import fr.acinq.eclair.router.RouteCalculation._ import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Transactions import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TimestampSecond, TimestampSecondLong, ToMilliSatoshiConversion, randomKey} import org.scalatest.funsuite.AnyFunSuite import org.scalatest.{ParallelTestExecution, Tag} import scodec.bits._ @@ -916,7 +916,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { // This test have a channel (542280x2156x0) that according to heuristics is very convenient but actually useless to reach the target, // then if the cost function is not monotonic the path-finding breaks because the result path contains a loop. val updates = SortedMap( - ShortChannelId("565643x1216x0").toReal -> PublicChannel( + RealShortChannelId(BlockHeight(565643), 1216, 0) -> PublicChannel( ann = makeChannel(ShortChannelId("565643x1216x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"024655b768ef40951b20053a5c4b951606d4d86085d51238f2c67c7dec29c792ca")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -924,7 +924,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("565643x1216x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 0 msat, feeBaseMsat = 1000 msat, 100, Some(15000000000L msat))), meta_opt = None ), - ShortChannelId("542280x2156x0").toReal -> PublicChannel( + RealShortChannelId(BlockHeight(542280), 2156, 0) -> PublicChannel( ann = makeChannel(ShortChannelId("542280x2156x0").toLong, PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), PublicKey(hex"03cb7983dc247f9f81a0fa2dfa3ce1c255365f7279c8dd143e086ca333df10e278")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -932,7 +932,7 @@ class RouteCalculationSpec extends AnyFunSuite with ParallelTestExecution { update_2_opt = Some(ChannelUpdate(ByteVector64.Zeroes, ByteVector32.Zeroes, ShortChannelId("542280x2156x0"), 0 unixsec, ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), CltvExpiryDelta(144), htlcMinimumMsat = 1 msat, feeBaseMsat = 667 msat, 1, Some(16777000000L msat))), meta_opt = None ), - ShortChannelId("565779x2711x0").toReal -> PublicChannel( + RealShortChannelId(BlockHeight(565779), 2711, 0) -> PublicChannel( ann = makeChannel(ShortChannelId("565779x2711x0").toLong, PublicKey(hex"036d65409c41ab7380a43448f257809e7496b52bf92057c09c4f300cbd61c50d96"), PublicKey(hex"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")), fundingTxid = ByteVector32.Zeroes, capacity = DEFAULT_CAPACITY, @@ -1949,7 +1949,7 @@ object RouteCalculationSpec { def makeChannel(shortChannelId: Long, nodeIdA: PublicKey, nodeIdB: PublicKey): ChannelAnnouncement = { val (nodeId1, nodeId2) = if (Announcements.isNode1(nodeIdA, nodeIdB)) (nodeIdA, nodeIdB) else (nodeIdB, nodeIdA) - ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, Features.empty, Block.RegtestGenesisBlock.hash, realScid(shortChannelId), nodeId1, nodeId2, randomKey().publicKey, randomKey().publicKey) + ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, DUMMY_SIG, Features.empty, Block.RegtestGenesisBlock.hash, RealShortChannelId(shortChannelId), nodeId1, nodeId2, randomKey().publicKey, randomKey().publicKey) } def makeEdge(shortChannelId: Long, @@ -1963,7 +1963,7 @@ object RouteCalculationSpec { capacity: Satoshi = DEFAULT_CAPACITY, balance_opt: Option[MilliSatoshi] = None): GraphEdge = { val update = makeUpdateShort(ShortChannelId(shortChannelId), nodeId1, nodeId2, feeBase, feeProportionalMillionth, minHtlc, maxHtlc, cltvDelta) - GraphEdge(ChannelDesc(ShortChannelId(shortChannelId), nodeId1, nodeId2), ChannelRelayParams.FromAnnouncement(update), capacity, balance_opt) + GraphEdge(ChannelDesc(RealShortChannelId(shortChannelId), nodeId1, nodeId2), ChannelRelayParams.FromAnnouncement(update), capacity, balance_opt) } def makeUpdateShort(shortChannelId: ShortChannelId, nodeId1: PublicKey, nodeId2: PublicKey, feeBase: MilliSatoshi, feeProportionalMillionth: Int, minHtlc: MilliSatoshi = DEFAULT_AMOUNT_MSAT, maxHtlc: Option[MilliSatoshi] = None, cltvDelta: CltvExpiryDelta = CltvExpiryDelta(0), timestamp: TimestampSecond = 0 unixsec): ChannelUpdate = diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 58dbe5447d..fcd54e96cc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -22,7 +22,7 @@ import akka.testkit.TestProbe import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Script.{pay2wsh, write} import fr.acinq.bitcoin.scalacompat.{Block, SatoshiLong, Transaction, TxOut} -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel.{AvailableBalanceChanged, CommitmentsSpec, LocalChannelUpdate, RealScidStatus, ShortIds} import fr.acinq.eclair.crypto.TransportHandler @@ -35,7 +35,7 @@ import fr.acinq.eclair.router.RouteCalculationSpec.{DEFAULT_AMOUNT_MSAT, DEFAULT import fr.acinq.eclair.router.Router._ import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampSecond, nodeFee, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TestConstants, TimestampSecond, nodeFee, randomKey} import scodec.bits._ import scala.concurrent.duration._ @@ -55,7 +55,7 @@ class RouterSpec extends BaseRouterSpec { { // valid channel announcement, no stashing - val chan_ac = channelAnnouncement(ShortChannelId(BlockHeight(420000), 5, 0), priv_a, priv_c, priv_funding_a, priv_funding_c) + val chan_ac = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 5, 0), priv_a, priv_c, priv_funding_a, priv_funding_c) val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, chan_ac.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) val node_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures(), timestamp = TimestampSecond.now() + 1) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ac)) @@ -85,7 +85,7 @@ class RouterSpec extends BaseRouterSpec { // valid channel announcement, stashing while validating channel announcement val priv_u = randomKey() val priv_funding_u = randomKey() - val chan_uc = channelAnnouncement(ShortChannelId(BlockHeight(420000), 100, 0), priv_u, priv_c, priv_funding_u, priv_funding_c) + val chan_uc = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 100, 0), priv_u, priv_c, priv_funding_u, priv_funding_c) val update_uc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_u, c, chan_uc.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) val node_u = makeNodeAnnouncement(priv_u, "node-U", Color(-120, -20, 60), Nil, Features.empty) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_uc)) @@ -130,7 +130,7 @@ class RouterSpec extends BaseRouterSpec { { // invalid signatures val invalid_node_b = node_b.copy(timestamp = node_b.timestamp + 10) - val invalid_chan_ac = channelAnnouncement(ShortChannelId(BlockHeight(420000), 101, 1), priv_a, priv_c, priv_funding_a, priv_funding_c).copy(nodeId1 = randomKey().publicKey) + val invalid_chan_ac = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 101, 1), priv_a, priv_c, priv_funding_a, priv_funding_c).copy(nodeId1 = randomKey().publicKey) val invalid_update_ab = update_ab.copy(cltvExpiryDelta = CltvExpiryDelta(21), timestamp = update_ab.timestamp + 1) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, invalid_node_b)) peerConnection.expectMsg(TransportHandler.ReadAck(invalid_node_b)) @@ -150,7 +150,7 @@ class RouterSpec extends BaseRouterSpec { // pruned channel val priv_v = randomKey() val priv_funding_v = randomKey() - val chan_vc = channelAnnouncement(ShortChannelId(BlockHeight(420000), 102, 0), priv_v, priv_c, priv_funding_v, priv_funding_c) + val chan_vc = channelAnnouncement(RealShortChannelId(BlockHeight(420000), 102, 0), priv_v, priv_c, priv_funding_v, priv_funding_c) nodeParams.db.network.addToPruned(chan_vc.shortChannelId :: Nil) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_vc)) peerConnection.expectMsg(TransportHandler.ReadAck(chan_vc)) @@ -191,7 +191,7 @@ class RouterSpec extends BaseRouterSpec { // invalid announcement + reject stashed val priv_y = randomKey() val priv_funding_y = randomKey() // a-y will have an invalid script - val chan_ay = channelAnnouncement(realScid(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) + val chan_ay = channelAnnouncement(RealShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y) val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, htlcMaximum) val node_y = makeNodeAnnouncement(priv_y, "node-Y", Color(123, 100, -40), Nil, TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ay)) @@ -213,7 +213,7 @@ class RouterSpec extends BaseRouterSpec { { // validation failure val priv_x = randomKey() - val chan_ax = channelAnnouncement(realScid(42001), priv_a, priv_x, priv_funding_a, randomKey()) + val chan_ax = channelAnnouncement(RealShortChannelId(42001), priv_a, priv_x, priv_funding_a, randomKey()) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_ax)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_ax) watcher.send(router, ValidateResult(chan_ax, Left(new RuntimeException("funding tx not found")))) @@ -228,7 +228,7 @@ class RouterSpec extends BaseRouterSpec { // funding tx spent (funding tx not confirmed) val priv_z = randomKey() val priv_funding_z = randomKey() - val chan_az = channelAnnouncement(realScid(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) + val chan_az = channelAnnouncement(RealShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_az)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_az) watcher.send(router, ValidateResult(chan_az, Right(Transaction(version = 0, txIn = Nil, txOut = TxOut(1000000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_z.publicKey)))) :: Nil, lockTime = 0), UtxoStatus.Spent(spendingTxConfirmed = false)))) @@ -243,7 +243,7 @@ class RouterSpec extends BaseRouterSpec { // funding tx spent (funding tx confirmed) val priv_z = randomKey() val priv_funding_z = randomKey() - val chan_az = channelAnnouncement(realScid(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) + val chan_az = channelAnnouncement(RealShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, chan_az)) assert(watcher.expectMsgType[ValidateRequest].ann == chan_az) watcher.send(router, ValidateResult(chan_az, Right(Transaction(version = 0, txIn = Nil, txOut = TxOut(1000000 sat, write(pay2wsh(Scripts.multiSig2of2(funding_a, priv_funding_z.publicKey)))) :: Nil, lockTime = 0), UtxoStatus.Spent(spendingTxConfirmed = true)))) @@ -286,7 +286,7 @@ class RouterSpec extends BaseRouterSpec { test("handle bad signature for ChannelAnnouncement") { fixture => import fixture._ val peerConnection = TestProbe() - val channelId_ac = ShortChannelId(BlockHeight(420000), 105, 0) + val channelId_ac = RealShortChannelId(BlockHeight(420000), 105, 0) val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c) val buggy_chan_ac = chan_ac.copy(nodeSignature1 = chan_ac.nodeSignature2) peerConnection.send(router, PeerRoutingMessage(peerConnection.ref, remoteNodeId, buggy_chan_ac)) @@ -524,7 +524,7 @@ class RouterSpec extends BaseRouterSpec { val targetNodeId = randomKey().publicKey { - val invoiceRoutingHint = ExtraHop(b, ShortChannelId(BlockHeight(420000), 516, 1105), 10 msat, 150, CltvExpiryDelta(96)) + val invoiceRoutingHint = ExtraHop(b, RealShortChannelId(BlockHeight(420000), 516, 1105), 10 msat, 150, CltvExpiryDelta(96)) val preComputedRoute = PredefinedChannelRoute(targetNodeId, Seq(scid_ab, invoiceRoutingHint.shortChannelId)) val amount = 10_000.msat // the amount affects the way we estimate the channel capacity of the hinted channel @@ -539,7 +539,7 @@ class RouterSpec extends BaseRouterSpec { assert(route.hops.last.params == ChannelRelayParams.FromHint(invoiceRoutingHint, RoutingHeuristics.CAPACITY_CHANNEL_LOW + nodeFee(invoiceRoutingHint.feeBase, invoiceRoutingHint.feeProportionalMillionths, RoutingHeuristics.CAPACITY_CHANNEL_LOW))) } { - val invoiceRoutingHint = ExtraHop(h, ShortChannelId(BlockHeight(420000), 516, 1105), 10 msat, 150, CltvExpiryDelta(96)) + val invoiceRoutingHint = ExtraHop(h, RealShortChannelId(BlockHeight(420000), 516, 1105), 10 msat, 150, CltvExpiryDelta(96)) val preComputedRoute = PredefinedChannelRoute(targetNodeId, Seq(scid_ag_private, scid_gh, invoiceRoutingHint.shortChannelId)) val amount = RoutingHeuristics.CAPACITY_CHANNEL_LOW * 2 // the amount affects the way we estimate the channel capacity of the hinted channel @@ -584,7 +584,7 @@ class RouterSpec extends BaseRouterSpec { test("ask for channels that we marked as stale for which we receive a new update") { fixture => import fixture._ val blockHeight = BlockHeight(400000) - 2020 - val channelId = ShortChannelId(blockHeight, 5, 0) + val channelId = RealShortChannelId(blockHeight, 5, 0) val announcement = channelAnnouncement(channelId, priv_a, priv_c, priv_funding_a, priv_funding_c) val oldTimestamp = TimestampSecond.now() - 14.days - 1.day val staleUpdate = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, CltvExpiryDelta(7), 0 msat, 766000 msat, 10, 5 msat, timestamp = oldTimestamp) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index 1e33b4ee0d..987c5f22d2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -22,7 +22,7 @@ import akka.testkit.{TestFSMRef, TestProbe} import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, Satoshi, Script, Transaction, TxIn, TxOut} import fr.acinq.eclair.TestConstants.{Alice, Bob} -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{UtxoStatus, ValidateRequest, ValidateResult} import fr.acinq.eclair.crypto.TransportHandler @@ -50,7 +50,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle .shortChannelIds .take(60) .foldLeft(TreeMap.empty[RealShortChannelId, (PublicChannel, NodeAnnouncement, NodeAnnouncement)]) { - case (m, shortChannelId) => m + (shortChannelId.toReal -> makeFakeRoutingInfo(pub2priv)(shortChannelId)) + case (m, shortChannelId) => m + (shortChannelId -> makeFakeRoutingInfo(pub2priv)(shortChannelId)) } class YesWatcher extends Actor { @@ -311,7 +311,7 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle test("sync progress") { - def req = QueryShortChannelIds(Block.RegtestGenesisBlock.hash, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(42))), TlvStream.empty) + def req = QueryShortChannelIds(Block.RegtestGenesisBlock.hash, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(42))), TlvStream.empty) val nodeIdA = randomKey().publicKey val nodeIdB = randomKey().publicKey @@ -332,15 +332,15 @@ class RoutingSyncSpec extends TestKitBaseClass with AnyFunSuiteLike with Paralle object RoutingSyncSpec { - lazy val shortChannelIds: SortedSet[ShortChannelId] = (for { + lazy val shortChannelIds: SortedSet[RealShortChannelId] = (for { block <- 400000 to 420000 txindex <- 0 to 5 outputIndex <- 0 to 1 - } yield ShortChannelId(BlockHeight(block), txindex, outputIndex)).foldLeft(SortedSet.empty[ShortChannelId])(_ + _) + } yield RealShortChannelId(BlockHeight(block), txindex, outputIndex)).foldLeft(SortedSet.empty[RealShortChannelId])(_ + _) val unused: PrivateKey = randomKey() - def makeFakeRoutingInfo(pub2priv: mutable.Map[PublicKey, PrivateKey])(shortChannelId: ShortChannelId): (PublicChannel, NodeAnnouncement, NodeAnnouncement) = { + def makeFakeRoutingInfo(pub2priv: mutable.Map[PublicKey, PrivateKey])(shortChannelId: RealShortChannelId): (PublicChannel, NodeAnnouncement, NodeAnnouncement) = { val timestamp = TimestampSecond.now() val (priv1, priv2) = { val (priv_a, priv_b) = (randomKey(), randomKey()) @@ -350,7 +350,7 @@ object RoutingSyncSpec { val priv_funding2 = unused pub2priv += (priv1.publicKey -> priv1) pub2priv += (priv2.publicKey -> priv2) - val channelAnn_12 = channelAnnouncement(shortChannelId.toReal, priv1, priv2, priv_funding1, priv_funding2) + val channelAnn_12 = channelAnnouncement(shortChannelId, priv1, priv2, priv_funding1, priv_funding2) val channelUpdate_12 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv1, priv2.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) val channelUpdate_21 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv2, priv1.publicKey, shortChannelId, cltvExpiryDelta = CltvExpiryDelta(7), 0 msat, feeBaseMsat = 766000 msat, feeProportionalMillionths = 10, 500000000L msat, timestamp = timestamp) val nodeAnnouncement_1 = makeNodeAnnouncement(priv1, "a", Color(0, 0, 0), List(), TestConstants.Bob.nodeParams.features.nodeAnnouncementFeatures()) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index e68eee3486..0d82508f50 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.internal.channel import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, Crypto, DeterministicWallet, Satoshi, SatoshiLong, Transaction, TxIn} -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.Helpers.Funding @@ -136,11 +136,11 @@ class ChannelCodecsSpec extends AnyFunSuite { // this test makes sure that we actually produce the same objects than previous versions of eclair val refs = Map( hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B134000456E4167E3C0EB8C856C79CA31C97C0AA0000000000000222000000012A05F2000000000000028F5C000000000000000102D0001E000BD48A2402E80B723C42EE3E42938866EC6686ABB7ABF64380000000C501A7F2974C5074E9E10DBB3F0D9B8C40932EC63ABC610FAD7EB6B21C6D081A459B000000000000011E80000001EEFFFE5C00000000000147AE00000000000001F403F000F18146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB20131AD64F76FAF90CD7DE26892F1BDAB82FB9E02EF6538D82FF4204B5348F02AE081A5388E9474769D69C4F60A763AE0CCDB5228A06281DE64408871A927297FDFD8818B6383985ABD4F0AC22E73791CF3A4D63C592FA2648242D34B8334B1539E823381BB1F1404C37D9C2318F5FC6B1BF7ECF5E6835B779E3BE09BADCF6DF1F51DCFBC80000000C0808000000000000EFD80000000007F00000000061A0A4880000001EDE5F3C3801203B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E808000000015FFFFFF800000000011001029DFB814F6502A68D6F83B6049E3D2948A2080084083750626532FDB437169C20023A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A95700AD0100000000008083B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E80800000001961B4C001618F8180000000001100102E648BA30998A28C02C2DFD9DDCD0E0BA064DA199C55186485AFAB296B94E704426FFE00000000000B000A67D9B9FAADB91650E0146B1F742E5C16006708890200239822011026A6925C659D006FEB42D639F1E42DD13224EE49AA34E71B612CF96DB66A8CD4011032C22F653C54CC5E41098227427650644266D80DED45B7387AE0FFC10E529C4680A418228110807CB47D9C1A14CB832FB361C398EA672C9542F34A90BAD4288FA6AC5FC9E9845C01101CF71CAE9252D389135D8C606225DCF1E0333CCDF1FAE84B74FC5D3D440C25F880A3A9108146779F781067ED04B4957E14F1C5623AB653039B2B1D49910240848E4E682DB21081B30694071254D8B3B9537320C014B8CB1052E5514F5EFC19CF2EB806308D5CF1A9573D7C531000000000000000000F3180000000007F00000001EDE5F3C380000000061A0A48D64CA627B243AD5915A2E5D0BAD026762028DDF3304992B83A26D6C11735FC5F01ED56D769BDE7F6A068AF1A4BCFDF950321F3A4744B01B1DDC7498677F112AE1A80000000000000000000000000000000000000658000000000000819800040D37301C10C9419287E9A3B704EB6D7F45CC145DD77DCE8A63B0A47C8AB67467D800901DCE3C8B05A891E56F2BAF1B82405ABD8640B759AEEBD939B976D42C311758F40400000000AFFFFFFC00000000008800814EFDC0A7B2815346B7C1DB024F1E94A451040042041BA83132997EDA1B8B4E10011D48840A33BCFBC0833F6825A4ABF0A78E2B11D5B2981CD958EA4C881204247273416D90840D9834A03892A6C59DCA9B990600A5C65882972A8A7AF7E0CE7975C031846AE78D4AB8002000EC0003FFFFFFFF86801076D98A575A4CDFD0E3F44D1BB3CD3BBAF3BD04C38FED439ED90D88DF932A9296801A80007FFFFFFFF4008136A9D5896669E8724C5120FB6B36C241EF3CEF68AE0316161F04A9EE3EAFF36000FC0003FFFFFFFF86780106E4B5CC4155733A2427082907338051A5DA1E7CA6432840A5528ECAFFA3FB628801B80007FFFFFFFF10020CA4E125E9126107745D4354D4187ABCDE323117857A1DCEB7CCF60B2AAFA80C6003A0000FFFFFFFFE1C0080981575FD981A73A848CC0243CB467BF451F6811DAF4D71CAD8CE8B1E96DB190C01000003FFFFFFFF867400814C747E0FD8290BE8A3B8B3F73015A261479A71780CD3A0A9270234E4B394409C00D80003FFFFFFFF90020E1B9C9B10A97F15F5E1BB27FC8AC670DF8DADEAE4EDFAFB23BDD0AC705FDF51600340000FFFFFFFFF0020AD2581F3494A17B0BE3F63516D53F028A204FD3156D8B21AA4E57A8738D2062080007FFFFFFFF0CE83B9C79160B5123CADE575E370480B57B0C816EB35DD7B27372EDA858622EB1E0B8C1E00000B8000FA46CC2C7E9AB4A37C64216CD65C944E6D73998419D1A1AD2827AB6BC85B32280230764E374064EC82A3751E789607E23BEAE93FB0EDDD5E7FA803767079662E80EAEF384E2AFCB68049D9DC246119E77BD2ED4112330760CAB6CD3671CFCE006C584B9C95E0B554261E00154D40806EA694F44751B328A9291BAD124EFD5664280936EC92D27B242737E7E3E83B4704BA367B7DA5108F2F6EDFB1C38EE721A369E77EED71B12090BAEAAAC322C1457E31AB0C4DE5D9351943F10FD747742616A1AABD09F680B37D4105A8872695EE9B97FAB8985FAA9D747D45046229BF265CEEB300A40FE23040C5F335E0515496C58EE47418B72331FCC6F47A31A9B33B8E000008692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002069FCA5D3141D3A78436ECFC366E31024CBB18EAF1843EB5FADAC871B42069166C0726710955E3AD621072FCBDFCB90D79E5B1951A5EE01DB533B72429F84E2562680519DE7DE0419FB412D255F853C71588EAD94C0E6CAC7526440902123939A0B6C806CC1A501C495362CEE54DCC830052E32C414B95453D7BF0673CBAE018C23573C69C694A8F88483050257A7366B838489731E5776B6FA0F02573401176D3E7FAEEF11E95A671420586631255F51A0EC2CF4D4D9F69D587712070FE1FB9316B71868692FFAFF04D2AE211E9461FB39D875D74F32E4109D21D5A03D46612000000002E307800002E0002BA11BBBA0202012000000000000007D0000007D0000000C800000007CFFFF83000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1513532x23x1"},"localAlias":"1513532x23x1"},"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"07738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63c","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[1457788542,1007597768,1455922339,479707306]},"dustLimit":546,"maxHtlcValueInFlightMsat":5000000000,"requestedChannelReserve_opt":167772,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":false,"defaultFinalScriptPubKey":"a9144805d016e47885dc7c852710cdd8cd0d576f57ec87","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","dustLimit":573,"maxHtlcValueInFlightMsat":16609443000,"requestedChannelReserve_opt":167772,"htlcMinimum":1000,"toSelfDelay":2016,"maxAcceptedHtlcs":483,"fundingPubKey":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","revocationBasepoint":"02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1","paymentBasepoint":"034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1","delayedPaymentBasepoint":"0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467","htlcBasepoint":"03763e280986fb384631ebf8d637efd9ebcd06b6ef3c77c1375b9edbe3ea3b9f79","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":7675,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":204739729,"toRemote":16572475271},"commitTxAndRemoteSig":{"commitTx":{"txid":"e25a866b79212015e01e155e530fb547abc8276869f8740a9948e52ca231f1e4","tx":"020000000107738f22c16a24795bcaebc6e09016af61902dd66bbaf64e6e5db50b0c45d63d010000000032c3698002c31f0300000000002200205cc91746133145180585bfb3bb9a1c1740c9b43338aa30c90b5f5652d729ce0884dffc0000000000160014cfb373f55b722ca1c028d63ee85cb82c00ce11127af8a620"},"remoteSig":"4d4d24b8cb3a00dfd685ac73e3c85ba26449dc935469ce36c259f2db6cd519a865845eca78a998bc8213044e84eca0c884cdb01bda8b6e70f5c1ff821ca5388d"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":7779,"spec":{"htlcs":[],"commitTxFeerate":254,"toLocal":16572475271,"toRemote":204739729},"txid":"ac994c4f64875ab22b45cba175a04cec4051bbe660932570744dad822e6bf8be","remotePerCommitmentPoint":"03daadaed37bcfed40d15e34979fbf2a0643e748e8960363bb8e930cefe2255c35"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":203,"remoteNextHtlcId":4147,"originChannels":{},"remoteNextCommitInfo":"034dcc0704325064a1fa68edc13adb5fd173051775df73a298ec291f22ad9d19f6","commitInput":{"outPoint":"3dd6450c0bb55d6e4ef6ba6bd62d9061af1690e0c6ebca5b79246ac1228f7307:1","amountSatoshis":16777215},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1513532x23x1"},"localAlias":"0x17183c0000170001"},"channelAnnouncement":{"nodeSignature1":"d2366163f4d5a51be3210b66b2e4a2736b9ccc20ce8d0d69413d5b5e42d991401183b271ba032764151ba8f3c4b03f11df5749fd876eeaf3fd401bb383cb3174","nodeSignature2":"075779c27157e5b4024ecee12308cf3bde976a0891983b0655b669b38e7e700362c25ce4af05aaa130f000aa6a04037534a7a23a8d99454948dd689277eab321","bitcoinSignature1":"4049b7649693d92139bf3f1f41da3825d1b3dbed2884797b76fd8e1c77390d1b4f3bf76b8d890485d7555619160a2bf18d58626f2ec9a8ca1f887eba3ba130b5","bitcoinSignature2":"0d55e84fb4059bea082d443934af74dcbfd5c4c2fd54eba3ea2823114df932e7759805207f1182062f99af028aa4b62c7723a0c5b9198fe637a3d18d4d99dc70","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","nodeId1":"034fe52e98a0e9d3c21b767e1b371881265d8c7578c21f5afd6d6438da10348b36","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64","bitcoinKey2":"03660d280e24a9b16772a6e6418029719620a5caa29ebdf8339e5d700c611ab9e3","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"4e34a547c424182812bd39b35c1c244b98f2bbb5b7d07812b9a008bb69f3fd77788f4ad338a102c331892afa8d076167a6a6cfb4eac3b890387f0fdc98b5b8c3","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1513532x23x1","timestamp":{"iso":"2019-06-18T12:49:33Z","unix":1560862173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":16777215000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"00000303933884AAF1D6B108397E5EFE5C86BCF2D8CA8D2F700EDA99DB9214FC2712B1340004D443ECE9D9C43A11A19B554BAAA6AD150000000000000222000000003B9ACA0000000000000249F000000000000000010090001E800BD48A22F4C80A42CC8BB29A764DBAEFC95674931FBE9A4380000000C50134D4A745996002F219B5FDBA1E045374DF589ECA06ABE23CECAE47343E65EDCF800000000000011E80000001BA90824000000000000124F800000000000001F4038500F1810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E2266201E8BFEEEEED725775B8116F6F82CF8E87835A5B45B184E56F272AD70D6078118601E06212B8C8F2E25B73EE7974FDCDF007E389B437BBFE238CCC3F3BF7121B6C5E81AA8589D21E9584B24A11F3ABBA5DAD48D121DD63C57A69CD767119C05DA159CB81A649D8CC0E136EB8DFBD2268B69DCA86F8CE4A604235A03D9D37AE7B07FC563F80000000C080800000000000271C000000000177000000002808B14600000001970039BA00123767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB08800000000015E070F20000000000110010584241B5FB364208F6E64A80D1166DAD866186B10C015ED0283FF1C308C2105A0023A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA95700AD81000000000080B767F0F4F00D5E9FDF24177EF2872343D9F8FAEC65D3048BA575E70E00A0AB0880000000003E7AEDC0011ABE8A00000000001100101A9CE4B6AEF469590BC7BCC51DCEEAE9C86084055A63CC01E443C733FBE400B9B5B16800000000000B000A5E5700106D1A7097E4DE87EBAF1F8F2773842FA482002418228110805E84989A81F51ABD9D11889AE43E68FAD93659DEC019F1B8C0ADBF15A57B118B81101DCC1256F9306439AD3962C043FC47A5179CAAA001CCB23342BE0E8D92E4022780A4182281108074F306DA3751B84EC5FFB155BDCA7B8E02208BBDBC8D4F3327ABA557BF27CD1701102EF4AC8CC92F469DA9642D4D4162BC545F8B34ADE15B7D6F99808AA22B086B0180A3A910810AE1AF8A1D6F56F80855E26705F191BB07CD4E2434BC5BB1698E7E5880E226621081DE8ADFA110DC8A94D8B9E9EF616BAE8598287C8F82AFDF0FC068697D570266FDA9576F8099900000000000000000271C00000000017700000001970039BA000000002808B14648CE00AE97051EE10A3C361263F81A98165CE4AA7BA076933D4266E533585F24815C15DEACF0691332B38ECF23EC39982C5C978C748374A01BA9B30D501EE4F26E8000000000000000000000000000000000001224000000000000004B800040A911C460F1467952E3B99BED072F81BFB4454FF389636DCB399FE6A78113C28580091BB3F87A7806AF4FEF920BBF794391A1ECFC7D7632E98245D2BAF3870050558440000000000AF0387900000000000880082C2120DAFD9B21047B732540688B36D6C330C3588600AF68141FF8E18461082D0011D488408570D7C50EB7AB7C042AF13382F8C8DD83E6A7121A5E2DD8B4C73F2C407113310840EF456FD0886E454A6C5CF4F7B0B5D742CC143E47C157EF87E03434BEAB81337ED4AB8001C00F40003FFFFFFFEC7200403248A1D44DFA3AC9EC237D452C936400CAA86E9517CCCF2A8F77B7493CD70B6A00780001FFFFFFFF63A0041826829646B907A97FBD1455EA8673A12B8E7AA6EA790F7802E955CE3B69DE57E006E0001FFFFFFFF640081E51EB1F91218821E680B50E4B22DF8B094385BD33ACAE36BFC9E8C2F5AD2DA5400EC0003FFFFFFFEC7801047C26AD5435658D063EBCF73A5D0EEFE73ED6B73426246E8DFB3A21D1C4C7465001900007FFFFFFFE0040B115AC58BAAA900195893EA3B2AB408D2AD348AD047E3B6CB15E599625E38608006A0001FFFFFFFF7002033C39A21A38BB61F6FB33623771A9356D8885B7C12C939C770C939EF826286C200360000FFFFFFFFB4008104EF4271064A0973B053727C3E67352D00E25CAEED944F50782449CEAE8F50960001FFFFFFFF6390DD9FC3D3C0357A7F7C905DFBCA1C8D0F67E3EBB1974C122E95D79C380282AC222B21FA0007920001295AA1FB77029F7620A90EF7AE6A6CD31E4588B93264A7ADB76152D535C52E90B9E1B7C2376DABA316A6290F1A9730D4E5E44D0B1CB0EE6A795702E6A6BCDFCDA1A4BFEBFC134AB8847A5187ECE761D75D3CCB904274875680F51984800000000AC87E8001E480002E884D2A8080804800000000000001F4000001F40000003200000001BF08EB000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1413373x969x0"},"localAlias":"1413373x969x0"},"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"6ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c01415611","channelConfig":[],"channelFeatures":[],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[3561221353,3653515793,2711311691,2863050005]},"dustLimit":546,"maxHtlcValueInFlightMsat":1000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":144,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"a91445e990148599176534ec9b75df92ace9263f7d3487","initFeatures":{"activated":{"option_data_loss_protect":"optional","initial_routing_sync":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"0269a94e8b32c005e4336bfb743c08a6e9beb13d940d57c479d95c8e687ccbdb9f","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1000,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4","revocationBasepoint":"03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c","paymentBasepoint":"03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd","delayedPaymentBasepoint":"03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397","htlcBasepoint":"034c93b1981c26dd71bf7a44d16d3b950df19c94c0846b407b3a6f5cf60ff8ac7f","initFeatures":{"activated":{"option_data_loss_protect":"mandatory","gossip_queries":"optional"},"unknown":[]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":1343316620,"toRemote":13656683380},"commitTxAndRemoteSig":{"commitTx":{"txid":"65fe0b1f079fa763448df3ab8d94b1ad7d377c061121376be90b9c0c1bb0cd43","tx":"02000000016ecfe1e9e01abd3fbe482efde50e4687b3f1f5d8cba609174aebce1c0141561100000000007cf5db8002357d1400000000002200203539c96d5de8d2b2178f798a3b9dd5d390c1080ab4c79803c8878e67f7c801736b62d00000000000160014bcae0020da34e12fc9bd0fd75e3f1e4ee7085f49df013320"},"remoteSig":"bd09313503ea357b3a231135c87cd1f5b26cb3bd8033e371815b7e2b4af623173b9824adf260c8735a72c58087f88f4a2f39554003996466857c1d1b25c8044f"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":20024,"spec":{"htlcs":[],"commitTxFeerate":750,"toLocal":13656683380,"toRemote":1343316620},"txid":"919c015d2e0a3dc214786c24c7f035302cb9c954f740ed267a84cdca66b0be49","remotePerCommitmentPoint":"02b82bbd59e0d22665671d9e47d8733058b92f18e906e9403753661aa03dc9e4dd"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":9288,"remoteNextHtlcId":151,"originChannels":{},"remoteNextCommitInfo":"02a4471183c519e54b8ee66fb41cbe06fed1153fce258db72ce67f9a9e044f0a16","commitInput":{"outPoint":"115641011cceeb4a1709a6cbd8f5f1b387460ee5fd2e48be3fbd1ae0e9e1cf6e:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"1413373x969x0"},"localAlias":"0x1590fd0003c90000"},"channelUpdate":{"signature":"52b543f6ee053eec41521def5cd4d9a63c8b117264c94f5b6ec2a5aa6b8a5d2173c36f846edb57462d4c521e352e61a9cbc89a163961dcd4f2ae05cd4d79bf9b","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"1413373x969x0","timestamp":{"iso":"2019-06-24T09:39:33Z","unix":1561369173},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1000,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""", hex"0200020000000303933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13400098c4b989bbdced820a77a7186c2320e7d176a5c8b5c16d6ac2af3889d6bc8bf8080000001000000000000022200000004a817c80000000000000249f0000000000000000102d0001eff1600148061b7fbd2d84ed1884177ea785faecb2080b10302e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b300000004080aa982027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8000000000000023d000000037521048000000000000249f00000000000000001070a01e302eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b7503c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a5700000004808a52a1010000000000000004000000001046000000037e11d6000000000000000000245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aefd013b020000000001015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61040047304402207f8c1936d0a50671c993890f887c78c6019abc2a2e8018899dcdc0e891fd2b090220046b56afa2cb7e9470073c238654ecf584bcf5c00b96b91e38335a70e2739ec901483045022100871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c0220119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b01475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52aed7782c20000000000000000000040000000010460000000000000000000000037e11d600b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d802e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a000000000000000000000000000000000000000000000000000000000000ff03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d245986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b000000002bc0e1e40000000000220020690fb50de412adf9b20a7fc6c8fb86f1bfd4ebc1ef8e2d96a5a196560798d944475221023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d2102eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b52ae0001003e0000fffffffffffc0080474b8cf7bb98217dd8dc475cb7c057a3465d466728978bbb909d0a05d4ae7bbe0001fffffffffff85986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b1eedce0000010000fffffd01ae98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be54920134196992f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef09bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce0000010000027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b803933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b13402eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d88710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea3309000000001eedce000001000060e6eb14010100900000000000000001000003e800000064000000037e11d6000000" - -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"2026958x1x0"},"localAlias":"2026958x1x0"},"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" + -> """{"type":"DATA_NORMAL","commitments":{"channelId":"5986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b","channelConfig":["funding_pubkey_based_channel_keypath"],"channelFeatures":["option_static_remotekey"],"localParams":{"nodeId":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","fundingKeyPath":{"path":[2353764507,3184449568,2809819526,3258060413,392846475,1545000620,720603293,1808318336,2147483649]},"dustLimit":546,"maxHtlcValueInFlightMsat":20000000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":720,"maxAcceptedHtlcs":30,"isInitiator":true,"defaultFinalScriptPubKey":"00148061b7fbd2d84ed1884177ea785faecb2080b103","walletStaticPaymentBasepoint":"02e56c8eca8d4f00df84ac34c23f49c006d57d316b7ada5c346e9d4211e11604b3","initFeatures":{"activated":{"option_support_large_channel":"optional","gossip_queries_ex":"optional","option_data_loss_protect":"optional","var_onion_optin":"mandatory","option_static_remotekey":"optional","payment_secret":"optional","option_shutdown_anysegwit":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[]}},"remoteParams":{"nodeId":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","dustLimit":573,"maxHtlcValueInFlightMsat":14850000000,"requestedChannelReserve_opt":150000,"htlcMinimum":1,"toSelfDelay":1802,"maxAcceptedHtlcs":483,"fundingPubKey":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","revocationBasepoint":"0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75","paymentBasepoint":"03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd","delayedPaymentBasepoint":"03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8","htlcBasepoint":"022f3b055b0d35cde31dec5263a8ed638433e3424a4e197c06d94053985a364a57","initFeatures":{"activated":{"option_upfront_shutdown_script":"optional","payment_secret":"mandatory","option_data_loss_protect":"mandatory","var_onion_optin":"optional","option_static_remotekey":"mandatory","option_support_large_channel":"optional","option_anchors_zero_fee_htlc_tx":"optional","basic_mpp":"optional","gossip_queries":"optional"},"unknown":[31]}},"channelFlags":{"announceChannel":true},"localCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":15000000000,"toRemote":0},"commitTxAndRemoteSig":{"commitTx":{"txid":"fa747ecb6f718c6831cc7148cf8d65c3468d2bb6c202605e2b82d2277491222f","tx":"02000000015986f644357956c1674f385e0878fb7bb44ab3d57ea9911fab98af8a71e1ad1b0000000000c2d6178001f8d5e4000000000022002080f1dfe71a865b605593e169677c952aaa1196fc2f541ef7d21c3b1006527b61d7782c20"},"remoteSig":"871afd240e20a171b9cba46f20555f848c5850f94ec7da7b33b9eeaf6af6653c119cda8cbf5f80986d6a4f0db2590c734d1de399a7060a477b5d94df0183625b"},"htlcTxsAndRemoteSigs":[]},"remoteCommit":{"index":4,"spec":{"htlcs":[],"commitTxFeerate":4166,"toLocal":0,"toRemote":15000000000},"txid":"b5f2287b2d5edf4df5602a3c287db3b938c3f1a943e40715886db5bd400f95d8","remotePerCommitmentPoint":"02e7e1abac1feb54ee3ac2172c9e2231f77765df57664fb44a6dc2e4aa9e6a9a6a"},"localChanges":{"proposed":[],"signed":[],"acked":[]},"remoteChanges":{"proposed":[],"acked":[],"signed":[]},"localNextHtlcId":0,"remoteNextHtlcId":0,"originChannels":{},"remoteNextCommitInfo":"03fd10fe44564e2d7e1550099785c2c1bad32a5ae0feeef6e27f0c108d18b4931d","commitInput":{"outPoint":"1bade1718aaf98ab1f91a97ed5b34ab47bfb78085e384f67c156793544f68659:0","amountSatoshis":15000000},"remotePerCommitmentSecrets":null},"shortIds":{"real":{"status":"final","realScid":"2026958x1x0"},"localAlias":"0x1eedce0000010000"},"channelAnnouncement":{"nodeSignature1":"98d7a81bc1aa92fcfb74ced2213e85e0d92ae8ac622bf294b3551c7c27f6f84f782f3b318e4d0eb2c67ac719a7c65afcf85bf159f6ceea9427be549201341969","nodeSignature2":"92f6ed0e059db72105a13ec0e799bb08896cad8b4feb7e9ec7283c309b5f43123af1bd9e913fc2db018edadde8932d6992408f10c1ad020504361972dfa7fef0","bitcoinSignature1":"9bbc2b568cef3c8c006f7860106fd5984bcc271ff06c4829db2a665e59b7c0b22c311a340ff2ab9bcb74a50db10ed85503ad2d248d95af8151aca8ef96248e8f","bitcoinSignature2":"84b3075922385fbaf012f057e7ee84ecbc14c84880520b26d6fd22ab5f107db606a906efdcf0f88ffbe32dc6ecc10131e1ff0dc8d68dad89c98562557f00448b","features":{"activated":{},"unknown":[]},"chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","nodeId1":"027455aef8453d92f4706b560b61527cc217ddf14da41770e8ed6607190a1851b8","nodeId2":"03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134","bitcoinKey1":"02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b","bitcoinKey2":"023ced05ed1ab328b67477376d68a69ecd0f371a9d5843c6c3be4d31498d516d8d","tlvStream":{"records":[],"unknown":[]}},"channelUpdate":{"signature":"710d73875607575f3d84bb507dd87cca5b85f0cdac84f4ccecce7af3a55897525a45070fe26c0ea43e9580d4ea4cfa62ee3273e5546911145cba6bbf56e59d8e","chainHash":"43497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000","shortChannelId":"2026958x1x0","timestamp":{"iso":"2021-07-08T12:09:56Z","unix":1625746196},"channelFlags":{"isEnabled":true,"isNode1":false},"cltvExpiryDelta":144,"htlcMinimumMsat":1,"feeBaseMsat":1000,"feeProportionalMillionths":100,"htlcMaximumMsat":15000000000,"tlvStream":{"records":[],"unknown":[]}}}""" ) refs.foreach { case (oldbin, refjson) => @@ -329,7 +329,7 @@ object ChannelCodecsSpec { commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init) - DATA_NORMAL(commitments, ShortIds(RealScidStatus.Final(realScid(42)), ShortChannelId.generateLocalAlias(), None), None, channelUpdate, None, None, None) + DATA_NORMAL(commitments, ShortIds(RealScidStatus.Final(RealShortChannelId(42)), ShortChannelId.generateLocalAlias(), None), None, channelUpdate, None, None, None) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index 349f199b5e..8c93c02966 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -23,7 +23,7 @@ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.Codecs._ import fr.acinq.eclair.wire.internal.channel.version3.ChannelCodecs3.channelDataCodec -import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, ShortChannelId, UInt64, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, MilliSatoshi, RealShortChannelId, ShortChannelId, UInt64, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits.{ByteVector, HexStringSyntax} @@ -255,7 +255,7 @@ class ChannelCodecs3Spec extends AnyFunSuite { val bin = hex"0001000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae00000000000000075bcd1541dd93fe76288e183498a7d57065cf472a4413c643730f9c117ca806be6b57f75303a153d48f51439f168ca85ef2fd6f3e0642fc7132f7a530cd50d880ec05cb4c17" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY] assert(data.shortIds.localAlias == ShortChannelId(123456789L)) - assert(data.shortIds.real == RealScidStatus.Temporary(ShortChannelId(123456789L).toReal)) + assert(data.shortIds.real == RealScidStatus.Temporary(RealShortChannelId(123456789L))) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0010")) // NB: 01 -> 10 } @@ -264,7 +264,7 @@ class ChannelCodecs3Spec extends AnyFunSuite { val bin = hex"0007000000000000000000000000000000000000000000000000000000000000000001010003af0ed6052cf28d670665549bc86f4b721c9fdb309d40c58f5811f63966e005d000010000002a00000000000002220000000002faf0800000000000002710000000000000271000900032ff0000000000022cd2a00cddf20323d320bd14ce0e59b00d62def4d853b88e8bf7dc44c556fc07000000000000022200000000004c4b400000000000002710000000000000138800900032031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b03f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00000000000100000000000000000005fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc0000000002faf08000000000042c1d8024bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752aefd010f02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a48848900000000000000000000040047304402202148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab71702202bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d0147304402206cb12624b253adeb0a41210d63ac6280154923c502202ea16a581bc1839e1e610220178e31542e4a7735d9e243927a5aac00bae1b2889cb9eb785c4d182f3b22a87d01475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000002148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d00000000000000000000000500fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f424066687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925000001f400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001f00000000002dc6c0648aa5c579fb30f38af744d97d6ec840c7a91277a499a0d780f3e7314eca090b000001f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fffd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000001e00000000001e848075877bb41d393b5fb8455ce60ecd8dda001d06316496b14dfa7f895656eeca4a000001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000200000000003d09009f4fb68f3e1dac82202f9aa581ce0bbf1f765df0e9ac3c8c57e20f685abab8ed000001f80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd05aa0000000000000000000000000000000000000000000000000000000000000000000000000000000100000000001e848072cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793000001f500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dc000000000000c35000000000000aae60030303030303030303030303030303030303030303030303030303030303030303462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b000000000000000000000000000000000000002000000000000000040002000000000000002a00036e9078b299874e92af44d59fcbc9d2190000000000003a9800022a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000000000002b0000000000a7d8c00000000000989680ff023730c8e0fc52aff6ba2f618f29e5ebd551c0129e13ce20312df76e4403c5abbc24bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000002b80969800000000002200205e9ed9d4087f82a14496be26b842e968f9ae2e65e331fd93fb97e1f5c6577934475221031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f2103afd229e5da2cc156d1fb929c22bf6878791adad2574614e1c1e5decd65a71a3752ae000000000000000000002aff008821c93fbf280b2391826bc70ae858cf815d4afc1816f85445364f188e635d4ae64dfe1c58bedb017dd6f267452444d991b66fcfc638396f72fa6926f69d6125ff01010101010101010101010101010101010101010101010101010101010101010000000000022cd962975eec0101002a000000000000000f0000023f0000003500000003e8000000000000" val data = channelDataCodec.decode(bin.bits).require.value.asInstanceOf[DATA_NORMAL] assert(data.shortIds.localAlias == ShortChannelId(42)) - assert(data.shortIds.real == RealScidStatus.Final(ShortChannelId(42).toReal)) + assert(data.shortIds.real == RealScidStatus.Final(RealShortChannelId(42))) val binMigrated = channelDataCodec.encode(data).require.toHex assert(binMigrated.startsWith("0009")) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala index 94d8db2b21..9d8e643d62 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/ExtendedQueriesCodecsSpec.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64} import fr.acinq.eclair.RealShortChannelId -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.router.Sync import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.ReplyChannelRangeTlv._ @@ -31,7 +31,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode a list of short channel ids") { { // encode/decode with encoding 'uncompressed' - val ids = EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))) + val ids = EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))) val encoded = encodedShortChannelIdsCodec.encode(ids).require val decoded = encodedShortChannelIdsCodec.decode(encoded).require.value assert(decoded == ids) @@ -39,7 +39,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { { // encode/decode with encoding 'zlib' - val ids = EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))) + val ids = EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))) val encoded = encodedShortChannelIdsCodec.encode(ids).require val decoded = encodedShortChannelIdsCodec.decode(encoded).require.value assert(decoded == ids) @@ -67,7 +67,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (no optional data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream.empty) val encoded = queryShortChannelIdsCodec.encode(query_short_channel_id).require @@ -78,7 +78,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (with optional data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte)))) val encoded = queryShortChannelIdsCodec.encode(query_short_channel_id).require @@ -89,7 +89,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { test("encode query_short_channel_ids (with optional data including unknown data)") { val query_short_channel_id = QueryShortChannelIds( Block.RegtestGenesisBlock.blockId, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream( QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte)) :: Nil, GenericTlv(UInt64(43), ByteVector.fromValidHex("deadbeef")) :: Nil @@ -106,7 +106,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), None, None) val encoded = replyChannelRangeCodec.encode(replyChannelRange).require @@ -119,7 +119,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec)))), None) @@ -133,7 +133,7 @@ class ExtendedQueriesCodecsSpec extends AnyFunSuite { Block.RegtestGenesisBlock.blockId, BlockHeight(1), 100, 1.toByte, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream( List( EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index a7ec2e3118..44d3e5308a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -22,7 +22,7 @@ import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, SatoshiL import fr.acinq.eclair.FeatureSupport.Optional import fr.acinq.eclair.Features.DataLossProtect import fr.acinq.eclair.RealShortChannelId -import fr.acinq.eclair.TestUtils.realScid +import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes} @@ -356,7 +356,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { FundingCreated(randomBytes32(), bin32(0), 3, randomBytes64()), FundingSigned(randomBytes32(), randomBytes64()), ChannelReady(randomBytes32(), point(2)), - ChannelReady(randomBytes32(), point(2), TlvStream(ChannelReadyTlv.ShortChannelIdTlv(ShortChannelId(123456)))), + ChannelReady(randomBytes32(), point(2), TlvStream(ChannelReadyTlv.ShortChannelIdTlv(Alias(123456)))), UpdateFee(randomBytes32(), FeeratePerKw(2 sat)), Shutdown(randomBytes32(), bin(47, 0)), ClosingSigned(randomBytes32(), 2 sat, randomBytes64()), @@ -366,19 +366,19 @@ class LightningMessageCodecsSpec extends AnyFunSuite { UpdateFailMalformedHtlc(randomBytes32(), 2, randomBytes32(), 1111), CommitSig(randomBytes32(), randomBytes64(), randomBytes64() :: randomBytes64() :: randomBytes64() :: Nil), RevokeAndAck(randomBytes32(), scalar(0), point(1)), - ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, realScid(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey), + ChannelAnnouncement(randomBytes64(), randomBytes64(), randomBytes64(), randomBytes64(), Features(bin(7, 9)), Block.RegtestGenesisBlock.hash, RealShortChannelId(1), randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey), NodeAnnouncement(randomBytes64(), Features(DataLossProtect -> Optional), 1 unixsec, randomKey().publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil), ChannelUpdate(randomBytes64(), Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2 unixsec, ChannelUpdate.ChannelFlags.DUMMY, CltvExpiryDelta(3), 4 msat, 5 msat, 6, None), - AnnouncementSignatures(randomBytes32(), realScid(42), randomBytes64(), randomBytes64()), + AnnouncementSignatures(randomBytes32(), RealShortChannelId(42), randomBytes64(), randomBytes64()), GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000 unixsec, 1500), - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream.empty), QueryChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(100000), 1500, TlvStream(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL) :: Nil, unknownTlv :: Nil)), ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(100000), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream( EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))) :: EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) :: Nil, unknownTlv :: Nil) @@ -411,13 +411,13 @@ class LightningMessageCodecsSpec extends AnyFunSuite { test("non-reg encoding type") { val refs = Map( hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream.empty), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001601789c636000833e08659309a65c971d0100126e02e3" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream.empty), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4010400010204" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1, 2, 4)))), + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.UNCOMPRESSED, List(1, 2, 4)))), hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001601789c636000833e08659309a65c971d0100126e02e3010c01789c6364620100000e0008" - -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) + -> QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) ) refs.forall { @@ -439,28 +439,28 @@ class LightningMessageCodecsSpec extends AnyFunSuite { TlvStream(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL))) -> hex"01070f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000088b800000064010103", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(756230), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), None, None) -> + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), None, None) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000b8a06000005dc01001900000000000000008e0000000000003c69000000000045a6c4", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(1600), 110, 1, - EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(142), realScid(15465), realScid(265462))), None, None) -> + EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(265462))), None, None) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206000006400000006e01001601789c636000833e08659309a65878be010010a9023a", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(122334), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(12355), realScid(489686), realScid(4645313))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(12355), RealShortChannelId(489686), RealShortChannelId(4645313))), Some(EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060001ddde000005dc01001900000000000000304300000000000778d6000000000046e1c1011900000282c1000e77c5000778ad00490ab00000b57800955bff031800000457000008ae00000d050000115c000015b300001a0a", ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(122334), 1500, 1, - EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(12355), realScid(489686), realScid(4645313))), + EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(12355), RealShortChannelId(489686), RealShortChannelId(4645313))), Some(EncodedTimestamps(EncodingType.COMPRESSED_ZLIB, List(Timestamps(164545 unixsec, 948165 unixsec), Timestamps(489645 unixsec, 4786864 unixsec), Timestamps(46456 unixsec, 9788415 unixsec)))), Some(EncodedChecksums(List(Checksums(1111, 2222), Checksums(3333, 4444), Checksums(5555, 6666))))) -> hex"01080f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060001ddde000005dc01001801789c63600001036730c55e710d4cbb3d3c080017c303b1012201789c63606a3ac8c0577e9481bd622d8327d7060686ad150c53a3ff0300554707db031800000457000008ae00000d050000115c000015b300001a0a", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(142), realScid(15465), realScid(4564676))), TlvStream.empty) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream.empty) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001900000000000000008e0000000000003c69000000000045a6c4", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(4564), realScid(178622), realScid(4564676))), TlvStream.empty) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(4564), RealShortChannelId(178622), RealShortChannelId(4564676))), TlvStream.empty) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001801789c63600001c12b608a69e73e30edbaec0800203b040e", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(realScid(12232), realScid(15556), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(12232), RealShortChannelId(15556), RealShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e22060019000000000000002fc80000000000003cc4000000000045a6c4010c01789c6364620100000e0008", - QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(realScid(14200), realScid(46645), realScid(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> + QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, EncodedShortChannelIds(EncodingType.COMPRESSED_ZLIB, List(RealShortChannelId(14200), RealShortChannelId(46645), RealShortChannelId(4564676))), TlvStream(QueryShortChannelIdsTlv.EncodedQueryFlags(EncodingType.COMPRESSED_ZLIB, List(1, 2, 4)))) -> hex"01050f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206001801789c63600001f30a30c5b0cd144cb92e3b020017c6034a010c01789c6364620100000e0008" ) diff --git a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala index 045603b6e7..ed1f868cb3 100644 --- a/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala +++ b/eclair-front/src/main/scala/fr/acinq/eclair/router/FrontRouter.scala @@ -111,7 +111,7 @@ class FrontRouter(routerConf: RouterConf, remoteRouter: ActorRef, initialized: O origin.peerConnection ! TransportHandler.ReadAck(ann) Metrics.gossipDropped(ann).increment() d - case u: ChannelUpdate if d.channels.get(u.shortChannelId.toReal).exists(_.getChannelUpdateSameSideAs(u).contains(u)) => + case u: ChannelUpdate if d.channels.get(RealShortChannelId(u.shortChannelId.toLong)).exists(_.getChannelUpdateSameSideAs(u).contains(u)) => origin.peerConnection ! TransportHandler.ReadAck(ann) Metrics.gossipDropped(ann).increment() d @@ -281,7 +281,7 @@ object FrontRouter { case ChannelUpdatesReceived(updates) => log.debug("adding/updating {} channel_updates", updates.size) val channels1 = updates.foldLeft(d.channels) { - case (channels, u) => channels.get(u.shortChannelId.toReal) match { + case (channels, u) => channels.get(RealShortChannelId(u.shortChannelId.toLong)) match { case Some(c) => channels + (c.ann.shortChannelId -> c.updateChannelUpdateSameSideAs(u)) case None => channels } @@ -310,7 +310,7 @@ object FrontRouter { case n: NodeAnnouncement => d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins)) case c: ChannelAnnouncement => d.rebroadcast.copy(channels = d.rebroadcast.channels + (c -> origins)) case u: ChannelUpdate => - if (d.channels.contains(u.shortChannelId.toReal)) { + if (d.channels.contains(RealShortChannelId(u.shortChannelId.toLong))) { d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins)) } else { d.rebroadcast // private channel, we don't rebroadcast the channel_update diff --git a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala index ebbc249ca8..0501dfcf67 100644 --- a/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala +++ b/eclair-front/src/test/scala/fr/acinq/eclair/router/FrontRouterSpec.scala @@ -340,10 +340,10 @@ object FrontRouterSpec { val ann_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil, Features.empty) val ann_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil, Features.empty) - val channelId_ab = ShortChannelId(BlockHeight(420000), 1, 0) - val channelId_bc = ShortChannelId(BlockHeight(420000), 2, 0) - val channelId_cd = ShortChannelId(BlockHeight(420000), 3, 0) - val channelId_ef = ShortChannelId(BlockHeight(420000), 4, 0) + val channelId_ab = RealShortChannelId(BlockHeight(420000), 1, 0) + val channelId_bc = RealShortChannelId(BlockHeight(420000), 2, 0) + val channelId_cd = RealShortChannelId(BlockHeight(420000), 3, 0) + val channelId_ef = RealShortChannelId(BlockHeight(420000), 4, 0) def channelAnnouncement(shortChannelId: RealShortChannelId, node1_priv: PrivateKey, node2_priv: PrivateKey, funding1_priv: PrivateKey, funding2_priv: PrivateKey) = { val witness = Announcements.generateChannelAnnouncementWitness(Block.RegtestGenesisBlock.hash, shortChannelId, node1_priv.publicKey, node2_priv.publicKey, funding1_priv.publicKey, funding2_priv.publicKey, Features.empty) diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala index 4843701bd6..5f1bc093d8 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/serde/FormParamExtractors.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.Bolt11Invoice import fr.acinq.eclair.wire.protocol.MessageOnionCodecs.blindedRouteCodec -import fr.acinq.eclair.{MilliSatoshi, ShortChannelId, TimestampSecond} +import fr.acinq.eclair.{UnspecifiedShortChannelId, MilliSatoshi, ShortChannelId, TimestampSecond} import scodec.bits.ByteVector import java.util.UUID diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 9d48e22f70..e134c67b7a 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -994,7 +994,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM val mockChannelUpdate1 = ChannelUpdate( signature = ByteVector64.fromValidHex("92cf3f12e161391986eb2cd7106ddab41a23c734f8f1ed120fb64f4b91f98f690ecf930388e62965f8aefbf1adafcd25a572669a125396dcfb83615208754679"), chainHash = ByteVector32.fromValidHex("024b7b3626554c44dcc2454ee3812458bfa68d9fced466edfab470844cb7ffe2"), - shortChannelId = ShortChannelId(BlockHeight(1), 2, 3), + shortChannelId = RealShortChannelId(BlockHeight(1), 2, 3), timestamp = 0 unixsec, channelFlags = ChannelUpdate.ChannelFlags.DUMMY, cltvExpiryDelta = CltvExpiryDelta(0), @@ -1003,8 +1003,8 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM feeProportionalMillionths = 1, htlcMaximumMsat = None ) - val mockChannelUpdate2 = mockChannelUpdate1.copy(shortChannelId = ShortChannelId(BlockHeight(1), 2, 4)) - val mockChannelUpdate3 = mockChannelUpdate1.copy(shortChannelId = ShortChannelId(BlockHeight(1), 2, 5)) + val mockChannelUpdate2 = mockChannelUpdate1.copy(shortChannelId = RealShortChannelId(BlockHeight(1), 2, 4)) + val mockChannelUpdate3 = mockChannelUpdate1.copy(shortChannelId = RealShortChannelId (BlockHeight(1), 2, 5)) val mockHop1 = Router.ChannelHop(mockChannelUpdate1.shortChannelId, PublicKey.fromBin(ByteVector.fromValidHex("03007e67dc5a8fd2b2ef21cb310ab6359ddb51f3f86a8b79b8b1e23bc3a6ea150a")), PublicKey.fromBin(ByteVector.fromValidHex("026105f6cb4862810be989385d16f04b0f748f6f2a14040338b1a534d45b4be1c1")), ChannelRelayParams.FromAnnouncement(mockChannelUpdate1)) val mockHop2 = Router.ChannelHop(mockChannelUpdate2.shortChannelId, mockHop1.nextNodeId, PublicKey.fromBin(ByteVector.fromValidHex("038cfa2b5857843ee90cff91b06f692c0d8fe201921ee6387aee901d64f43699f0")), ChannelRelayParams.FromAnnouncement(mockChannelUpdate2)) From c20f672be053214637dbaf4fa3cea259b17d87ce Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 10 Jun 2022 18:25:51 +0200 Subject: [PATCH 45/55] leverage 31799b7ae to simplify generation of routing hints --- .../channel/fsm/ChannelOpenSingleFunder.scala | 3 ++- .../scala/fr/acinq/eclair/router/Router.scala | 18 ++++++++---------- .../fr/acinq/eclair/router/Validation.scala | 9 +++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index d9275cdc2e..28fc47e194 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -389,7 +389,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { ) // we announce our identifiers as early as we can - val shortIds = ShortIds(real = realShortChannelIdStatus, localAlias = localAlias, remoteAlias_opt = channelReady.alias_opt) + val shortIds = ShortIds(real = realShortChannelIdStatus, localAlias = localAlias, remoteAlias_opt = None) context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortIds, remoteNodeId = remoteNodeId)) goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = shortIds, channelReady) storing() sending channelReady @@ -434,6 +434,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { log.info("received remoteAlias={}", remoteAlias) context.system.eventStream.publish(ShortChannelIdAssigned(self, d.commitments.channelId, shortIds = shortIds1, remoteNodeId = remoteNodeId)) } + log.info("shortIds={}", shortIds1) // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val scidForChannelUpdate = Helpers.scidForChannelUpdate(channelAnnouncement_opt = None, shortIds1) log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 273d72a99b..eff86e4813 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -39,7 +39,6 @@ import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph import fr.acinq.eclair.router.Graph.{HeuristicsConstants, WeightRatios} import fr.acinq.eclair.router.Monitoring.Metrics import fr.acinq.eclair.wire.protocol._ -import kamon.context.Context import java.util.UUID import scala.collection.immutable.SortedMap @@ -395,15 +394,14 @@ object Router { /** Create an invoice routing hint from that channel. Note that if the channel is private, the invoice will leak its existence. */ def toIncomingExtraHop: Option[ExtraHop] = { // we want the incoming channel_update - val (localUpdate_opt, remoteUpdate_opt) = if (localNodeId == nodeId1) (update_1_opt, update_2_opt) else (update_2_opt, update_1_opt) - (localUpdate_opt, remoteUpdate_opt) match { - case (Some(localUpdate), Some(remoteUpdate)) => - // this is tricky: for incoming payments we need the *remote alias*, we can find it in the channel_update that we sent them - Some(ExtraHop(remoteNodeId, localUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) - case (_, Some(remoteUpdate)) if remoteUpdate.shortChannelId != shortIds.localAlias => - // they are using a real scid (otherwise it would match our local alias, we can use it in the routing hint) - Some(ExtraHop(remoteNodeId, remoteUpdate.shortChannelId, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta)) - case _ => None + val remoteUpdate_opt = if (localNodeId == nodeId1) update_2_opt else update_1_opt + // for incoming payments we preferably use the *remote alias*, otherwise the real scid if we have it + val scid_opt = shortIds.remoteAlias_opt.orElse(shortIds.real.toOption) + // we override the remote update's scid, because it contains either the real scid or our local alias + scid_opt.flatMap { scid => + remoteUpdate_opt.map {remoteUpdate => + ExtraHop(remoteNodeId, scid, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta) + } } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index f9f3c651f0..b81842d00d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -503,12 +503,13 @@ object Validation { // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) log.debug("processing channel_update") - val d2 = handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) - d2 + handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) case None => log.debug("this is a known private channel, processing channel_update privateChannel={}", privateChannel) - // this is a known unannounced channel, we can process the channel_update - handleChannelUpdate(d, db, nodeParams.routerConf, Left(lcu)) + // this a known private channel, we update the short ids (may have the remote_alias) + val d1 = d.copy(privateChannels = d.privateChannels + (privateChannel.channelId -> privateChannel.copy(shortIds = lcu.shortIds))) + // then we can process the channel_update + handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) } case None => // should never happen, we log a warning and handle the update, it will be rejected since there is no related channel From 305124f5753eceefaa3a305a2c35f73b0e4fe5be Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 13 Jun 2022 15:09:44 +0200 Subject: [PATCH 46/55] simplify scid.equals() --- eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index 56d6d492d8..a868d47908 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -26,7 +26,7 @@ sealed trait ShortChannelId extends Ordered[ShortChannelId] { override def hashCode(): Int = toLong.hashCode() override def equals(obj: Any): Boolean = obj match { case scid: ShortChannelId => this.toLong.equals(scid.toLong) - case _ => super.equals(obj) + case _ => false } def toCoordinatesString: String = { val TxCoordinates(blockHeight, txIndex, outputIndex) = ShortChannelId.coordinates(this) From 79ef2c3d922ffd3f7e84c440d785169bc1cd2b7b Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:47:56 +0200 Subject: [PATCH 47/55] Remove watch-confirmed with depth 0 in zero-conf (#2310) Instead of using a watch with minDepth=0, we can directly skip the wait_for_funding_confirmed state when using 0-conf, which is less hacky. Co-authored-by: pm47 --- .../fr/acinq/eclair/ShortChannelId.scala | 2 +- .../blockchain/bitcoind/ZmqWatcher.scala | 32 ++----- .../fr/acinq/eclair/channel/ChannelData.scala | 6 +- .../acinq/eclair/channel/ChannelEvents.scala | 19 ++-- .../eclair/channel/ChannelFeatures.scala | 2 +- .../fr/acinq/eclair/channel/Helpers.scala | 26 +++--- .../fr/acinq/eclair/channel/Register.scala | 2 +- .../fr/acinq/eclair/channel/fsm/Channel.scala | 34 ++++---- .../channel/fsm/ChannelOpenSingleFunder.scala | 87 ++++++++----------- .../eclair/channel/fsm/FundingHandlers.scala | 19 +++- .../scala/fr/acinq/eclair/router/Graph.scala | 4 +- .../scala/fr/acinq/eclair/router/Router.scala | 4 +- .../fr/acinq/eclair/router/Validation.scala | 54 +++++++----- .../channel/version3/ChannelCodecs3.scala | 2 +- .../fr/acinq/eclair/ShortChannelIdSpec.scala | 17 ++-- .../fr/acinq/eclair/channel/HelpersSpec.scala | 16 ++-- .../ChannelStateTestsHelperMethods.scala | 26 +++--- .../b/WaitForFundingSignedStateSpec.scala | 7 +- .../c/WaitForChannelReadyStateSpec.scala | 17 ++-- .../c/WaitForFundingConfirmedStateSpec.scala | 54 +++--------- .../channel/states/e/NormalStateSpec.scala | 2 +- .../channel/version3/ChannelCodecs3Spec.scala | 2 +- 22 files changed, 202 insertions(+), 232 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index a868d47908..2265f220b7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -39,7 +39,7 @@ case class UnspecifiedShortChannelId(private val id: Long) extends ShortChannelI override def toLong: Long = id override def toString: String = toCoordinatesString // for backwards compatibility, because ChannelUpdate have an unspecified scid } -case class RealShortChannelId private (private val id: Long) extends ShortChannelId { +case class RealShortChannelId private(private val id: Long) extends ShortChannelId { override def toLong: Long = id override def toString: String = toCoordinatesString def blockHeight: BlockHeight = ShortChannelId.blockHeight(this) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index 37c31a368a..c9331c651a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -20,18 +20,17 @@ import akka.actor.typed.eventstream.EventStream import akka.actor.typed.scaladsl.{ActorContext, Behaviors, TimerScheduler} import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} import fr.acinq.bitcoin.scalacompat._ -import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair.blockchain.Monitoring.Metrics import fr.acinq.eclair.blockchain._ import fr.acinq.eclair.blockchain.bitcoind.rpc.BitcoinCoreClient import fr.acinq.eclair.blockchain.watchdogs.BlockchainWatchdog import fr.acinq.eclair.wire.protocol.ChannelAnnouncement -import fr.acinq.eclair.{BlockHeight, KamonExt, NodeParams, ShortChannelId, TimestampSecond} +import fr.acinq.eclair.{BlockHeight, KamonExt, NodeParams, RealShortChannelId, TimestampSecond} import java.util.concurrent.atomic.AtomicLong import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Random, Success} +import scala.util.{Failure, Success} /** * Created by PM on 21/02/2016. @@ -237,11 +236,6 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client case _: WatchConfirmed[_] => // nothing to do case _: WatchFundingLost => // nothing to do } - watches - .collect { - case w: WatchFundingConfirmed if w.minDepth == 0 && w.txId == tx.txid => - checkConfirmed(w) - } Behaviors.same case ProcessNewBlock(blockHash) => @@ -412,21 +406,13 @@ private class ZmqWatcher(nodeParams: NodeParams, blockHeight: AtomicLong, client client.getTxConfirmations(w.txId).flatMap { case Some(confirmations) if confirmations >= w.minDepth => client.getTransaction(w.txId).flatMap { tx => - w match { - case w: WatchFundingConfirmed if confirmations == 0 => - // if the tx doesn't have confirmations but we don't require any, we reply with a fake block index - // otherwise, we get the real short id - context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(BlockHeight(0), 0, tx)) - Future.successful((): Unit) - case _ => - client.getTransactionShortId(w.txId).map { - case (height, index) => w match { - case w: WatchFundingConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(height, index, tx)) - case w: WatchFundingDeeplyBuried => context.self ! TriggerEvent(w.replyTo, w, WatchFundingDeeplyBuriedTriggered(height, index, tx)) - case w: WatchTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchTxConfirmedTriggered(height, index, tx)) - case w: WatchParentTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchParentTxConfirmedTriggered(height, index, tx)) - } - } + client.getTransactionShortId(w.txId).map { + case (height, index) => w match { + case w: WatchFundingConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchFundingConfirmedTriggered(height, index, tx)) + case w: WatchFundingDeeplyBuried => context.self ! TriggerEvent(w.replyTo, w, WatchFundingDeeplyBuriedTriggered(height, index, tx)) + case w: WatchTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchTxConfirmedTriggered(height, index, tx)) + case w: WatchParentTxConfirmed => context.self ! TriggerEvent(w.replyTo, w, WatchParentTxConfirmedTriggered(height, index, tx)) + } } } case _ => Future.successful((): Unit) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 095d377be3..538f69d9ac 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -429,10 +429,14 @@ final case class DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments: Commitments, final case class DATA_WAIT_FOR_CHANNEL_READY(commitments: Commitments, shortIds: ShortIds, lastSent: ChannelReady) extends PersistentChannelData + sealed trait RealScidStatus { def toOption: Option[RealShortChannelId] } object RealScidStatus { + /** The funding transaction has been confirmed but hasn't reached min_depth, we must be ready for a reorg. */ case class Temporary(realScid: RealShortChannelId) extends RealScidStatus { override def toOption: Option[RealShortChannelId] = Some(realScid) } + /** The funding transaction has been deeply confirmed. */ case class Final(realScid: RealShortChannelId) extends RealScidStatus { override def toOption: Option[RealShortChannelId] = Some(realScid) } + /** We don't know the status of the funding transaction. */ case object Unknown extends RealScidStatus { override def toOption: Option[RealShortChannelId] = None } } @@ -441,7 +445,7 @@ object RealScidStatus { * * @param real the real scid, it may change if a reorg happens before the channel reaches 6 conf * @param localAlias we must remember the alias that we sent to our peer because we use it to: - * - identify incoming [[ChannelUpdate]] + * - identify incoming [[ChannelUpdate]] at the connection level * - route outgoing payments to that channel * @param remoteAlias_opt we only remember the last alias received from our peer, we use this to generate * routing hints in [[fr.acinq.eclair.payment.Bolt11Invoice]] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala index 910a731407..b41cffaa9c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala @@ -19,10 +19,10 @@ package fr.acinq.eclair.channel import akka.actor.ActorRef import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector32, Satoshi, Transaction} -import fr.acinq.eclair.{BlockHeight, Features, Alias, RealShortChannelId, ShortChannelId} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.Helpers.Closing.ClosingType import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelUpdate} +import fr.acinq.eclair.{BlockHeight, Features, ShortChannelId} /** * Created by PM on 17/08/2016. @@ -51,15 +51,18 @@ case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, sh case class LocalChannelUpdate(channel: ActorRef, channelId: ByteVector32, shortIds: ShortIds, remoteNodeId: PublicKey, channelAnnouncement_opt: Option[ChannelAnnouncement], channelUpdate: ChannelUpdate, commitments: AbstractCommitments) extends ChannelEvent { /** - * We always map the local alias because we must always be able to route based on it - * However we only map the real scid if option_scid_alias (TODO: rename to option_scid_privacy) is disabled + * We always include the local alias because we must always be able to route based on it. + * However we only include the real scid if option_scid_alias is disabled, because we otherwise want to hide it. */ def scidsForRouting: Seq[ShortChannelId] = { - commitments match { - case c: Commitments => - val realScid_opt = if (c.channelFeatures.hasFeature(Features.ScidAlias)) None else shortIds.real.toOption - realScid_opt.toSeq :+ shortIds.localAlias - case _ => Seq(shortIds.localAlias) // TODO: ugly + val canUseRealScid = commitments match { + case c: Commitments => !c.channelFeatures.hasFeature(Features.ScidAlias) + case _ => false + } + if (canUseRealScid) { + shortIds.real.toOption.toSeq :+ shortIds.localAlias + } else { + Seq(shortIds.localAlias) } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 7bd79bb98e..77b1b15561 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -115,7 +115,7 @@ object ChannelTypes { ).flatten override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" + override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zero_conf" else ""}" } case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType { override def features: Set[InitFeature] = featureBits.activated.keySet diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index a2649ef69e..238b9f4a9e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.ScriptFlags import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, sha256} import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat._ +import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.OnChainAddressGenerator import fr.acinq.eclair.blockchain.fee.{FeeEstimator, FeeTargets, FeeratePerKw} import fr.acinq.eclair.channel.fsm.Channel @@ -35,7 +36,6 @@ import fr.acinq.eclair.transactions.Scripts._ import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{RealShortChannelId, _} import scodec.bits.ByteVector import scala.concurrent.duration._ @@ -196,17 +196,17 @@ object Helpers { * - before channel announcement: use remote_alias * - after channel announcement: use real scid * - no remote_alias from peer - * - min_depth > 0 : use real scid (may change if reorg between min_depth and 6 conf) - * - min_depth = 0 (zero-conf) : unsupported + * - min_depth > 0: use real scid (may change if reorg between min_depth and 6 conf) + * - min_depth = 0 (zero-conf): spec violation, our peer MUST send an alias when using zero-conf */ - def scidForChannelUpdate(channelAnnouncement_opt: Option[ChannelAnnouncement], shortIds: ShortIds)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = { + def scidForChannelUpdate(channelAnnouncement_opt: Option[ChannelAnnouncement], shortIds: ShortIds): ShortChannelId = { channelAnnouncement_opt.map(_.shortChannelId) // we use the real "final" scid when it is publicly announced .orElse(shortIds.remoteAlias_opt) // otherwise the remote alias .orElse(shortIds.real.toOption) // if we don't have a remote alias, we use the real scid (which could change because the funding tx possibly has less than 6 confs here) .getOrElse(throw new RuntimeException("this is a zero-conf channel and no alias was provided in channel_ready")) // if we don't have a real scid, it means this is a zero-conf channel and our peer must have sent an alias } - def scidForChannelUpdate(d: DATA_NORMAL)(implicit log: DiagnosticLoggingAdapter): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, d.shortIds) + def scidForChannelUpdate(d: DATA_NORMAL): ShortChannelId = scidForChannelUpdate(d.channelAnnouncement, d.shortIds) /** * Compute the delay until we need to refresh the channel_update for our channel not to be considered stale by @@ -291,11 +291,11 @@ object Helpers { * wait for one conf, except if the channel has the zero-conf feature (because presumably the peer will send an * alias in that case). */ - def minDepthFunder(channelFeatures: ChannelFeatures): Long = { + def minDepthFunder(channelFeatures: ChannelFeatures): Option[Long] = { if (channelFeatures.hasFeature(Features.ZeroConf)) { - 0 + None } else { - 1 + Some(1) } } @@ -304,16 +304,16 @@ object Helpers { * we make sure the cumulative block reward largely exceeds the channel size. * * @param fundingSatoshis funding amount of the channel - * @return number of confirmations needed + * @return number of confirmations needed, if any */ - def minDepthFundee(channelConf: ChannelConf, channelFeatures: ChannelFeatures, fundingSatoshis: Satoshi): Long = fundingSatoshis match { - case _ if channelFeatures.hasFeature(Features.ZeroConf) => 0 // zero-conf stay zero-conf, whatever the funding amount is - case funding if funding <= Channel.MAX_FUNDING => channelConf.minDepthBlocks + def minDepthFundee(channelConf: ChannelConf, channelFeatures: ChannelFeatures, fundingSatoshis: Satoshi): Option[Long] = fundingSatoshis match { + case _ if channelFeatures.hasFeature(Features.ZeroConf) => None // zero-conf stay zero-conf, whatever the funding amount is + case funding if funding <= Channel.MAX_FUNDING => Some(channelConf.minDepthBlocks) case funding => val blockReward = 6.25 // this is true as of ~May 2020, but will be too large after 2024 val scalingFactor = 15 val blocksToReachFunding = (((scalingFactor * funding.toBtc.toDouble) / blockReward).ceil + 1).toInt - channelConf.minDepthBlocks.max(blocksToReachFunding) + Some(channelConf.minDepthBlocks.max(blocksToReachFunding)) } def makeFundingInputInfo(fundingTxId: ByteVector32, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala index c385f352ac..7622bd13f1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala @@ -49,7 +49,7 @@ class Register extends Actor with ActorLogging { case scidAssigned: ShortChannelIdAssigned => // We map all known scids (real or alias) to the channel_id. The relayer is in charge of deciding whether a real - // scid can be used or not for routing (see option_scid_privacy), but the register is neutral. + // scid can be used or not for routing (see option_scid_alias), but the register is neutral. val m = (scidAssigned.shortIds.real.toOption.toSeq :+ scidAssigned.shortIds.localAlias).map(_ -> scidAssigned.channelId).toMap context become main(channels, shortIds ++ m, channelsTo) 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 106a48ad6f..8d8a80e800 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 @@ -615,30 +615,31 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case Event(WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, fundingTx), d: DATA_NORMAL) if d.channelAnnouncement.isEmpty => val finalRealShortId = RealScidStatus.Final(RealShortChannelId(blockHeight, txIndex, d.commitments.commitInput.outPoint.index.toInt)) - val shortIds1 = d.shortIds.copy(real = finalRealShortId) log.info(s"funding tx is deeply buried at blockHeight=$blockHeight txIndex=$txIndex shortChannelId=${finalRealShortId.realScid}") + val shortIds1 = d.shortIds.copy(real = finalRealShortId) + context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortIds1, remoteNodeId)) if (d.shortIds.real == RealScidStatus.Unknown) { // this is a zero-conf channel and it is the first time we know for sure that the funding tx has been confirmed context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) } - if (!d.shortIds.real.toOption.contains(finalRealShortId.realScid)) { - log.info(s"setting final real scid: old=${d.shortIds.real} new=${finalRealShortId}") - // we announce the new shortChannelId - context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortIds1, remoteNodeId)) - } val scidForChannelUpdate = Helpers.scidForChannelUpdate(d.channelAnnouncement, shortIds1) // if the shortChannelId is different from the one we had before, we need to re-announce it val channelUpdate1 = if (d.channelUpdate.shortChannelId != scidForChannelUpdate) { log.info(s"using new scid in channel_update: old=${d.channelUpdate.shortChannelId} new=$scidForChannelUpdate") // we re-announce the channelUpdate for the same reason Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, scidForChannelUpdate, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = Helpers.aboveReserve(d.commitments)) - } else d.channelUpdate - val localAnnSigs_opt = if (d.commitments.announceChannel) { + } else { + d.channelUpdate + } + if (d.commitments.announceChannel) { // if channel is public we need to send our announcement_signatures in order to generate the channel_announcement - Some(Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, finalRealShortId.realScid)) - } else None - // we use goto() instead of stay() because we want to fire transitions - goto(NORMAL) using d.copy(shortIds = shortIds1, channelUpdate = channelUpdate1) storing() sending localAnnSigs_opt.toSeq + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments, finalRealShortId.realScid) + // we use goto() instead of stay() because we want to fire transitions + goto(NORMAL) using d.copy(shortIds = shortIds1, channelUpdate = channelUpdate1) storing() sending localAnnSigs + } else { + // we use goto() instead of stay() because we want to fire transitions + goto(NORMAL) using d.copy(shortIds = shortIds1, channelUpdate = channelUpdate1) storing() + } case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_NORMAL) if d.commitments.announceChannel => // channels are publicly announced if both parties want it (defined as feature bit) @@ -1326,7 +1327,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val Helpers.Funding.minDepthFundee(nodeParams.channelConf, d.commitments.channelFeatures, d.commitments.commitInput.txOut.amount) } // we put back the watch (operation is idempotent) because the event may have been fired while we were in OFFLINE - blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, minDepth) + require(minDepth.nonEmpty, "min_depth must be set since we're waiting for the funding tx to confirm") + blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, minDepth.get) goto(WAIT_FOR_FUNDING_CONFIRMED) case Event(_: ChannelReestablish, d: DATA_WAIT_FOR_CHANNEL_READY) => @@ -1618,6 +1620,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // We only send the channel_update directly to the peer if we are connected AND the channel hasn't been announced val emitEvent_opt: Option[EmitLocalChannelEvent] = (state, nextState, stateData, nextStateData) match { 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 (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)) @@ -1628,7 +1631,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val } emitEvent_opt.foreach { case EmitLocalChannelUpdate(reason, d, sendToPeer) => - log.info(s"emitting channel update event: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=${sendToPeer} realScid=${d.shortIds.real} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) + log.info(s"emitting channel update event: reason=$reason enabled=${d.channelUpdate.channelFlags.isEnabled} sendToPeer=$sendToPeer realScid=${d.shortIds.real} channel_update={} channel_announcement={}", d.channelUpdate, d.channelAnnouncement.map(_ => "yes").getOrElse("no")) val lcu = LocalChannelUpdate(self, d.channelId, d.shortIds, d.commitments.remoteParams.nodeId, d.channelAnnouncement, d.channelUpdate, d.commitments) context.system.eventStream.publish(lcu) if (sendToPeer) { @@ -1644,7 +1647,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val (stateData, nextStateData) match { // NORMAL->NORMAL, NORMAL->OFFLINE, SYNCING->NORMAL case (d1: DATA_NORMAL, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = Some(d1.channelUpdate), d2) - // WAIT_FOR_CHANNEL_READY->NORMAL + // WAIT_FOR_FUNDING_CONFIRMED->NORMAL, WAIT_FOR_CHANNEL_READY->NORMAL + case (_: DATA_WAIT_FOR_FUNDING_CONFIRMED, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = None, d2) case (_: DATA_WAIT_FOR_CHANNEL_READY, d2: DATA_NORMAL) => maybeEmitChannelUpdateChangedEvent(newUpdate = d2.channelUpdate, oldUpdate_opt = None, d2) case _ => () } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index 28fc47e194..db13acfbad 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -31,8 +31,8 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.Transactions.TxOwner import fr.acinq.eclair.transactions.{Scripts, Transactions} -import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelReady, ChannelReadyTlv, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream} -import fr.acinq.eclair.{BlockHeight, Features, Alias, RealShortChannelId, ShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, AnnouncementSignatures, ChannelReady, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream} +import fr.acinq.eclair.{Features, RealShortChannelId, ToMilliSatoshiConversion, randomKey, toLongId} import scodec.bits.ByteVector import scala.concurrent.duration.DurationInt @@ -63,8 +63,8 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { WAIT_FOR_FUNDING_SIGNED| | | funding_signed | |<-------------------------------| - WAIT_FOR_FUNDING_LOCKED| |WAIT_FOR_FUNDING_LOCKED - | funding_locked funding_locked | + WAIT_FOR_CHANNEL_READY| |WAIT_FOR_CHANNEL_READY + | channel_ready channel_ready | |--------------- ---------------| | \/ | | /\ | @@ -89,7 +89,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { dustLimitSatoshis = localParams.dustLimit, maxHtlcValueInFlightMsat = localParams.maxHtlcValueInFlightMsat, channelReserveSatoshis = localParams.requestedChannelReserve_opt.getOrElse(0 sat), - minimumDepth = minimumDepth, + minimumDepth = minimumDepth.getOrElse(0), htlcMinimumMsat = localParams.htlcMinimum, toSelfDelay = localParams.toSelfDelay, maxAcceptedHtlcs = localParams.maxAcceptedHtlcs, @@ -256,9 +256,14 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { // NB: we don't send a ChannelSignatureSent for the first commit log.info(s"waiting for them to publish the funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") watchFundingTx(commitments) - val fundingMinDepth = Funding.minDepthFundee(nodeParams.channelConf, commitments.channelFeatures, fundingAmount) - blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) - goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, None, nodeParams.currentBlockHeight, None, Right(fundingSigned)) storing() sending fundingSigned + Funding.minDepthFundee(nodeParams.channelConf, commitments.channelFeatures, fundingAmount) match { + case Some(fundingMinDepth) => + blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) + goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, None, nodeParams.currentBlockHeight, None, Right(fundingSigned)) storing() sending fundingSigned + case None => + val (shortIds, channelReady) = acceptFundingTx(commitments, RealScidStatus.Unknown) + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds, channelReady) storing() sending Seq(fundingSigned, channelReady) + } } } @@ -296,9 +301,6 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx for channelId=$channelId fundingTxid=${commitInput.outPoint.txid}") watchFundingTx(commitments) - val fundingMinDepth = Funding.minDepthFunder(commitments.channelFeatures) - blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) - log.info(s"committing txid=${fundingTx.txid}") // we will publish the funding tx only after the channel state has been written to disk because we want to // make sure we first persist the commitment that returns back the funds to us in case of problem @@ -316,7 +318,14 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { } } - goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, Some(fundingTx), blockHeight, None, Left(fundingCreated)) storing() calling publishFundingTx() + Funding.minDepthFunder(commitments.channelFeatures) match { + case Some(fundingMinDepth) => + blockchain ! WatchFundingConfirmed(self, commitInput.outPoint.txid, fundingMinDepth) + goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, Some(fundingTx), blockHeight, None, Left(fundingCreated)) storing() calling publishFundingTx() + case None => + val (shortIds, channelReady) = acceptFundingTx(commitments, RealScidStatus.Unknown) + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds, channelReady) storing() sending channelReady calling publishFundingTx() + } } case Event(c: CloseCommand, d: DATA_WAIT_FOR_FUNDING_SIGNED) => @@ -345,54 +354,30 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions { - case Event(channelReady: ChannelReady, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) => - if (channelReady.alias_opt.isDefined && - d.commitments.localParams.isInitiator && - !d.commitments.channelFeatures.features.contains(Features.ZeroConf)) { + 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") - // we set a new zero-conf watch which will trigger instantly, the original watch will trigger later and be ignored - blockchain ! WatchFundingConfirmed(self, d.commitments.commitInput.outPoint.txid, 0) + // 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 + // NB: we will receive a WatchFundingConfirmedTriggered later that will simply be ignored + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(d.commitments, shortIds, localChannelReady) storing() sending localChannelReady } else { log.info("received their channel_ready, deferring message") + stay() using d.copy(deferred = Some(remoteChannelReady)) // no need to store, they will re-send if we get disconnected } - stay() using d.copy(deferred = Some(channelReady)) // no need to store, they will re-send if we get disconnected case Event(WatchFundingConfirmedTriggered(blockHeight, txIndex, fundingTx), d@DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, _, _, deferred, _)) => Try(Transaction.correctlySpends(commitments.fullySignedLocalCommitTx(keyManager).tx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) match { case Success(_) => - blockchain ! WatchFundingLost(self, commitments.commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks) + log.info(s"channelId=${d.channelId} was confirmed at blockHeight=$blockHeight txIndex=$txIndex") if (!d.commitments.localParams.isInitiator) context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, fundingTx, 0 sat, "funding")) - val channelKeyPath = keyManager.keyPath(d.commitments.localParams, commitments.channelConfig) - val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) - deferred.foreach(self ! _) - // this is the real scid, it might change when the funding tx gets deeply buried (if there was a reorg in the meantime) - val realShortChannelIdStatus = if (blockHeight == BlockHeight(0)) { - // If we are using zero-conf then the transaction may not have been confirmed yet, that's why the block - // height is zero. In some cases (e.g. we were down for some time) the tx may actually have confirmed, in - // that case we will have a real scid even if the channel is zero-conf - log.info("skipping funding tx confirmation") - RealScidStatus.Unknown - } - else { - log.info(s"channel was confirmed at blockHeight=$blockHeight txIndex=$txIndex") - context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) - RealScidStatus.Temporary(RealShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) - } - // the alias will use in our channel_update message, the goal is to be able to use our channel - // as soon as it reaches NORMAL state, and before it is announced on the network - val localAlias = ShortChannelId.generateLocalAlias() - // we always send our local alias, even if it isn't explicitly supported, that's an optional TLV anyway - val channelReady = ChannelReady( - channelId = commitments.channelId, - nextPerCommitmentPoint = nextPerCommitmentPoint, - tlvStream = TlvStream(ChannelReadyTlv.ShortChannelIdTlv(localAlias)) - ) + context.system.eventStream.publish(TransactionConfirmed(d.channelId, remoteNodeId, fundingTx)) - // we announce our identifiers as early as we can - val shortIds = ShortIds(real = realShortChannelIdStatus, localAlias = localAlias, remoteAlias_opt = None) - context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortIds, remoteNodeId = remoteNodeId)) - - goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds = shortIds, channelReady) storing() sending channelReady + val realScidStatus = RealScidStatus.Temporary(RealShortChannelId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)) + val (shortIds, channelReady) = acceptFundingTx(commitments, realScidStatus = realScidStatus) + deferred.foreach(self ! _) + goto(WAIT_FOR_CHANNEL_READY) using DATA_WAIT_FOR_CHANNEL_READY(commitments, shortIds, channelReady) storing() sending channelReady case Failure(t) => log.error(t, s"rejecting channel with invalid funding tx: ${fundingTx.bin}") goto(CLOSED) @@ -444,7 +429,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { context.system.scheduler.scheduleWithFixedDelay(initialDelay = REFRESH_CHANNEL_UPDATE_INTERVAL, delay = REFRESH_CHANNEL_UPDATE_INTERVAL, receiver = self, message = BroadcastChannelUpdate(PeriodicRefresh)) // used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly) blockchain ! WatchFundingDeeplyBuried(self, d.commitments.commitInput.outPoint.txid, ANNOUNCEMENTS_MINCONF) - goto(NORMAL) using DATA_NORMAL(d.commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), shortIds = shortIds1, None, initialChannelUpdate, None, None, None) storing() + goto(NORMAL) using DATA_NORMAL(d.commitments.copy(remoteNextCommitInfo = Right(channelReady.nextPerCommitmentPoint)), shortIds1, None, initialChannelUpdate, None, None, None) storing() case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_CHANNEL_READY) if d.commitments.announceChannel => log.debug("received remote announcement signatures, delaying") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/FundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/FundingHandlers.scala index f478859c2f..58adea13a7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/FundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/FundingHandlers.scala @@ -19,12 +19,12 @@ package fr.acinq.eclair.channel.fsm import akka.actor.Status import akka.actor.typed.scaladsl.adapter.{TypedActorRefOps, actorRefAdapter} import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Transaction} -import fr.acinq.eclair.BlockHeight -import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMeta, GetTxWithMetaResponse, WatchFundingSpent} +import fr.acinq.eclair.{Alias, BlockHeight, RealShortChannelId, ShortChannelId} +import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher.{GetTxWithMeta, GetTxWithMetaResponse, WatchFundingLost, WatchFundingSpent} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITCOIN_FUNDING_TIMEOUT, FUNDING_TIMEOUT_FUNDEE} import fr.acinq.eclair.channel.publish.TxPublisher.PublishFinalTx -import fr.acinq.eclair.wire.protocol.Error +import fr.acinq.eclair.wire.protocol.{ChannelReady, ChannelReadyTlv, Error, TlvStream} import scala.concurrent.duration.DurationInt import scala.util.{Failure, Success} @@ -59,6 +59,19 @@ trait FundingHandlers extends CommonHandlers { // TODO: implement this? (not needed if we use a reasonable min_depth) //blockchain ! WatchLost(self, commitments.commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks, BITCOIN_FUNDING_LOST) } + + def acceptFundingTx(commitments: Commitments, realScidStatus: RealScidStatus): (ShortIds, ChannelReady) = { + blockchain ! WatchFundingLost(self, commitments.commitInput.outPoint.txid, nodeParams.channelConf.minDepthBlocks) + val channelKeyPath = keyManager.keyPath(commitments.localParams, commitments.channelConfig) + val nextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, 1) + // the alias will use in our peer's channel_update message, the goal is to be able to use our channel as soon + // as it reaches NORMAL state, and before it is announced on the network + val shortIds = ShortIds(realScidStatus, ShortChannelId.generateLocalAlias(), remoteAlias_opt = None) + context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortIds, remoteNodeId)) + // we always send our local alias, even if it isn't explicitly supported, that's an optional TLV anyway + val channelReady = ChannelReady(commitments.channelId, nextPerCommitmentPoint, TlvStream(ChannelReadyTlv.ShortChannelIdTlv(shortIds.localAlias))) + (shortIds, channelReady) + } /** * When we are funder, we use this function to detect when our funding tx has been double-spent (by another transaction diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 8b091bc3db..73c4fcca11 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -311,9 +311,7 @@ object Graph { // Every edge is weighted by funding block height where older blocks add less weight. The window considered is 1 year. val ageFactor = edge.desc.shortChannelId match { - case real: RealShortChannelId => - val channelBlockHeight = ShortChannelId.coordinates(real).blockHeight - normalize(channelBlockHeight.toDouble, min = (currentBlockHeight - BLOCK_TIME_ONE_YEAR).toDouble, max = currentBlockHeight.toDouble) + case real: RealShortChannelId => normalize(real.blockHeight.toDouble, min = (currentBlockHeight - BLOCK_TIME_ONE_YEAR).toDouble, max = currentBlockHeight.toDouble) // for local channels or route hints we don't easily have access to the channel block height, but we want to // give them the best score anyway case _: Alias => 1 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index eff86e4813..5755151d33 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -399,10 +399,10 @@ object Router { val scid_opt = shortIds.remoteAlias_opt.orElse(shortIds.real.toOption) // we override the remote update's scid, because it contains either the real scid or our local alias scid_opt.flatMap { scid => - remoteUpdate_opt.map {remoteUpdate => + remoteUpdate_opt.map { remoteUpdate => ExtraHop(remoteNodeId, scid, remoteUpdate.feeBaseMsat, remoteUpdate.feeProportionalMillionths, remoteUpdate.cltvExpiryDelta) - } } + } } } // @formatter:on diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala index b81842d00d..b173ceb2a7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala @@ -81,7 +81,6 @@ object Validation { def handleChannelValidationResponse(d0: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], r: ValidateResult)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - import nodeParams.db.{network => db} import r.c // now we can acknowledge the message, we only need to do it for the first peer that sent us the announcement // (the other ones have already been acknowledged as duplicates) @@ -118,15 +117,15 @@ object Validation { } case ValidateResult(c, Right((tx, fundingTxStatus: UtxoStatus.Spent))) => if (fundingTxStatus.spendingTxConfirmed) { - log.debug("ignoring shortChannelId={} tx={} (funding tx already spent and spending tx is confirmed)", c.shortChannelId, tx.txid) + log.debug("ignoring shortChannelId={} txid={} (funding tx already spent and spending tx is confirmed)", c.shortChannelId, tx.txid) // the funding tx has been spent by a transaction that is now confirmed: peer shouldn't send us those remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.ChannelClosed(c))) } else { - log.debug("ignoring shortChannelId={} tx={} (funding tx already spent but spending tx isn't confirmed)", c.shortChannelId, tx.txid) + log.debug("ignoring shortChannelId={} txid={} (funding tx already spent but spending tx isn't confirmed)", c.shortChannelId, tx.txid) remoteOrigins.foreach(o => sendDecision(o.peerConnection, GossipDecision.ChannelClosing(c))) } // there may be a record if we have just restarted - db.removeChannel(c.shortChannelId) + nodeParams.db.network.removeChannel(c.shortChannelId) None } // we also reprocess node and channel_update announcements related to the channel that was just analyzed @@ -159,12 +158,11 @@ object Validation { private def addPublicChannel(d: Data, nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Command], ann: ChannelAnnouncement, fundingTxid: ByteVector32, capacity: Satoshi)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - import nodeParams.db.{network => db} val fundingOutputIndex = outputIndex(ann.shortChannelId) val channelId = toLongId(fundingTxid.reverse, fundingOutputIndex) watcher ! WatchExternalChannelSpent(ctx.self, fundingTxid, fundingOutputIndex, ann.shortChannelId) ctx.system.eventStream.publish(ChannelsDiscovered(SingleChannelDiscovered(ann, capacity, None, None) :: Nil)) - db.addChannel(ann, fundingTxid, capacity) + nodeParams.db.network.addChannel(ann, fundingTxid, capacity) // if this is a local channel graduating from private to public, we already have data val privChan_opt = d.privateChannels.get(channelId) val pubChan = PublicChannel( @@ -207,7 +205,7 @@ object Validation { rebroadcast = d.rebroadcast.copy( // we rebroadcast the channel to our peers channels = d.rebroadcast.channels + (pubChan.ann -> d.awaiting.getOrElse(pubChan.ann, if (pubChan.nodeId1 == nodeParams.nodeId || pubChan.nodeId2 == nodeParams.nodeId) Seq(LocalGossip) else Nil).toSet), - // those updates are only defined if the channel is was previously an unannounced local channel, we broadcast them + // those updates are only defined if the channel was previously an unannounced local channel, we broadcast them updates = d.rebroadcast.updates ++ rebroadcastUpdates1 ), graphWithBalances = graph1 @@ -225,7 +223,7 @@ object Validation { val lostChannel = d.channels(shortChannelId).ann log.info("funding tx of channelId={} has been spent", shortChannelId) // we need to remove nodes that aren't tied to any channels anymore - val channels1 = d.channels - lostChannel.shortChannelId + val channels1 = d.channels - shortChannelId val lostNodes = Seq(lostChannel.nodeId1, lostChannel.nodeId2).filterNot(nodeId => hasChannels(nodeId, channels1.values)) // let's clean the db and send the events log.info("pruning shortChannelId={} (spent)", shortChannelId) @@ -242,12 +240,12 @@ object Validation { db.removeNode(nodeId) ctx.system.eventStream.publish(NodeLost(nodeId)) } - d.copy(nodes = d.nodes -- lostNodes, channels = d.channels - shortChannelId, graphWithBalances = graphWithBalances1) + d.copy(nodes = d.nodes -- lostNodes, channels = channels1, graphWithBalances = graphWithBalances1) } def handleNodeAnnouncement(d: Data, db: NetworkDb, origins: Set[GossipOrigin], n: NodeAnnouncement, wasStashed: Boolean = false)(implicit ctx: ActorContext, log: LoggingAdapter): Data = { implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors - val remoteOrigins = origins flatMap { + val remoteOrigins = origins.flatMap { case r: RemoteGossip if wasStashed => Some(r.peerConnection) case RemoteGossip(peerConnection, _) => @@ -267,7 +265,7 @@ object Validation { remoteOrigins.foreach(sendDecision(_, GossipDecision.Accepted(n))) val origins1 = d.rebroadcast.nodes(n) ++ origins d.copy(rebroadcast = d.rebroadcast.copy(nodes = d.rebroadcast.nodes + (n -> origins1))) - } else if (d.nodes.contains(n.nodeId) && d.nodes(n.nodeId).timestamp >= n.timestamp) { + } else if (d.nodes.get(n.nodeId).exists(_.timestamp >= n.timestamp)) { log.debug("ignoring {} (duplicate)", n) remoteOrigins.foreach(sendDecision(_, GossipDecision.Duplicate(n))) d @@ -319,10 +317,15 @@ object Validation { log.debug("ignoring {} (pending rebroadcast)", u) sendDecision(origins, GossipDecision.Accepted(u)) val origins1 = d.rebroadcast.updates(u) ++ origins - // NB: we update the channels because the balances may have changed even if the channel_update is the same. - val pc1 = pc.applyChannelUpdate(update) - val graphWithBalances1 = d.graphWithBalances.addEdge(GraphEdge(u, pc1)) - d.copy(rebroadcast = d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins1)), channels = d.channels + (pc.shortChannelId -> pc1), graphWithBalances = graphWithBalances1) + update match { + case Left(_) => + // NB: we update the channels because the balances may have changed even if the channel_update is the same. + val pc1 = pc.applyChannelUpdate(update) + val graphWithBalances1 = d.graphWithBalances.addEdge(GraphEdge(u, pc1)) + d.copy(rebroadcast = d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins1)), channels = d.channels + (pc.shortChannelId -> pc1), graphWithBalances = graphWithBalances1) + case Right(_) => + d.copy(rebroadcast = d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins1))) + } } else if (StaleChannels.isStale(u)) { log.debug("ignoring {} (stale)", u) sendDecision(origins, GossipDecision.Stale(u)) @@ -367,7 +370,7 @@ object Validation { val pc1 = pc.applyChannelUpdate(update) val graphWithBalances1 = d.graphWithBalances.addEdge(GraphEdge(u, pc1)) update.left.foreach(_ => log.info("added local shortChannelId={} public={} to the network graph", u.shortChannelId, publicChannel)) - d.copy(channels = d.channels + (pc.shortChannelId -> pc1), privateChannels = d.privateChannels - pc1.channelId, rebroadcast = d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins)), graphWithBalances = graphWithBalances1) + d.copy(channels = d.channels + (pc.shortChannelId -> pc1), rebroadcast = d.rebroadcast.copy(updates = d.rebroadcast.updates + (u -> origins)), graphWithBalances = graphWithBalances1) } case Some(pc: PrivateChannel) => val publicChannel = false @@ -375,7 +378,7 @@ object Validation { log.debug("ignoring {} (stale)", u) sendDecision(origins, GossipDecision.Stale(u)) d - } else if (pc.getChannelUpdateSameSideAs(u).exists(_.timestamp >= u.timestamp)) { + } else if (pc.getChannelUpdateSameSideAs(u).exists(previous => previous.timestamp >= u.timestamp && previous.shortChannelId == u.shortChannelId)) { // NB: we also check the id because there could be a switch alias->real scid log.debug("ignoring {} (already know same or newer)", u) sendDecision(origins, GossipDecision.Duplicate(u)) d @@ -426,18 +429,17 @@ object Validation { // but we ignored it because the channel was in the 'pruned' list. Now that we know that the channel is alive again, // let's remove the channel from the zombie list and ask the sender to re-send announcements (channel_announcement + updates) // about that channel. We can ignore this update since we will receive it again - log.info(s"channel shortChannelId=${realShortChannelId} is back from the dead! requesting announcements about this channel") + log.info(s"channel shortChannelId=$realShortChannelId is back from the dead! requesting announcements about this channel") sendDecision(origins, GossipDecision.RelatedChannelPruned(u)) db.removeFromPruned(realShortChannelId) // peerConnection_opt will contain a valid peerConnection only when we're handling an update that we received from a peer, not // when we're sending updates to ourselves - origins head match { + origins.head match { case RemoteGossip(peerConnection, remoteNodeId) => val query = QueryShortChannelIds(u.chainHash, EncodedShortChannelIds(routerConf.encodingType, List(realShortChannelId)), TlvStream.empty) d.sync.get(remoteNodeId) match { case Some(sync) if sync.started => // we already have a pending request to that node, let's add this channel to the list and we'll get it later - // TODO: we only request channels with old style channel_query d.copy(sync = d.sync + (remoteNodeId -> sync.copy(remainingQueries = sync.remainingQueries :+ query, totalQueries = sync.totalQueries + 1))) case _ => // otherwise we send the query right away @@ -498,16 +500,20 @@ object Validation { // channel is graduating from private to public // since this is a local channel, we can trust the announcement, no need to go through the full // verification process and make calls to bitcoin core - val commitments = lcu.commitments.asInstanceOf[Commitments] // TODO: ugly! a public channel has to have a real commitment - val d1 = addPublicChannel(d, nodeParams, watcher, ann, fundingTxid = commitments.commitInput.outPoint.txid, capacity = commitments.capacity) + val fundingTxId = lcu.commitments match { + case commitments: Commitments => commitments.commitInput.outPoint.txid + case _ => ByteVector32.Zeroes + } + val d1 = addPublicChannel(d, nodeParams, watcher, ann, fundingTxId, lcu.commitments.capacity) // maybe the local channel was pruned (can happen if we were disconnected for more than 2 weeks) db.removeFromPruned(ann.shortChannelId) log.debug("processing channel_update") handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) case None => log.debug("this is a known private channel, processing channel_update privateChannel={}", privateChannel) - // this a known private channel, we update the short ids (may have the remote_alias) - val d1 = d.copy(privateChannels = d.privateChannels + (privateChannel.channelId -> privateChannel.copy(shortIds = lcu.shortIds))) + // this a known private channel, we update the short ids (we now may have the remote_alias) and the balances + val pc1 = privateChannel.copy(shortIds = lcu.shortIds).updateBalances(lcu.commitments) + val d1 = d.copy(privateChannels = d.privateChannels + (privateChannel.channelId -> pc1)) // then we can process the channel_update handleChannelUpdate(d1, db, nodeParams.routerConf, Left(lcu)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index 8d239dd94b..8dfb7410b8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -410,7 +410,7 @@ private[channel] object ChannelCodecs3 { // Order matters! val channelDataCodec: Codec[PersistentChannelData] = discriminated[PersistentChannelData].by(uint16) - .typecase(0x10, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) + .typecase(0x0a, Codecs.DATA_WAIT_FOR_CHANNEL_READY_Codec) .typecase(0x09, Codecs.DATA_NORMAL_Codec) .typecase(0x08, Codecs.DATA_SHUTDOWN_Codec) .typecase(0x07, Codecs.DATA_NORMAL_COMPAT_07_Codec) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala index 90dd3f0a0c..ae6853e51a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala @@ -20,11 +20,9 @@ import org.scalatest.funsuite.AnyFunSuite import scala.util.Try - - class ShortChannelIdSpec extends AnyFunSuite { - test("handle values from 0 to 0xffffffffffff") { + test("handle real short channel ids from 0 to 0xffffffffffff") { val expected = Map( TxCoordinates(BlockHeight(0), 0, 0) -> RealShortChannelId(0), TxCoordinates(BlockHeight(42000), 27, 3) -> RealShortChannelId(0x0000a41000001b0003L), @@ -44,7 +42,7 @@ class ShortChannelIdSpec extends AnyFunSuite { assert(RealShortChannelId(0x0000a41000001b0003L).toString == "42000x27x3") } - test("parse a short channel it") { + test("parse a short channel id") { assert(ShortChannelId("42000x27x3").toLong == 0x0000a41000001b0003L) } @@ -58,17 +56,18 @@ class ShortChannelIdSpec extends AnyFunSuite { assert(Try(ShortChannelId("42000x")).isFailure) } - test("scids key space") { - + test("compare different types of short channel ids") { val id = 123456 val alias = Alias(id) val realScid = RealShortChannelId(id) val scid = ShortChannelId(id) - + assert(alias == realScid) + assert(realScid == scid) val m = Map(alias -> "alias", realScid -> "real", scid -> "unknown") - // all scids are in the same key space assert(m.size == 1) - + // Values outside of the range [0;0xffffffffffff] can be used for aliases. + Seq(-561L, 0xffffffffffffffffL, 0x2affffffffffffffL).foreach(id => assert(Alias(id) == UnspecifiedShortChannelId(id))) } + } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 1f01194324..d521d6c5f8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -40,14 +40,14 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging test("compute the funding tx min depth according to funding amount") { - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(1)) == 4) - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf.copy(minDepthBlocks = 6), ChannelFeatures(), Btc(1)) == 6) // 4 conf would be enough but we use min-depth=6 - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(6.25)) == 16) // we use scaling_factor=15 and a fixed block reward of 6.25BTC - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.50)) == 31) - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.60)) == 32) - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(30)) == 73) - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(50)) == 121) - assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(Features.ZeroConf), Btc(50)) == 0) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(1)).contains(4)) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf.copy(minDepthBlocks = 6), ChannelFeatures(), Btc(1)).contains(6)) // 4 conf would be enough but we use min-depth=6 + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(6.25)).contains(16)) // we use scaling_factor=15 and a fixed block reward of 6.25BTC + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.50)).contains(31)) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(12.60)).contains(32)) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(30)).contains(73)) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(), Btc(50)).contains(121)) + assert(Helpers.Funding.minDepthFundee(nodeParams.channelConf, ChannelFeatures(Features.ZeroConf), Btc(50)).isEmpty) } test("compute refresh delay") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala index 569d276ab3..b25046e630 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala @@ -214,6 +214,9 @@ trait ChannelStateTestsBase extends Assertions with Eventually { (TestConstants.fundingSatoshis, TestConstants.pushMsat) } + val eventListener = TestProbe() + systemA.eventStream.subscribe(eventListener.ref, classOf[TransactionPublished]) + val aliceInit = Init(aliceParams.initFeatures) val bobInit = Init(bobParams.initFeatures) alice ! INPUT_INIT_FUNDER(ByteVector32.Zeroes, fundingSatoshis, pushMsat, commitTxFeerate, TestConstants.feeratePerKw, aliceParams, alice2bob.ref, bobInit, channelFlags, channelConfig, channelType) @@ -230,14 +233,18 @@ trait ChannelStateTestsBase extends Assertions with Eventually { bob2alice.forward(alice) assert(alice2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId != ByteVector32.Zeroes) alice2blockchain.expectMsgType[WatchFundingSpent] - val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] assert(bob2blockchain.expectMsgType[TxPublisher.SetChannelId].channelId != ByteVector32.Zeroes) bob2blockchain.expectMsgType[WatchFundingSpent] - val bobWatchFundingConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] - eventually(assert(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)) - val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - alice ! fundingConfirmedEvent(aliceWatchFundingConfirmed, fundingTx) - bob ! fundingConfirmedEvent(bobWatchFundingConfirmed, fundingTx) + val fundingTx = eventListener.expectMsgType[TransactionPublished].tx + if (!channelType.features.contains(Features.ZeroConf)) { + alice2blockchain.expectMsgType[WatchFundingConfirmed] + bob2blockchain.expectMsgType[WatchFundingConfirmed] + eventually(assert(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)) + alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + } + eventually(assert(alice.stateName == WAIT_FOR_CHANNEL_READY)) + eventually(assert(bob.stateName == WAIT_FOR_CHANNEL_READY)) alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] alice2bob.expectMsgType[ChannelReady] @@ -260,13 +267,6 @@ trait ChannelStateTestsBase extends Assertions with Eventually { fundingTx } - /** This simulates the behavior of our watcher: it replies to zero-conf watches with a zero block height. */ - def fundingConfirmedEvent(watch: WatchFundingConfirmed, fundingTx: Transaction) = if (watch.minDepth == 0) { - WatchFundingConfirmedTriggered(BlockHeight(0), 0, fundingTx) - } else { - WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) - } - def localOrigin(replyTo: ActorRef): Origin.LocalHot = Origin.LocalHot(replyTo, UUID.randomUUID()) def makeCmdAdd(amount: MilliSatoshi, destination: PublicKey, currentBlockHeight: BlockHeight): (ByteVector32, CMD_ADD_HTLC) = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 5f0007e698..ddccfb0499 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -100,10 +100,11 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS import f._ bob2alice.expectMsgType[FundingSigned] bob2alice.forward(alice) - awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) + awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) + // alice doesn't watch for the funding tx to confirm alice2blockchain.expectMsgType[WatchFundingSpent] - val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] - assert(watchConfirmed.minDepth == 0) // zeroconf + alice2blockchain.expectMsgType[WatchFundingLost] + alice2blockchain.expectNoMessage(100 millis) aliceOrigin.expectMsgType[ChannelOpenResponse.ChannelOpened] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index 22834b04ab..c43a8f7ea5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.payment.relay.Relayer.RelayFees import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{MilliSatoshiLong, TestConstants, TestKitBaseClass} +import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -68,14 +68,17 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu bob2alice.forward(alice) alice2blockchain.expectMsgType[TxPublisher.SetChannelId] alice2blockchain.expectMsgType[WatchFundingSpent] - val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] bob2blockchain.expectMsgType[TxPublisher.SetChannelId] bob2blockchain.expectMsgType[WatchFundingSpent] - val bobWatchFundingConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] - awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) - val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - alice ! fundingConfirmedEvent(aliceWatchFundingConfirmed, fundingTx) - bob ! fundingConfirmedEvent(bobWatchFundingConfirmed, fundingTx) + if (!test.tags.contains(ChannelStateTestsTags.ZeroConf)) { + alice2blockchain.expectMsgType[WatchFundingConfirmed] + bob2blockchain.expectMsgType[WatchFundingConfirmed] + awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) + awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED) + val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get + alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx) + } alice2blockchain.expectMsgType[WatchFundingLost] bob2blockchain.expectMsgType[WatchFundingLost] alice2bob.expectMsgType[ChannelReady] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index f71bbab9aa..340f7ca24f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -17,9 +17,7 @@ package fr.acinq.eclair.channel.states.c import akka.testkit.{TestFSMRef, TestProbe} -import com.softwaremill.quicklens.ModifyPimp import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong, Script, Transaction} -import fr.acinq.eclair.Features.ZeroConf import fr.acinq.eclair.blockchain.CurrentBlockHeight import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ @@ -28,7 +26,7 @@ import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITC import fr.acinq.eclair.channel.publish.TxPublisher import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags} import fr.acinq.eclair.transactions.Scripts.multiSig2of2 -import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, ChannelReadyTlv, Error, FundingCreated, FundingSigned, Init, OpenChannel} +import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, Error, FundingCreated, FundingSigned, Init, OpenChannel, TlvStream} import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, TimestampSecond, randomKey} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -82,22 +80,21 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF } } - test("recv ChannelReady (funder)") { f => + test("recv ChannelReady (funder, with remote alias)") { f => import f._ // make bob send a ChannelReady msg val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) - val channelReady = bob2alice.expectMsgType[ChannelReady] - assert(channelReady.alias_opt.isDefined) + val bobChannelReady = bob2alice.expectMsgType[ChannelReady] + assert(bobChannelReady.alias_opt.isDefined) // test starts here bob2alice.forward(alice) - // alice keeps bob's channel_ready for later processing - eventually { - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReady)) - } - // and alice also creates a zero-conf watch - val aliceWatchFundingConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] - assert(aliceWatchFundingConfirmed.minDepth == 0) + // alice stops waiting for confirmations since bob is accepting the channel + alice2blockchain.expectMsgType[WatchFundingLost] + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + val aliceChannelReady = alice2bob.expectMsgType[ChannelReady] + assert(aliceChannelReady.alias_opt.nonEmpty) + awaitAssert(assert(alice.stateName == NORMAL)) } test("recv ChannelReady (funder, no remote alias)") { f => @@ -105,8 +102,7 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF // make bob send a ChannelReady msg val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) - val channelReadyNoAlias = bob2alice.expectMsgType[ChannelReady] - .modify(_.tlvStream.records).using(_.filter { case _: ChannelReadyTlv.ShortChannelIdTlv => false; case _ => true }) + val channelReadyNoAlias = bob2alice.expectMsgType[ChannelReady].copy(tlvStream = TlvStream.empty) // test starts here bob2alice.forward(alice, channelReadyNoAlias) // alice keeps bob's channel_ready for later processing @@ -116,23 +112,6 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF alice2blockchain.expectNoMessage() } - test("recv ChannelReady (funder, zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => - import f._ - // make bob send a ChannelReady msg - val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - bob ! WatchFundingConfirmedTriggered(BlockHeight(42000), 42, fundingTx) - val channelReady = bob2alice.expectMsgType[ChannelReady] - assert(channelReady.alias_opt.isDefined) - // test starts here - bob2alice.forward(alice) - // alice keeps bob's channel_ready for later processing - eventually { - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(channelReady)) - } - // and alice also doesn't creates a zero-conf watch because the channel is already zero-conf - alice2blockchain.expectNoMessage() - } - test("recv ChannelReady (fundee)") { f => import f._ // make alice send a ChannelReady msg @@ -165,17 +144,6 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF assert(channelReady.alias_opt.isDefined) } - test("recv WatchFundingConfirmedTriggered (funder, early ChannelReady from fundee)") { f => - import f._ - // the channel isn't zero-conf, but bob (fundee) has sent an early channel_ready so alice created a zero-conf watch - assert(!alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.channelFeatures.hasFeature(ZeroConf)) - val fundingTx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].fundingTx.get - // the zero-conf watch confirms instantly - alice ! WatchFundingConfirmedTriggered(BlockHeight(0), 0, fundingTx) - awaitCond(alice.stateName == WAIT_FOR_CHANNEL_READY) - assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) - } - test("recv WatchFundingConfirmedTriggered (fundee)") { f => import f._ // we create a new listener that registers after alice has published the funding tx diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 7abdf93f85..cd0854c31a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3468,7 +3468,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we create a new listener that registers after alice has published the funding tx val listener = TestProbe() alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) - // zero-conf channel : the funding tx isn't confirmed + // zero-conf channel: the funding tx isn't confirmed assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42000), 42, null) val realShortChannelId = RealShortChannelId(BlockHeight(42000), 42, 0) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala index 8c93c02966..ff76462c35 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3Spec.scala @@ -257,7 +257,7 @@ class ChannelCodecs3Spec extends AnyFunSuite { assert(data.shortIds.localAlias == ShortChannelId(123456789L)) assert(data.shortIds.real == RealScidStatus.Temporary(RealShortChannelId(123456789L))) val binMigrated = channelDataCodec.encode(data).require.toHex - assert(binMigrated.startsWith("0010")) // NB: 01 -> 10 + assert(binMigrated.startsWith("000a")) // NB: 01 -> 0a } { From 189d1019f18f7b89e222f58fe9c0880b2bbe5f27 Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:06:28 +0200 Subject: [PATCH 48/55] Improve tests in #2224 (#2314) --- .../eclair/wire/protocol/CommonCodecs.scala | 6 +- .../scala/fr/acinq/eclair/TestUtils.scala | 8 ++ .../fr/acinq/eclair/channel/RestoreSpec.scala | 10 +-- .../c/WaitForChannelReadyStateSpec.scala | 36 +++++--- .../channel/states/e/NormalStateSpec.scala | 46 ++++++---- .../channel/states/e/OfflineStateSpec.scala | 8 +- .../basic/ZeroConfAliasIntegrationSpec.scala | 89 +++++++++++-------- .../basic/fixtures/MinimalNodeFixture.scala | 24 ++--- .../router/ChannelRouterIntegrationSpec.scala | 31 ++++--- .../protocol/LightningMessageCodecsSpec.scala | 5 +- .../acinq/eclair/api/handlers/Channel.scala | 7 +- 11 files changed, 151 insertions(+), 119 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala index 4b3e402f54..314fbaf4bf 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Satoshi, Transa import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, RealScidStatus, ShortIds} import fr.acinq.eclair.crypto.Mac32 -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, UnspecifiedShortChannelId, Alias, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, channel} +import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshi, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, UnspecifiedShortChannelId} import org.apache.commons.codec.binary.Base32 import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ @@ -138,8 +138,8 @@ object CommonCodecs { val realShortChannelIdStatus: Codec[RealScidStatus] = discriminated[RealScidStatus].by(uint8) .typecase(0, provide(RealScidStatus.Unknown)) - .typecase(1, realshortchannelid.as[channel.RealScidStatus.Temporary]) - .typecase(2, realshortchannelid.as[channel.RealScidStatus.Final]) + .typecase(1, realshortchannelid.as[RealScidStatus.Temporary]) + .typecase(2, realshortchannelid.as[RealScidStatus.Final]) val shortids: Codec[ShortIds] = ( ("real" | realShortChannelIdStatus) :: diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala index 82825f9cee..a6482c1646 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala @@ -23,11 +23,13 @@ import fr.acinq.eclair.channel.fsm.Channel import fr.acinq.eclair.io.Peer import fr.acinq.eclair.wire.protocol.LightningMessage import org.scalatest.concurrent.Eventually.eventually +import org.scalatest.concurrent.PatienceConfiguration import java.io.File import java.net.ServerSocket import java.nio.file.Files import java.util.UUID +import scala.concurrent.duration.{DurationInt, FiniteDuration} object TestUtils { @@ -104,7 +106,13 @@ object TestUtils { eventStream.publish(DummyEvent()) assert(listener.msgAvailable) } + } + def waitFor(duration: FiniteDuration): Unit = { + val now = TimestampMilli.now() + eventually(PatienceConfiguration.Timeout(duration * 2), PatienceConfiguration.Interval(50 millis)) { + assert(TimestampMilli.now() - now > duration) + } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala index 65f4343ce0..256d4b90df 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/RestoreSpec.scala @@ -213,11 +213,11 @@ class RestoreSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with Chan .modify(_.relayParams.privateChannelFees.feeBase).setTo(765 msat), Alice.nodeParams .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345), - // Alice.nodeParams - // .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), - // Alice.nodeParams - // .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345) - // .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), + Alice.nodeParams + .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), + Alice.nodeParams + .modify(_.relayParams.privateChannelFees.feeProportionalMillionths).setTo(2345) + .modify(_.channelConf.expiryDelta).setTo(CltvExpiryDelta(147)), ) foreach { newConfig => val newAlice: TestFSMRef[ChannelState, ChannelData, Channel] = TestFSMRef(new Channel(newConfig, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, alice2relayer.ref, FakeTxPublisherFactory(alice2blockchain)), alicePeer.ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala index c43a8f7ea5..167c75563d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForChannelReadyStateSpec.scala @@ -92,14 +92,17 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu import f._ // we have a real scid at this stage, because this isn't a zero-conf channel assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val initialChannelUpdate = alice2bob.expectMsgType[ChannelUpdate] + assert(initialChannelUpdate == alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate) // we have a real scid, but the channel is not announced so alice uses bob's alias assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + bob2alice.expectNoMessage(100 millis) awaitCond(alice.stateName == NORMAL) } @@ -110,12 +113,14 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu val channelReady = bob2alice.expectMsgType[ChannelReady] val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val initialChannelUpdate = alice2bob.expectMsgType[ChannelUpdate] + assert(initialChannelUpdate == alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate) // the channel is not announced but bob didn't send an alias so we use the real scid assert(initialChannelUpdate.shortChannelId == realScid) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + bob2alice.expectNoMessage(100 millis) awaitCond(alice.stateName == NORMAL) } @@ -123,14 +128,17 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu import f._ // zero-conf channel: we don't have a real scid assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val initialChannelUpdate = alice2bob.expectMsgType[ChannelUpdate] + assert(initialChannelUpdate == alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate) // the channel is not announced so alice uses bob's alias (we have a no real scid anyway) assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + bob2alice.expectNoMessage(100 millis) awaitCond(alice.stateName == NORMAL) } @@ -138,6 +146,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu import f._ // zero-conf channel: we don't have a real scid assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] val channelReadyNoAlias = channelReady.modify(_.tlvStream.records).using(_.filterNot(_.isInstanceOf[ChannelReadyTlv.ShortChannelIdTlv])) bob2alice.forward(alice, channelReadyNoAlias) @@ -153,14 +162,18 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu // we have a real scid at this stage, because this isn't a zero-conf channel assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.channelFlags.announceChannel) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real.isInstanceOf[RealScidStatus.Temporary]) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].commitments.channelFlags.announceChannel) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val initialChannelUpdate = alice2bob.expectMsgType[ChannelUpdate] + assert(initialChannelUpdate == alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate) // we have a real scid, but it is not the final one (less than 6 confirmations) so alice uses bob's alias assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + bob2alice.expectNoMessage(100 millis) awaitCond(alice.stateName == NORMAL) } @@ -168,14 +181,17 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu import f._ // zero-conf channel: we don't have a real scid assert(alice.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) + assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_CHANNEL_READY].shortIds.real == RealScidStatus.Unknown) val channelReady = bob2alice.expectMsgType[ChannelReady] bob2alice.forward(alice) - val initialChannelUpdate = alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate + val initialChannelUpdate = alice2bob.expectMsgType[ChannelUpdate] + assert(initialChannelUpdate == alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate) // the channel is not announced, so alice uses bob's alias (we have a no real scid anyway) assert(initialChannelUpdate.shortChannelId == channelReady.alias_opt.get) assert(initialChannelUpdate.feeBaseMsat == relayFees.feeBase) assert(initialChannelUpdate.feeProportionalMillionths == relayFees.feeProportionalMillionths) - bob2alice.expectNoMessage(200 millis) + alice2blockchain.expectMsgType[WatchFundingDeeplyBuried] + bob2alice.expectNoMessage(100 millis) awaitCond(alice.stateName == NORMAL) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index cd0854c31a..8ff235ff85 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3403,6 +3403,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with test("recv WatchFundingDeeplyBuriedTriggered (public channel)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) @@ -3410,16 +3411,17 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) - // public channel: we prefer the real scid alias and it hasn't changed, so we don't send a new channel_update - alice2bob.expectNoMessage(1 second) - // we don't re-publish the same channel_update if there was no change - channelUpdateListener.expectNoMessage(1 second) + // public channel: alice will update the channel update to use the real scid when she receives her peer's announcement_signatures + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) } 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 assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias // funding tx coordinates (unknown before) val (blockHeight, txIndex) = (BlockHeight(400000), 42) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) @@ -3428,13 +3430,16 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with assert(annSigs.shortChannelId == realShortChannelId) // alice updates her internal state awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) - // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures - channelUpdateListener.expectNoMessage(1 second) + // public channel: alice will update the channel update to use the real scid when she receives her peer's announcement_signatures + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) } test("recv WatchFundingDeeplyBuriedTriggered (public channel, short channel id changed)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => import f._ val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) // new funding tx coordinates (there was a reorg) @@ -3445,22 +3450,25 @@ 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)) - // we don't send out a new channel_update with the real scid just yet, we wait for the peer's announcement_signatures - channelUpdateListener.expectNoMessage(1 second) + // public channel: alice will update the channel update to use the real scid when she receives her peer's announcement_signatures + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) } test("recv WatchFundingDeeplyBuriedTriggered (private channel)") { f => import f._ val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) // update data with real short channel id awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one - alice2bob.expectNoMessage() - // we don't re-publish the same channel_update if there was no change - channelUpdateListener.expectNoMessage(1 second) + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) } test("recv WatchFundingDeeplyBuriedTriggered (private channel, zero-conf)", Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f => @@ -3470,21 +3478,23 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed]) // zero-conf channel: the funding tx isn't confirmed assert(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Unknown) + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias 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 prefer the remote alias, so there is no change in the channel_update, and we don't send a new one - alice2bob.expectNoMessage() + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) // this is the first time we know the funding tx has been confirmed listener.expectMsgType[TransactionConfirmed] - // we don't re-publish the same channel_update if there was no change - channelUpdateListener.expectNoMessage(1 second) } test("recv WatchFundingDeeplyBuriedTriggered (private channel, short channel id changed)") { f => import f._ val realShortChannelId = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid + val bobAlias = bob.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias // existing funding tx coordinates val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId) // new funding tx coordinates (there was a reorg) @@ -3494,9 +3504,9 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // update data with real short channel id awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId)) // private channel: we prefer the remote alias, so there is no change in the channel_update, and we don't send a new one - alice2bob.expectNoMessage() - // we don't re-publish the same channel_update if there was no change - channelUpdateListener.expectNoMessage(1 second) + alice2bob.expectNoMessage(100 millis) + channelUpdateListener.expectNoMessage(100 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == bobAlias) } test("recv AnnouncementSignatures", Tag(ChannelStateTestsTags.ChannelsPublic)) { f => @@ -3513,7 +3523,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import initialState.commitments.{localParams, remoteParams} val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) // actual test starts here - bob2alice.forward(alice) + bob2alice.forward(alice, annSigsB) awaitAssert { val normal = alice.stateData.asInstanceOf[DATA_NORMAL] assert(normal.shortIds.real == RealScidStatus.Final(annSigsA.shortChannelId) && normal.channelAnnouncement.contains(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index d66d57c927..6a66d8a789 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -32,7 +32,7 @@ import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishTx} import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.transactions.Transactions.HtlcSuccessTx import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestFeeEstimator, TestKitBaseClass, randomBytes32} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestFeeEstimator, TestKitBaseClass, TestUtils, TimestampMilli, randomBytes32} import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.{Outcome, Tag} @@ -737,7 +737,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we simulate a disconnection / reconnection disconnect(alice, bob) // we wait 1s so the new channel_update doesn't have the same timestamp - Thread.sleep(1_000) + TestUtils.waitFor(1 second) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] @@ -762,7 +762,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we simulate a disconnection / reconnection disconnect(alice, bob) // we wait 1s so the new channel_update doesn't have the same timestamp - Thread.sleep(1_000) + TestUtils.waitFor(1 second) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] @@ -785,7 +785,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // we get disconnected again disconnect(alice, bob) // we wait 1s so the new channel_update doesn't have the same timestamp - Thread.sleep(1_000) + TestUtils.waitFor(1 second) reconnect(alice, bob, alice2bob, bob2alice) alice2bob.expectMsgType[ChannelReestablish] bob2alice.expectMsgType[ChannelReestablish] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index ebc2823a4c..fe8759f4af 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -8,8 +8,8 @@ import fr.acinq.eclair.channel.{DATA_NORMAL, RealScidStatus} import fr.acinq.eclair.integration.basic.fixtures.ThreeNodesFixture import fr.acinq.eclair.payment.PaymentSent import fr.acinq.eclair.testutils.FixtureSpec -import fr.acinq.eclair.wire.protocol.{ChannelReady, ChannelReadyTlv} import fr.acinq.eclair.{MilliSatoshiLong, RealShortChannelId} +import org.scalatest.OptionValues.convertOptionToValuable import org.scalatest.concurrent.IntegrationPatience import org.scalatest.{Tag, TestData} import scodec.bits.HexStringSyntax @@ -76,11 +76,10 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcScidAlias: Boolean, paymentWorksWithoutHint: Boolean, paymentWorksWithHint_opt: Option[Boolean], - paymentWorksWithRealScidHint_opt: Option[Boolean], - ): Unit = { + paymentWorksWithRealScidHint_opt: Option[Boolean]): Unit = { import f._ - val (channelId_ab, channelId_bc) = createChannels(f)(deepConfirm = deepConfirm) + val (_, channelId_bc) = createChannels(f)(deepConfirm = deepConfirm) eventually { assert(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].commitments.channelFeatures.features.contains(ZeroConf) == bcZeroConf) @@ -96,7 +95,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience } if (bcPublic && deepConfirm) { - // if channel b-c is public, we wait for alice to learn about it + // if channel bob-carol is public, we wait for alice to learn about it eventually { val data = getRouterData(alice) assert(data.channels.size == 2) @@ -112,68 +111,79 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience } } - if (paymentWorksWithHint_opt.contains(true)) { - sendPaymentAliceToCarol(f, useHint = true) - } else if (paymentWorksWithHint_opt.contains(false)) { - intercept[AssertionError] { + paymentWorksWithHint_opt match { + case Some(true) => sendPaymentAliceToCarol(f, useHint = true) + case Some(false) => intercept[AssertionError] { sendPaymentAliceToCarol(f, useHint = true) } - } else { - // skipped + case None => // skipped } - if (paymentWorksWithRealScidHint_opt.contains(true)) { - // if alice uses the real scid instead of the b-c alias, it still works - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.get)) - } else if (paymentWorksWithRealScidHint_opt.contains(false)) { - intercept[AssertionError] { - sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.get)) + paymentWorksWithRealScidHint_opt match { + // if alice uses the real scid instead of the bob-carol alias, it still works + case Some(true) => sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.value)) + case Some(false) => intercept[AssertionError] { + sendPaymentAliceToCarol(f, useHint = true, overrideHintScid_opt = Some(getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].shortIds.real.toOption.value)) } - } else { - // skipped + case None => // skipped } } test("a->b->c (b-c private)") { f => + import f._ + internalTest(f, deepConfirm = true, bcPublic = false, bcZeroConf = false, bcScidAlias = false, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works - paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the b-c alias, it still works + paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the bob-carol alias, it still works ) - //TODO - // NB: the default hints use bob's alias, even id scid alias isn't enabled, because eclair always sends and understands aliases - //assert(getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.get.shortChannelId == - // getChannelData(bob, channelId_bc).asInstanceOf[DATA_NORMAL].localAlias) - + // Bob and Carol understand scid aliases even when the feature isn't enabled. + val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value + val bobAlias = getRouterData(bob).privateChannels.values.head.shortIds.localAlias + assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => + import f._ + internalTest(f, deepConfirm = true, bcPublic = false, bcZeroConf = false, bcScidAlias = true, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works - paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the bob-carol alias, it doesn't work due to option_scid_alias ) + + // Carol must use Bob's scid alias in her routing hints. + val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value + val bobAlias = getRouterData(bob).privateChannels.values.head.shortIds.localAlias + assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => + import f._ + internalTest(f, deepConfirm = false, bcPublic = false, bcZeroConf = true, bcScidAlias = false, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works - paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + paymentWorksWithRealScidHint_opt = None // there is no real scid for bob-carol yet ) + + // Carol uses Bob's scid alias until the channel confirms. + val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value + val bobAlias = getRouterData(bob).privateChannels.values.collectFirst { case pc if pc.remoteNodeId == carol.nodeParams.nodeId => pc.shortIds.localAlias }.value + assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c zero-conf deeply confirmed private)", Tag(ZeroConfBobCarol)) { f => @@ -182,13 +192,14 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = true, bcScidAlias = false, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works - paymentWorksWithRealScidHint_opt = None // skipped, see below - // TODO This actually doesn't work, because the ChannelRelayer relies on the the LocalChannelUpdate event to maintain - // TODO its scid resolution map, and the channel doesn't emit a new one when a real scid is assigned, because we use the - // TODO remote alias for the channel_update, not the real scid. So the channel_update remains the same. We used to - // TODO have the ChannelRelayer also listen to ShortChannelIdAssigned event, but it's doesn't seem worth it here. + // TODO: we should be able to send payments with the real scid in the routing hint, but this currently doesn't work, + // because the ChannelRelayer relies on the the LocalChannelUpdate event to maintain its scid resolution map, and + // the channel doesn't emit a new one when a real scid is assigned, because we use the remote alias for the + // channel_update, not the real scid. So the channel_update remains the same. We used to have the ChannelRelayer + // also listen to ShortChannelIdAssigned event, but it's doesn't seem worth it here. + paymentWorksWithRealScidHint_opt = None ) } @@ -198,7 +209,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = false, bcZeroConf = true, bcScidAlias = true, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias ) @@ -210,9 +221,9 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcPublic = true, bcZeroConf = true, bcScidAlias = false, - paymentWorksWithoutHint = false, // alice can't find a route to carol because b-c isn't announced + paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced yet paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works - paymentWorksWithRealScidHint_opt = None // there is no real scid for b-c yet + paymentWorksWithRealScidHint_opt = None // there is no real scid for bob-carol yet ) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 4f4164552a..52a19c3bbf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -24,7 +24,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator import fr.acinq.eclair.payment.{Bolt11Invoice, PaymentSent} import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.IPAddress -import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TestBitcoinCoreClient, TestDatabases, TestFeeEstimator} +import fr.acinq.eclair.{BlockHeight, MilliSatoshi, MilliSatoshiLong, NodeParams, RealShortChannelId, TestBitcoinCoreClient, TestDatabases, TestFeeEstimator} import org.scalatest.Assertions import org.scalatest.concurrent.Eventually.eventually @@ -178,17 +178,10 @@ object MinimalNodeFixture extends Assertions { val data1After = getChannelData(node1, channelId).asInstanceOf[DATA_NORMAL] val data2After = getChannelData(node2, channelId).asInstanceOf[DATA_NORMAL] - - if ((blockHeight, txIndex) == (BlockHeight(0), 0)) { - // special case for zero-conf channels, the watcher answers with a special 0/0 coordinates when the funding tx - // hasn't been confirmed yet and doesn't have a real scid - None - } else { - val realScid1 = data1After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] - val realScid2 = data2After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] - assert(realScid1 == realScid2) - Some(realScid1) - } + val realScid1 = data1After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] + val realScid2 = data2After.shortIds.real.asInstanceOf[RealScidStatus.Temporary] + assert(realScid1 == realScid2) + Some(realScid1) } def confirmChannelDeep(node1: MinimalNodeFixture, node2: MinimalNodeFixture, channelId: ByteVector32, blockHeight: BlockHeight, txIndex: Int)(implicit system: ActorSystem): RealScidStatus.Final = { @@ -257,12 +250,7 @@ object MinimalNodeFixture extends Assertions { */ def watcherAutopilot(knownFundingTxs: () => Iterable[Transaction], deepConfirm: Boolean = true): TestActor.AutoPilot = (_, msg) => msg match { case watch: ZmqWatcher.WatchFundingConfirmed => - val realScid = if (watch.minDepth == 0) { - // special case for zero-conf channels, this is what the real watcher does - RealShortChannelId(BlockHeight(0), 0, 0) - } else { - deterministicShortId(watch.txId) - } + val realScid = deterministicShortId(watch.txId) val fundingTx = knownFundingTxs().find(_.txid == watch.txId) .getOrElse(throw new RuntimeException(s"unknown fundingTxId=${watch.txId}, known=${knownFundingTxs().map(_.txid).mkString(",")}")) watch.replyTo ! ZmqWatcher.WatchFundingConfirmedTriggered(realScid.blockHeight, txIndex(realScid), fundingTx) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala index 55c5f8b522..ebe90ec8f5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRouterIntegrationSpec.scala @@ -45,6 +45,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu private def internalTest(f: FixtureParam): Unit = { import f._ + val (aliceNodeId, bobNodeId) = (channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId) reachNormal(channels, testTags, interceptChannelUpdates = false) // the router learns about the local, still unannounced, channel @@ -68,7 +69,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(bobChannelUpdate1.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.localAlias) // channel_updates are handled by the peer connection and sent to the router val peerConnection = TestProbe() - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate1) + router ! PeerRoutingMessage(peerConnection.ref, bobNodeId, bobChannelUpdate1) // router processes bob's channel_update and now knows both channel updates awaitCond { @@ -79,7 +80,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(router.stateData.rebroadcast == Rebroadcast(Map.empty, Map.empty, Map.empty)) // router graph contains a single channel - assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId)) assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) if (testTags.contains(ChannelStateTestsTags.ChannelsPublic)) { @@ -110,21 +111,24 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu val aliceChannelUpdate2 = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate val bobChannelUpdate2 = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate // this time, they use the real scid - assert(aliceChannelUpdate2.shortChannelId == channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) - assert(bobChannelUpdate2.shortChannelId == channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get.shortChannelId) + val aliceAnn = channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get + val bobAnn = channels.bob.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get + assert(aliceAnn == bobAnn) + assert(aliceChannelUpdate2.shortChannelId == aliceAnn.shortChannelId) + assert(bobChannelUpdate2.shortChannelId == bobAnn.shortChannelId) // the router has already processed the new local channel update from alice which uses the real scid, and keeps bob's previous channel update assert(publicChannel.update_1_opt.contains(aliceChannelUpdate2) && publicChannel.update_2_opt.contains(bobChannelUpdate1)) - // the router prepares to rebroadcast the channel announcement, the local update which use the real scid, and the first node announcement + // the router prepares to rebroadcast the channel announcement, the local update which uses the real scid, and the first node announcement assert(router.stateData.rebroadcast == Rebroadcast( - channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), + channels = Map(aliceAnn -> Set[GossipOrigin](LocalGossip)), updates = Map(aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip)), nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) ) // bob's channel_update reaches the router - router ! PeerRoutingMessage(peerConnection.ref, channels.bob.underlyingActor.nodeParams.nodeId, bobChannelUpdate2) + router ! PeerRoutingMessage(peerConnection.ref, bobNodeId, bobChannelUpdate2) // router processes bob's channel_update and now knows both channel updates with real scids awaitCond { @@ -133,20 +137,20 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu // router is now ready to rebroadcast both channel updates assert(router.stateData.rebroadcast == Rebroadcast( - channels = Map(channels.alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.get -> Set[GossipOrigin](LocalGossip)), + channels = Map(aliceAnn -> Set[GossipOrigin](LocalGossip)), updates = Map( aliceChannelUpdate2 -> Set[GossipOrigin](LocalGossip), - bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, nodeId = channels.bob.underlyingActor.nodeParams.nodeId))), + bobChannelUpdate2 -> Set[GossipOrigin](RemoteGossip(peerConnection.ref, bobNodeId)) + ), nodes = Map(router.stateData.nodes.values.head -> Set[GossipOrigin](LocalGossip))) ) // router graph contains a single channel - assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId)) assert(router.stateData.graphWithBalances.graph.edgeSet().size == 2) assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate2, publicChannel), GraphEdge(bobChannelUpdate2, publicChannel))) } else { // this is a private channel - // funding tx reaches 6 blocks, no announcements are exchanged because the channel is private channels.alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) channels.bob ! WatchFundingDeeplyBuriedTriggered(BlockHeight(400000), 42, null) @@ -156,7 +160,7 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu channels.bob2alice.expectNoMessage(100 millis) // router graph contains a single channel - assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(channels.alice.underlyingActor.nodeParams.nodeId, channels.bob.underlyingActor.nodeParams.nodeId)) + assert(router.stateData.graphWithBalances.graph.vertexSet() == Set(aliceNodeId, bobNodeId)) assert(router.stateData.graphWithBalances.graph.edgeSet().toSet == Set(GraphEdge(aliceChannelUpdate1, privateChannel), GraphEdge(bobChannelUpdate1, privateChannel))) } // channel closes @@ -177,7 +181,8 @@ class ChannelRouterIntegrationSpec extends TestKitBaseClass with FixtureAnyFunSu assert(router.stateData.privateChannels == Map.empty) assert(router.stateData.scid2PrivateChannels == Map.empty) assert(router.stateData.graphWithBalances.graph.edgeSet().isEmpty) - //assert(router.stateData.graphWithBalances.graph.vertexSet().isEmpty) // TODO: bug! + // TODO: we're not currently pruning nodes without channels from the graph, but we should! + // assert(router.stateData.graphWithBalances.graph.vertexSet().isEmpty) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 44d3e5308a..064dfbf758 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -21,8 +21,6 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64, SatoshiLong, ScriptWitness, Transaction} import fr.acinq.eclair.FeatureSupport.Optional import fr.acinq.eclair.Features.DataLossProtect -import fr.acinq.eclair.RealShortChannelId -import fr.acinq.eclair.RealShortChannelId import fr.acinq.eclair._ import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.{ChannelFlags, ChannelTypes} @@ -378,7 +376,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite { TlvStream(QueryChannelRangeTlv.QueryFlags(QueryChannelRangeTlv.QueryFlags.WANT_ALL) :: Nil, unknownTlv :: Nil)), ReplyChannelRange(Block.RegtestGenesisBlock.blockId, BlockHeight(100000), 1500, 1, - EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), + EncodedShortChannelIds(EncodingType.UNCOMPRESSED, List(RealShortChannelId(142), RealShortChannelId(15465), RealShortChannelId(4564676))), TlvStream( EncodedTimestamps(EncodingType.UNCOMPRESSED, List(Timestamps(1 unixsec, 1 unixsec), Timestamps(2 unixsec, 2 unixsec), Timestamps(3 unixsec, 3 unixsec))) :: EncodedChecksums(List(Checksums(1, 1), Checksums(2, 2), Checksums(3, 3))) :: Nil, unknownTlv :: Nil) @@ -575,5 +573,4 @@ class LightningMessageCodecsSpec extends AnyFunSuite { } } - } diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala index 0948988816..f7a04b0263 100644 --- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala +++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala @@ -40,13 +40,11 @@ trait Channel { ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = false), ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true) - ).map(ct => ct.toString -> ct) // we use the toString method as name in the api - .toMap + ).map(ct => ct.toString -> ct).toMap // we use the toString method as name in the api val open: Route = postRequest("open") { implicit t => formFields(nodeIdFormParam, "fundingSatoshis".as[Satoshi], "pushMsat".as[MilliSatoshi].?, "channelType".?, "fundingFeerateSatByte".as[FeeratePerByte].?, "announceChannel".as[Boolean].?, "openTimeoutSeconds".as[Timeout].?) { (nodeId, fundingSatoshis, pushMsat, channelTypeName_opt, fundingFeerateSatByte, announceChannel_opt, openTimeout_opt) => - val (channelTypeOk, channelType_opt) = channelTypeName_opt match { case Some(channelTypeName) => supportedChannelTypes.get(channelTypeName) match { case Some(channelType) => (true, Some(channelType)) @@ -54,9 +52,8 @@ trait Channel { } case None => (true, None) } - if (!channelTypeOk) { - reject(MalformedFormFieldRejection("channelType", s"Channel type not supported: must be ${supportedChannelTypes.keys.mkString(",")}")) + reject(MalformedFormFieldRejection("channelType", s"Channel type not supported: must be one of ${supportedChannelTypes.keys.mkString(",")}")) } else { complete { eclairApi.open(nodeId, fundingSatoshis, pushMsat, channelType_opt, fundingFeerateSatByte, announceChannel_opt, openTimeout_opt) From b5f137f09d58b1917e6391259d3f3722d4ec5959 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 14 Jun 2022 16:17:54 +0200 Subject: [PATCH 49/55] implement waitFor with Thread.sleep --- eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala index a6482c1646..c8012d440b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestUtils.scala @@ -108,11 +108,6 @@ object TestUtils { } } - def waitFor(duration: FiniteDuration): Unit = { - val now = TimestampMilli.now() - eventually(PatienceConfiguration.Timeout(duration * 2), PatienceConfiguration.Interval(50 millis)) { - assert(TimestampMilli.now() - now > duration) - } - } + def waitFor(duration: FiniteDuration): Unit = Thread.sleep(duration.toMillis) } From dcc523666d5b6fa384dd101e32d52fcd000288da Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 14 Jun 2022 17:24:50 +0200 Subject: [PATCH 50/55] first draft of release notes --- docs/release-notes/eclair-vnext.md | 59 ++++++++++++++++++- .../eclair/channel/ChannelFeatures.scala | 2 +- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index 31392c7d38..a7f172c385 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -4,7 +4,64 @@ ## Major changes -Dropped support for version 2 of Tor protocol. That means +### Add support for channel aliases and zeroconf channels + +:information_source: Those features are only supported for channels of type `AnchorOutputsZeroFeeHtlcTx`, which is the +newest channel type and the one enabled by default. If you are opening a channel with a node that doesn't run Eclair, +make sure they support `option_anchors_zero_fee_htlc_tx`. + +#### Channel aliases + +Channel aliases offer a way to use arbitrary channel identifiers for routing. This feature improves privacy by not +leaking the funding transaction of the channel during payments. + +This feature is enabled by default, but your peer has to support it too. + +#### Zeroconf channels + +Zeroconf channels make it possible to use a newly created channel before the funding tx is confirmed on the blockchain. + +:warning: Zeroconf requires the fundee to trust the funder. For this reason it is disabled by default, and you should +only enable it on a peer-by-peer basis. + +##### Enabling through features + +Below is how to enable zeroconf with a given peer in `eclair.conf`. With this config, your node will _accept_ zeroconf channels from node `03864e...`. + +```eclair.conf +override-init-features = [ + { + nodeid = "03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f", + features = { + // these features need to be enabled + var_onion_optin = mandatory + payment_secret = mandatory + option_channel_type = optional + // dependencies of zeroconf + option_static_remotekey = optional + option_anchors_zero_fee_htlc_tx = optional + option_scid_alias = optional + // enable zeroconf + option_zeroconf = optional + } + } +] +``` + +Note that, as funder, Eclair will happily use an unconfirmed channel if the peer sends an early `channel_ready`, even if the `option_zeroconf` feature isn't enabled, as long as the peer provides a channel alias. + +##### Enabling through channel type + +You can enable `option_scid_alias` and `option_zeroconf` features by requesting them in the channel type, even if those options aren't enabled in your features. + +Below is how you would request a zeroconf channel thhrough the command-line interface: +```shell +$ ./eclair-cli open --nodeId=03864e... --fundingSatoshis=100000 --channelType=anchor_outputs_zero_fee_htlc_tx+zeroconf +``` + +### Remove support for Tor v2 + +Dropped support for version 2 of Tor protocol. That means: - Eclair can't open control connection to Tor daemon version 0.3.3.5 and earlier anymore - Eclair can't create hidden services for Tor protocol v2 with newer versions of Tor daemon diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala index 77b1b15561..7bd79bb98e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala @@ -115,7 +115,7 @@ object ChannelTypes { ).flatten override def paysDirectlyToWallet: Boolean = false override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat - override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zero_conf" else ""}" + override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}" } case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType { override def features: Set[InitFeature] = featureBits.activated.keySet From 02bfd0f12cf3f2a96479139e82531ef024ecd3d0 Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 14 Jun 2022 18:11:49 +0200 Subject: [PATCH 51/55] test activation of zeroconf feature --- .../basic/ZeroConfActivationSpec.scala | 83 +++++++++++++++++++ .../basic/fixtures/MinimalNodeFixture.scala | 4 +- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala new file mode 100644 index 0000000000..a9aaabe204 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala @@ -0,0 +1,83 @@ +package fr.acinq.eclair.integration.basic + +import com.softwaremill.quicklens.ModifyPimp +import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong} +import fr.acinq.eclair.FeatureSupport.Optional +import fr.acinq.eclair.Features.ZeroConf +import fr.acinq.eclair.channel.ChannelTypes.AnchorOutputsZeroFeeHtlcTx +import fr.acinq.eclair.channel.PersistentChannelData +import fr.acinq.eclair.integration.basic.fixtures.TwoNodesFixture +import fr.acinq.eclair.testutils.FixtureSpec +import org.scalatest.concurrent.IntegrationPatience +import org.scalatest.{Tag, TestData} +import scodec.bits.HexStringSyntax + +/** + * Test the activation of zero-conf option, via features or channel type. + */ +class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience { + + type FixtureParam = TwoNodesFixture + + val ZeroConfBob = "zero_conf_bob" + + import fr.acinq.eclair.integration.basic.fixtures.MinimalNodeFixture._ + + override def createFixture(testData: TestData): FixtureParam = { + // seeds have been chosen so that node ids start with 02aaaa for alice, 02bbbb for bob, etc. + val aliceParams = nodeParamsFor("alice", ByteVector32(hex"b4acd47335b25ab7b84b8c020997b12018592bb4631b868762154d77fa8b93a3")) + val bobParams = nodeParamsFor("bob", ByteVector32(hex"7620226fec887b0b2ebe76492e5a3fd3eb0e47cd3773263f6a81b59a704dc492")) + .modify(_.features.activated).using(_ - ZeroConf) // we will enable those features on demand + .modify(_.features.activated).usingIf(testData.tags.contains(ZeroConfBob))(_ + (ZeroConf -> Optional)) + TwoNodesFixture(aliceParams, bobParams) + } + + override def cleanupFixture(fixture: FixtureParam): Unit = { + fixture.cleanup() + } + + test("open a channel alice-bob (zero-conf disabled on both sides)") { f => + import f._ + + assert(!alice.nodeParams.features.activated.contains(ZeroConf)) + assert(!bob.nodeParams.features.activated.contains(ZeroConf)) + + connect(alice, bob) + val channelId = openChannel(alice, bob, 100_000 sat).channelId + + assert(!getChannelData(alice, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) + assert(!getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) + } + + test("open a channel alice-bob (zero-conf disabled on both sides, requested via channel type by alice)") { f => + import f._ + + assert(!alice.nodeParams.features.activated.contains(ZeroConf)) + assert(!bob.nodeParams.features.activated.contains(ZeroConf)) + + connect(alice, bob) + val channelType = AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true) + // bob rejects the channel + intercept { + openChannel(alice, bob, 100_000 sat, channelType_opt = Some(channelType)).channelId + } + } + + test("open a channel alice-bob (zero-conf enabled on bob, requested via channel type by alice)", Tag(ZeroConfBob)) { f => + import f._ + + assert(!alice.nodeParams.features.activated.contains(ZeroConf)) + assert(bob.nodeParams.features.activated.contains(ZeroConf)) + + connect(alice, bob) + val channelType = AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true) + val channelId = openChannel(alice, bob, 100_000 sat, channelType_opt = Some(channelType)).channelId + + assert(getChannelData(alice, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) + assert(getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) + } + + +} + + diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala index 52a19c3bbf..491a3ea271 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/fixtures/MinimalNodeFixture.scala @@ -149,9 +149,9 @@ object MinimalNodeFixture extends Assertions { sender.expectMsgType[ConnectionResult.Connected] } - def openChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, funding: Satoshi)(implicit system: ActorSystem): ChannelOpened = { + def openChannel(node1: MinimalNodeFixture, node2: MinimalNodeFixture, funding: Satoshi, channelType_opt: Option[SupportedChannelType] = None)(implicit system: ActorSystem): ChannelOpened = { val sender = TestProbe("sender") - sender.send(node1.switchboard, Peer.OpenChannel(node2.nodeParams.nodeId, funding, 0L msat, None, None, None, None)) + sender.send(node1.switchboard, Peer.OpenChannel(node2.nodeParams.nodeId, funding, 0L msat, channelType_opt, None, None, None)) sender.expectMsgType[ChannelOpened] } From a1ebd760e4a92e39ce78fa90162bafb768ba63fe Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 15 Jun 2022 10:53:37 +0200 Subject: [PATCH 52/55] also remove real scid from the map on channel down --- .../scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala index 320ead3ab2..741f508970 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelayer.scala @@ -105,7 +105,7 @@ object ChannelRelayer { case WrappedLocalChannelDown(LocalChannelDown(_, channelId, shortIds, remoteNodeId)) => context.log.debug(s"removed local channel info for channelId=$channelId localAlias=${shortIds.localAlias}") val channels1 = channels - channelId - val scid2Channels1 = scid2channels - shortIds.localAlias + val scid2Channels1 = scid2channels - shortIds.localAlias -- shortIds.real.toOption val node2channels1 = node2channels.subtractOne(remoteNodeId, channelId) apply(nodeParams, register, channels1, scid2Channels1, node2channels1) From ac60f87ee447437800d9aba7e0c9caebc04fa7af Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 15 Jun 2022 11:07:01 +0200 Subject: [PATCH 53/55] address review comments --- docs/release-notes/eclair-vnext.md | 66 ++++++++++++++----- .../basic/ZeroConfActivationSpec.scala | 6 +- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md index a7f172c385..33cdb33d0b 100644 --- a/docs/release-notes/eclair-vnext.md +++ b/docs/release-notes/eclair-vnext.md @@ -15,7 +15,7 @@ make sure they support `option_anchors_zero_fee_htlc_tx`. Channel aliases offer a way to use arbitrary channel identifiers for routing. This feature improves privacy by not leaking the funding transaction of the channel during payments. -This feature is enabled by default, but your peer has to support it too. +This feature is enabled by default, but your peer has to support it too, and it is not compatible with public channels. #### Zeroconf channels @@ -26,7 +26,8 @@ only enable it on a peer-by-peer basis. ##### Enabling through features -Below is how to enable zeroconf with a given peer in `eclair.conf`. With this config, your node will _accept_ zeroconf channels from node `03864e...`. +Below is how to enable zeroconf with a given peer in `eclair.conf`. With this config, your node will _accept_ zeroconf +channels from node `03864e...`. ```eclair.conf override-init-features = [ @@ -48,25 +49,43 @@ override-init-features = [ ] ``` -Note that, as funder, Eclair will happily use an unconfirmed channel if the peer sends an early `channel_ready`, even if the `option_zeroconf` feature isn't enabled, as long as the peer provides a channel alias. +Note that, as funder, Eclair will happily use an unconfirmed channel if the peer sends an early `channel_ready`, even if +the `option_zeroconf` feature isn't enabled, as long as the peer provides a channel alias. ##### Enabling through channel type -You can enable `option_scid_alias` and `option_zeroconf` features by requesting them in the channel type, even if those options aren't enabled in your features. +You can enable `option_scid_alias` and `option_zeroconf` features by requesting them in the channel type, even if those +options aren't enabled in your features. + +The new channel types variations are: + +- `anchor_outputs_zero_fee_htlc_tx+scid_alias` +- `anchor_outputs_zero_fee_htlc_tx+zeroconf` +- `anchor_outputs_zero_fee_htlc_tx+scid_alias+zeroconf` + +Examples using the command-line interface: + +- open a public zeroconf channel: + +```shell +$ ./eclair-cli open --nodeId=03864e... --fundingSatoshis=100000 --channelType=anchor_outputs_zero_fee_htlc_tx+zeroconf --announceChannel=true +``` + +- open a private zeroconf channel with aliases: -Below is how you would request a zeroconf channel thhrough the command-line interface: ```shell -$ ./eclair-cli open --nodeId=03864e... --fundingSatoshis=100000 --channelType=anchor_outputs_zero_fee_htlc_tx+zeroconf +$ ./eclair-cli open --nodeId=03864e... --fundingSatoshis=100000 --channelType=anchor_outputs_zero_fee_htlc_tx+scid_alias+zeroconf --announceChannel=false ``` ### Remove support for Tor v2 -Dropped support for version 2 of Tor protocol. That means: +Dropped support for version 2 of Tor protocol. That means: - Eclair can't open control connection to Tor daemon version 0.3.3.5 and earlier anymore - Eclair can't create hidden services for Tor protocol v2 with newer versions of Tor daemon -IMPORTANT: You'll need to upgrade your Tor daemon if for some reason you still use Tor v0.3.3.5 or earlier before upgrading to this release. +IMPORTANT: You'll need to upgrade your Tor daemon if for some reason you still use Tor v0.3.3.5 or earlier before +upgrading to this release. ### API changes @@ -77,25 +96,33 @@ IMPORTANT: You'll need to upgrade your Tor daemon if for some reason you still u #### Delay enforcement of new channel fees -When updating the relay fees for a channel, eclair can now continue accepting to relay payments using the old fee even if they would be rejected with the new fee. -By default, eclair will still accept the old fee for 10 minutes, you can change it by setting `eclair.relay.fees.enforcement-delay` to a different value. +When updating the relay fees for a channel, eclair can now continue accepting to relay payments using the old fee even +if they would be rejected with the new fee. +By default, eclair will still accept the old fee for 10 minutes, you can change it by +setting `eclair.relay.fees.enforcement-delay` to a different value. -If you want a specific fee update to ignore this delay, you can update the fee twice to make eclair forget about the previous fee. +If you want a specific fee update to ignore this delay, you can update the fee twice to make eclair forget about the +previous fee. #### New minimum funding setting for private channels -New settings have been added to independently control the minimum funding required to open public and private channels to your node. +New settings have been added to independently control the minimum funding required to open public and private channels +to your node. -The `eclair.channel.min-funding-satoshis` setting has been deprecated and replaced with the following two new settings and defaults: +The `eclair.channel.min-funding-satoshis` setting has been deprecated and replaced with the following two new settings +and defaults: * `eclair.channel.min-public-funding-satoshis = 100000` * `eclair.channel.min-private-funding-satoshis = 100000` -If your configuration file changes `eclair.channel.min-funding-satoshis` then you should replace it with both of these new settings. +If your configuration file changes `eclair.channel.min-funding-satoshis` then you should replace it with both of these +new settings. #### Expired incoming invoices now purged if unpaid -Expired incoming invoices that are unpaid will be searched for and purged from the database when Eclair starts up. Thereafter searches for expired unpaid invoices to purge will run once every 24 hours. You can disable this feature, or change the search interval with two new settings: +Expired incoming invoices that are unpaid will be searched for and purged from the database when Eclair starts up. +Thereafter searches for expired unpaid invoices to purge will run once every 24 hours. You can disable this feature, or +change the search interval with two new settings: * `eclair.purge-expired-invoices.enabled = true * `eclair.purge-expired-invoices.interval = 24 hours` @@ -134,13 +161,16 @@ Use the following command to generate the eclair-node package: mvn clean install -DskipTests ``` -That should generate `eclair-node/target/eclair-node--XXXXXXX-bin.zip` with sha256 checksums that match the one we provide and sign in `SHA256SUMS.asc` +That should generate `eclair-node/target/eclair-node--XXXXXXX-bin.zip` with sha256 checksums that match the one +we provide and sign in `SHA256SUMS.asc` -(*) You may be able to build the exact same artefacts with other operating systems or versions of JDK 11, we have not tried everything. +(*) You may be able to build the exact same artefacts with other operating systems or versions of JDK 11, we have not +tried everything. ## Upgrading -This release is fully compatible with previous eclair versions. You don't need to close your channels, just stop eclair, upgrade and restart. +This release is fully compatible with previous eclair versions. You don't need to close your channels, just stop eclair, +upgrade and restart. ## Changelog diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala index a9aaabe204..43b2a6911c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala @@ -76,8 +76,4 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience { assert(getChannelData(alice, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) assert(getChannelData(bob, channelId).asInstanceOf[PersistentChannelData].commitments.channelFeatures.hasFeature(ZeroConf)) } - - -} - - +} \ No newline at end of file From 4f6385bd5a45a2fec213077de2d44ba634db0f13 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 15 Jun 2022 11:17:05 +0200 Subject: [PATCH 54/55] fixup! test activation of zeroconf feature --- .../acinq/eclair/integration/basic/ZeroConfActivationSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala index 43b2a6911c..23313dffe9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfActivationSpec.scala @@ -58,7 +58,7 @@ class ZeroConfActivationSpec extends FixtureSpec with IntegrationPatience { connect(alice, bob) val channelType = AnchorOutputsZeroFeeHtlcTx(scidAlias = false, zeroConf = true) // bob rejects the channel - intercept { + intercept[AssertionError] { openChannel(alice, bob, 100_000 sat, channelType_opt = Some(channelType)).channelId } } From 04fc0e634b1106497375fb403ded3bc4403f5606 Mon Sep 17 00:00:00 2001 From: pm47 Date: Wed, 15 Jun 2022 11:55:32 +0200 Subject: [PATCH 55/55] simplify and fix tests --- .../channel/fsm/ChannelOpenSingleFunder.scala | 2 +- .../basic/ZeroConfAliasIntegrationSpec.scala | 25 ++++++------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala index db13acfbad..8966b25364 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunder.scala @@ -419,7 +419,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers { log.info("received remoteAlias={}", remoteAlias) context.system.eventStream.publish(ShortChannelIdAssigned(self, d.commitments.channelId, shortIds = shortIds1, remoteNodeId = remoteNodeId)) } - log.info("shortIds={}", shortIds1) + log.info("shortIds: real={} localAlias={} remoteAlias={}", shortIds1.real.toOption.getOrElse("none"), shortIds1.localAlias, shortIds1.remoteAlias_opt.getOrElse("none")) // we create a channel_update early so that we can use it to send payments through this channel, but it won't be propagated to other nodes since the channel is not yet announced val scidForChannelUpdate = Helpers.scidForChannelUpdate(channelAnnouncement_opt = None, shortIds1) log.info("using shortChannelId={} for initial channel_update", scidForChannelUpdate) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala index fe8759f4af..71c26c03b0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/ZeroConfAliasIntegrationSpec.scala @@ -63,8 +63,12 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience private def sendPaymentAliceToCarol(f: FixtureParam, useHint: Boolean = false, overrideHintScid_opt: Option[RealShortChannelId] = None): PaymentSent = { import f._ val hint = if (useHint) { - val Some(hint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop - Seq(hint.modify(_.shortChannelId).setToIfDefined(overrideHintScid_opt)) + val Some(carolHint) = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop + // due to how node ids are built, bob < carol so carol is always the node 2 + val bobAlias = getRouterData(bob).privateChannels.values.find(_.nodeId2 == carol.nodeParams.nodeId).value.shortIds.localAlias + // the hint is always using the alias + assert(carolHint.shortChannelId == bobAlias) + Seq(carolHint.modify(_.shortChannelId).setToIfDefined(overrideHintScid_opt)) } else Seq.empty sendPayment(alice, carol, 100_000 msat, hints = Seq(hint)) } @@ -138,14 +142,9 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcZeroConf = false, bcScidAlias = false, paymentWorksWithoutHint = false, // alice can't find a route to carol because bob-carol isn't announced - paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works + paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works (and it will use the alias, even if the feature isn't enabled) paymentWorksWithRealScidHint_opt = Some(true) // if alice uses the real scid instead of the bob-carol alias, it still works ) - - // Bob and Carol understand scid aliases even when the feature isn't enabled. - val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value - val bobAlias = getRouterData(bob).privateChannels.values.head.shortIds.localAlias - assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c scid-alias private)", Tag(ScidAliasBobCarol)) { f => @@ -160,11 +159,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = Some(false) // if alice uses the real scid instead of the bob-carol alias, it doesn't work due to option_scid_alias ) - - // Carol must use Bob's scid alias in her routing hints. - val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value - val bobAlias = getRouterData(bob).privateChannels.values.head.shortIds.localAlias - assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c zero-conf unconfirmed private)", Tag(ZeroConfBobCarol)) { f => @@ -179,11 +173,6 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience paymentWorksWithHint_opt = Some(true), // with a routing hint the payment works paymentWorksWithRealScidHint_opt = None // there is no real scid for bob-carol yet ) - - // Carol uses Bob's scid alias until the channel confirms. - val carolHint = getRouterData(carol).privateChannels.values.head.toIncomingExtraHop.value - val bobAlias = getRouterData(bob).privateChannels.values.collectFirst { case pc if pc.remoteNodeId == carol.nodeParams.nodeId => pc.shortIds.localAlias }.value - assert(carolHint.shortChannelId == bobAlias) } test("a->b->c (b-c zero-conf deeply confirmed private)", Tag(ZeroConfBobCarol)) { f =>