From fd47ba5f11d123ee7a3969e9285be5e802496577 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 7 May 2024 11:18:33 +0200 Subject: [PATCH 1/2] Accept onion failure without a `channel_update` https://github.com/lightning/bolts/pull/1163 makes the channel update in onion failures optional. One reason for this change is that it can be a privacy issue: by applying a `channel_update` received from an payment attempt, you may reveal that you are the sender. Another reason is that some nodes have been omitting that field for years (which was arguably a bug), and it's better to be able to correctly handle such failures. --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 2 +- .../acinq/eclair/payment/PaymentEvents.scala | 9 +- .../eclair/payment/relay/ChannelRelay.scala | 26 +++--- .../payment/send/PaymentLifecycle.scala | 90 +++++++++++-------- .../eclair/wire/protocol/FailureMessage.scala | 23 ++--- .../ZeroConfAliasIntegrationSpec.scala | 2 +- .../MultiPartPaymentLifecycleSpec.scala | 14 +-- .../eclair/payment/PaymentLifecycleSpec.scala | 16 ++-- .../payment/relay/ChannelRelayerSpec.scala | 30 +++---- .../protocol/FailureMessageCodecsSpec.scala | 15 ++-- 10 files changed, 126 insertions(+), 101 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 0f3a942d56..bc9344e808 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 @@ -640,7 +640,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case PostRevocationAction.RejectHtlc(add) => log.debug("rejecting incoming htlc {}", add) // NB: we don't set commit = true, we will sign all updates at once afterwards. - self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(d.channelUpdate)), commit = true) + self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(Some(d.channelUpdate))), commit = true) case PostRevocationAction.RelayFailure(result) => log.debug("forwarding {} to relayer", result) relayer ! result diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala index 57236424fa..b38f3fb13f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala @@ -211,7 +211,7 @@ object PaymentFailure { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(nodeId, _: Node)) => ignore + nodeId case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) => - if (Announcements.checkSig(failureMessage.update, nodeId)) { + if (failureMessage.update.forall(update => Announcements.checkSig(update, nodeId))) { val shouldIgnore = failureMessage match { case _: TemporaryChannelFailure => true case _: ChannelDisabled => true @@ -224,7 +224,7 @@ object PaymentFailure { ignore } } else { - // This node is fishy, it gave us a bad signature, so let's filter it out. + // This node is fishy, it gave us a bad channel update signature, so let's filter it out. ignore + nodeId } case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, _)) => @@ -257,7 +257,10 @@ object PaymentFailure { // We're only interested in the last channel update received per channel. val updates = failures.foldLeft(Map.empty[ShortChannelId, ChannelUpdate]) { case (current, failure) => failure match { - case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => current.updated(f.update.shortChannelId, f.update) + case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => f.update match { + case Some(update) => current.updated(update.shortChannelId, update) + case None => current + } case _ => current } } 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 adcb96729f..a68ff75dd7 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 @@ -71,15 +71,15 @@ object ChannelRelay { */ def translateLocalError(error: Throwable, channelUpdate_opt: Option[ChannelUpdate]): FailureMessage = { (error, channelUpdate_opt) match { - case (_: ExpiryTooSmall, Some(channelUpdate)) => ExpiryTooSoon(channelUpdate) + case (_: ExpiryTooSmall, Some(channelUpdate)) => ExpiryTooSoon(Some(channelUpdate)) case (_: ExpiryTooBig, _) => ExpiryTooFar() - case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: TooManyAcceptedHtlcs, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: HtlcValueTooHighInFlight, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: LocalDustHtlcExposureTooHigh, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: RemoteDustHtlcExposureTooHigh, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: FeerateTooDifferent, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) - case (_: ChannelUnavailable, Some(channelUpdate)) if !channelUpdate.channelFlags.isEnabled => ChannelDisabled(channelUpdate.messageFlags, channelUpdate.channelFlags, channelUpdate) + case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: TooManyAcceptedHtlcs, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: HtlcValueTooHighInFlight, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: LocalDustHtlcExposureTooHigh, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: RemoteDustHtlcExposureTooHigh, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: FeerateTooDifferent, Some(channelUpdate)) => TemporaryChannelFailure(Some(channelUpdate)) + case (_: ChannelUnavailable, Some(channelUpdate)) if !channelUpdate.channelFlags.isEnabled => ChannelDisabled(channelUpdate.messageFlags, channelUpdate.channelFlags, Some(channelUpdate)) case (_: ChannelUnavailable, None) => PermanentChannelFailure() case _ => TemporaryNodeFailure() } @@ -91,7 +91,7 @@ object ChannelRelay { case f: HtlcResult.RemoteFailMalformed => CMD_FAIL_HTLC(originHtlcId, Right(createBadOnionFailure(f.fail.onionHash, f.fail.failureCode)), commit = true) case _: HtlcResult.OnChainFail => CMD_FAIL_HTLC(originHtlcId, Right(PermanentChannelFailure()), commit = true) case HtlcResult.ChannelFailureBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(PermanentChannelFailure()), commit = true) - case f: HtlcResult.DisconnectedBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(TemporaryChannelFailure(f.channelUpdate)), commit = true) + case f: HtlcResult.DisconnectedBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(TemporaryChannelFailure(Some(f.channelUpdate))), commit = true) } } @@ -294,16 +294,16 @@ class ChannelRelay private(nodeParams: NodeParams, case None => RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) case Some(c) if !c.channelUpdate.channelFlags.isEnabled => - RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(c.channelUpdate.messageFlags, c.channelUpdate.channelFlags, c.channelUpdate)), commit = true)) + RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(c.channelUpdate.messageFlags, c.channelUpdate.channelFlags, Some(c.channelUpdate))), commit = true)) case Some(c) if r.amountToForward < c.channelUpdate.htlcMinimumMsat => - RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(r.amountToForward, c.channelUpdate)), commit = true)) + RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(r.amountToForward, Some(c.channelUpdate))), commit = true)) case Some(c) if r.expiryDelta < c.channelUpdate.cltvExpiryDelta => - RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, c.channelUpdate)), commit = true)) + RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, Some(c.channelUpdate))), commit = true)) case Some(c) if r.relayFeeMsat < nodeFee(c.channelUpdate.relayFees, r.amountToForward) && // fees also do not satisfy the previous channel update for `enforcementDelay` seconds after current update (TimestampSecond.now() - c.channelUpdate.timestamp > nodeParams.relayParams.enforcementDelay || outgoingChannel_opt.flatMap(_.prevChannelUpdate).forall(c => r.relayFeeMsat < nodeFee(c.relayFees, r.amountToForward))) => - RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, c.channelUpdate)), commit = true)) + RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(c.channelUpdate))), commit = true)) case Some(c: OutgoingChannel) => val origin = Origin.ChannelRelayedHot(addResponseAdapter.toClassic, r.add, r.amountToForward) val nextBlindingKey_opt = r.payload match { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala index d126703927..2bd6c1b0f0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala @@ -181,11 +181,18 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A router ! Router.RouteCouldRelay(stoppedRoute) } failureMessage match { - case TemporaryChannelFailure(update, _) => + case TemporaryChannelFailure(update_opt, _) => route.hops.find(_.nodeId == nodeId) match { - case Some(failingHop) if HopRelayParams.areSame(failingHop.params, HopRelayParams.FromAnnouncement(update), ignoreHtlcSize = true) => - router ! Router.ChannelCouldNotRelay(stoppedRoute.amount, failingHop) - case _ => // otherwise the relay parameters may have changed, so it's not necessarily a liquidity issue + case Some(failingHop) => + val isLiquidityIssue = update_opt match { + // If the relay parameters have changed, it's not necessarily a liquidity issue. + case Some(update) => HopRelayParams.areSame(failingHop.params, HopRelayParams.FromAnnouncement(update), ignoreHtlcSize = true) + case None => true + } + if (isLiquidityIssue) { + router ! Router.ChannelCouldNotRelay(stoppedRoute.amount, failingHop) + } + case _ => () } case _ => // other errors should not be used for liquidity issues } @@ -227,7 +234,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) => log.info(s"received 'Update' type error message from nodeId=$nodeId, retrying payment (failure=$failureMessage)") val failure = RemoteFailure(request.amount, route.fullRoute, e) - if (Announcements.checkSig(failureMessage.update, nodeId)) { + if (failureMessage.update.forall(update => Announcements.checkSig(update, nodeId))) { val recipient1 = handleUpdate(nodeId, failureMessage, d) val ignore1 = PaymentFailure.updateIgnored(failure, ignore) // let's try again, router will have updated its state @@ -240,7 +247,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A goto(WAITING_FOR_ROUTE) using WaitingForRoute(request.copy(recipient = recipient1), failures :+ failure, ignore1) } } else { - // this node is fishy, it gave us a bad sig!! let's filter it out + // this node is fishy, it gave us a bad channel update signature: let's filter it out. log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update}") request match { case _: SendPaymentToRoute => @@ -289,38 +296,49 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A val extraEdges1 = data.route.hops.find(_.nodeId == nodeId) match { case Some(hop) => hop.params match { case ann: HopRelayParams.FromAnnouncement => - if (ann.channelUpdate.shortChannelId != failure.update.shortChannelId) { - // it is possible that nodes in the route prefer using a different channel (to the same N+1 node) than the one we requested, that's fine - log.info("received an update for a different channel than the one we asked: requested={} actual={} update={}", ann.channelUpdate.shortChannelId, failure.update.shortChannelId, failure.update) - } else if (Announcements.areSame(ann.channelUpdate, failure.update)) { - // node returned the exact same update we used, this can happen e.g. if the channel is imbalanced - // in that case, let's temporarily exclude the channel from future routes, giving it time to recover - log.info("received exact same update from nodeId={}, excluding the channel from futures routes", nodeId) - router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) - } else if (PaymentFailure.hasAlreadyFailedOnce(nodeId, data.failures)) { - // this node had already given us a new channel update and is still unhappy, it is probably messing with us, let's exclude it - log.warning("it is the second time nodeId={} answers with a new update, excluding it: old={} new={}", nodeId, ann.channelUpdate, failure.update) - router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) - } else { - log.info("got a new update for shortChannelId={}: old={} new={}", ann.channelUpdate.shortChannelId, ann.channelUpdate, failure.update) + failure.update match { + case Some(update) if ann.channelUpdate.shortChannelId != update.shortChannelId => + // it is possible that nodes in the route prefer using a different channel (to the same N+1 node) than the one we requested, that's fine + log.info("received an update for a different channel than the one we asked: requested={} actual={} update={}", ann.channelUpdate.shortChannelId, update.shortChannelId, update) + case Some(update) if Announcements.areSame(ann.channelUpdate, update) => + // node returned the exact same update we used, this can happen e.g. if the channel is imbalanced + // in that case, let's temporarily exclude the channel from future routes, giving it time to recover + log.info("received exact same update from nodeId={}, excluding the channel from futures routes", nodeId) + router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) + case Some(_) if PaymentFailure.hasAlreadyFailedOnce(nodeId, data.failures) => + // this node had already given us a new channel update and is still unhappy, it is probably messing with us, let's exclude it + log.warning("it is the second time nodeId={} answers with a new update, excluding it: old={} new={}", nodeId, ann.channelUpdate, failure.update) + router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) + case Some(update) => + log.info("got a new update for shortChannelId={}: old={} new={}", ann.channelUpdate.shortChannelId, ann.channelUpdate, update) + case None => + // this isn't a relay parameter issue, so it's probably a liquidity issue + log.info("update not provided for shortChannelId={}", ann.channelUpdate.shortChannelId) + router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) } data.recipient.extraEdges - case _: HopRelayParams.FromHint => - log.info("received an update for a routing hint (shortChannelId={} nodeId={} enabled={} update={})", failure.update.shortChannelId, nodeId, failure.update.channelFlags.isEnabled, failure.update) - if (failure.update.channelFlags.isEnabled) { - data.recipient.extraEdges.map { - case edge: ExtraEdge if edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId => edge.update(failure.update) - case edge: ExtraEdge => edge - } - } else { - // if the channel is disabled, we temporarily exclude it: this is necessary because the routing hint doesn't - // contain channel flags to indicate that it's disabled - // we want the exclusion to be router-wide so that sister payments in the case of MPP are aware the channel is faulty - data.recipient.extraEdges - .find(edge => edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId) - .foreach(edge => router ! ExcludeChannel(ChannelDesc(edge.shortChannelId, edge.sourceNodeId, edge.targetNodeId), Some(nodeParams.routerConf.channelExcludeDuration))) - // we remove this edge for our next payment attempt - data.recipient.extraEdges.filterNot(edge => edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId) + case hint: HopRelayParams.FromHint => + failure.update match { + case Some(update) => + log.info("received an update for a routing hint (shortChannelId={} nodeId={} enabled={} update={})", update.shortChannelId, nodeId, update.channelFlags.isEnabled, failure.update) + if (update.channelFlags.isEnabled) { + data.recipient.extraEdges.map { + case edge: ExtraEdge if edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId => edge.update(update) + case edge: ExtraEdge => edge + } + } else { + // if the channel is disabled, we temporarily exclude it: this is necessary because the routing hint doesn't + // contain channel flags to indicate that it's disabled + // we want the exclusion to be router-wide so that sister payments in the case of MPP are aware the channel is faulty + data.recipient.extraEdges + .find(edge => edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId) + .foreach(edge => router ! ExcludeChannel(ChannelDesc(edge.shortChannelId, edge.sourceNodeId, edge.targetNodeId), Some(nodeParams.routerConf.channelExcludeDuration))) + // we remove this edge for our next payment attempt + data.recipient.extraEdges.filterNot(edge => edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId) + } + case None => + // this is most likely a liquidity issue, we remove this edge for our next payment attempt + data.recipient.extraEdges.filterNot(edge => edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId) } } case None => 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 c2f907251c..cc5a8b66d1 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 @@ -44,7 +44,9 @@ sealed trait FailureMessage { sealed trait BadOnion extends FailureMessage { def onionHash: ByteVector32 } sealed trait Perm extends FailureMessage sealed trait Node extends FailureMessage -sealed trait Update extends FailureMessage { def update: ChannelUpdate } +// Historically, this trait guaranteed that a channel update was provided. +// This was changed in https://github.com/lightning/bolts/pull/1163. +sealed trait Update extends FailureMessage { def update: Option[ChannelUpdate] } case class InvalidRealm(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "realm was not understood by the processing node" } case class TemporaryNodeFailure(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "general temporary failure of the processing node" } @@ -54,17 +56,17 @@ case class InvalidOnionVersion(onionHash: ByteVector32, tlvs: TlvStream[FailureM case class InvalidOnionHmac(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "onion HMAC was incorrect when it reached the processing node" } case class InvalidOnionKey(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "ephemeral key was unparsable by the processing node" } case class InvalidOnionBlinding(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "the blinded onion didn't match the processing node's requirements" } -case class TemporaryChannelFailure(update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = s"channel ${update.shortChannelId} is currently unavailable" } +case class TemporaryChannelFailure(update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = s"channel is currently unavailable (scid=${update.map(_.shortChannelId)})" } case class PermanentChannelFailure(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "channel is permanently unavailable" } case class RequiredChannelFeatureMissing(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "channel requires features not present in the onion" } case class UnknownNextPeer(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "processing node does not know the next peer in the route" } -case class AmountBelowMinimum(amount: MilliSatoshi, update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment amount was below the minimum required by the channel" } -case class FeeInsufficient(amount: MilliSatoshi, update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment fee was below the minimum required by the channel" } +case class AmountBelowMinimum(amount: MilliSatoshi, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment amount was below the minimum required by the channel" } +case class FeeInsufficient(amount: MilliSatoshi, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment fee was below the minimum required by the channel" } case class TrampolineFeeInsufficient(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "payment fee was below the minimum required by the trampoline node" } -case class ChannelDisabled(messageFlags: ChannelUpdate.MessageFlags, channelFlags: ChannelUpdate.ChannelFlags, update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "channel is currently disabled" } -case class IncorrectCltvExpiry(expiry: CltvExpiry, update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry doesn't match the value in the onion" } +case class ChannelDisabled(messageFlags: ChannelUpdate.MessageFlags, channelFlags: ChannelUpdate.ChannelFlags, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "channel is currently disabled" } +case class IncorrectCltvExpiry(expiry: CltvExpiry, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry doesn't match the value in the onion" } case class IncorrectOrUnknownPaymentDetails(amount: MilliSatoshi, height: BlockHeight, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "incorrect payment details or unknown payment hash" } -case class ExpiryTooSoon(update: ChannelUpdate, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } +case class ExpiryTooSoon(update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } case class TrampolineExpiryTooSoon(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } case class FinalIncorrectCltvExpiry(expiry: CltvExpiry, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" } case class FinalIncorrectHtlcAmount(amount: MilliSatoshi, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "payment amount is incorrect in the final htlc" } @@ -99,9 +101,10 @@ object FailureMessageCodecs { case _ => Attempt.failure(Err("not a ChanelUpdate message")) }, g => g) - // NB: for historical reasons some implementations were including/omitting the message type (258 for ChannelUpdate) - // this codec supports both versions for decoding, and will encode with the message type - val channelUpdateWithLengthCodec = variableSizeBytes(uint16, choice(channelUpdateCodecWithType, channelUpdateCodec)) + // NB: for historical reasons some implementations were including/omitting the message type (258 for ChannelUpdate). + // This codec supports both versions for decoding, and will encode with the message type. + // If the length provided is 0, the message doesn't include a channel update. + val channelUpdateWithLengthCodec: Codec[Option[ChannelUpdate]] = variableSizeBytes(uint16, optional(bitsRemaining, choice(channelUpdateCodecWithType, channelUpdateCodec))) val failureTlvsCodec: Codec[TlvStream[FailureMessageTlv]] = TlvCodecs.tlvStream(discriminated[FailureMessageTlv].by(varint)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala index f1383b24d8..117ef8224b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala @@ -280,7 +280,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience val failure = sendFailingPayment(alice, carol, 40_000_000 msat, hints = List(List(carolHint))) val failureWithChannelUpdate = failure.failures.collect { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => f } assert(failureWithChannelUpdate.length == 1) - assert(failureWithChannelUpdate.head.update.shortChannelId == bobAlias) + assert(failureWithChannelUpdate.head.update.map(_.shortChannelId).contains(bobAlias)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala index d86a9db2ff..9e3d23ac8e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala @@ -364,7 +364,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS // B changed his fees and expiry after the invoice was issued. val channelUpdate = channelUpdate_be.copy(feeBaseMsat = 250 msat, feeProportionalMillionths = 150, cltvExpiryDelta = CltvExpiryDelta(24)) val childId = payFsm.stateData.asInstanceOf[PaymentProgress].pending.keys.head - childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(route.amount, route.fullRoute, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(finalAmount, channelUpdate)))))) + childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(route.amount, route.fullRoute, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(finalAmount, Some(channelUpdate))))))) // We update the routing hints accordingly before requesting a new route. val extraEdge1 = extraEdge.copy(feeBase = 250 msat, feeProportionalMillionths = 150, cltvExpiryDelta = CltvExpiryDelta(24)) assert(router.expectMsgType[RouteRequest].target.extraEdges == Seq(extraEdge1)) @@ -388,7 +388,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS // NB: we need a channel update with a valid signature, otherwise we'll ignore the node instead of this specific channel. val channelUpdate = Announcements.makeChannelUpdate(channelUpdate_be.chainHash, priv_b, e, channelUpdate_be.shortChannelId, channelUpdate_be.cltvExpiryDelta, channelUpdate_be.htlcMinimumMsat, channelUpdate_be.feeBaseMsat, channelUpdate_be.feeProportionalMillionths, channelUpdate_be.htlcMaximumMsat) val childId = payFsm.stateData.asInstanceOf[PaymentProgress].pending.keys.head - childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(route.amount, route.fullRoute, Sphinx.DecryptedFailurePacket(b, TemporaryChannelFailure(channelUpdate)))))) + childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(route.amount, route.fullRoute, Sphinx.DecryptedFailurePacket(b, TemporaryChannelFailure(Some(channelUpdate))))))) // We update the routing hints accordingly before requesting a new route and ignore the channel. val routeRequest = router.expectMsgType[RouteRequest] assert(routeRequest.target.extraEdges == Seq(extraEdge)) @@ -429,7 +429,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS { val failures = Seq( LocalFailure(finalAmount, Nil, ChannelUnavailable(randomBytes32())), - RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48))))), + RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48)))))), UnreadableRemoteFailure(finalAmount, Nil) ) val extraEdges1 = Seq( @@ -441,10 +441,10 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS } { val failures = Seq( - RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(1), 20 msat, 20, CltvExpiryDelta(20))))), - RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(2), 21 msat, 21, CltvExpiryDelta(21))))), - RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(3), 22 msat, 22, CltvExpiryDelta(22))))), - RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(1), 23 msat, 23, CltvExpiryDelta(23))))), + RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(1), 20 msat, 20, CltvExpiryDelta(20)))))), + RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(2), 21 msat, 21, CltvExpiryDelta(21)))))), + RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(3), 22 msat, 22, CltvExpiryDelta(22)))))), + RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(1), 23 msat, 23, CltvExpiryDelta(23)))))), ) val extraEdges1 = Seq( ExtraEdge(a, b, ShortChannelId(1), 23 msat, 23, CltvExpiryDelta(23), defaultChannelUpdate.htlcMinimumMsat, Some(defaultChannelUpdate.htlcMaximumMsat)), 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 0022597a83..19219fea2d 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 @@ -492,7 +492,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { val WaitingForComplete(_, cmd1, Nil, sharedSecrets1, _, route) = paymentFSM.stateData register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd1)) - val failure = TemporaryChannelFailure(update_bc) + val failure = TemporaryChannelFailure(Some(update_bc)) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))))) // payment lifecycle will ask the router to temporarily exclude this channel from its route calculations @@ -524,7 +524,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // we change the cltv expiry val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, scid_bc, CltvExpiryDelta(42), htlcMinimumMsat = update_bc.htlcMinimumMsat, feeBaseMsat = update_bc.feeBaseMsat, feeProportionalMillionths = update_bc.feeProportionalMillionths, htlcMaximumMsat = update_bc.htlcMaximumMsat) - val failure = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified) + val failure = IncorrectCltvExpiry(CltvExpiry(5), Some(channelUpdate_bc_modified)) // and node replies with a failure containing a new channel update sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))))) @@ -539,7 +539,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // we change the cltv expiry one more time val channelUpdate_bc_modified_2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, scid_bc, CltvExpiryDelta(43), htlcMinimumMsat = update_bc.htlcMinimumMsat, feeBaseMsat = update_bc.feeBaseMsat, feeProportionalMillionths = update_bc.feeProportionalMillionths, htlcMaximumMsat = update_bc.htlcMaximumMsat) - val failure2 = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified_2) + val failure2 = IncorrectCltvExpiry(CltvExpiry(5), Some(channelUpdate_bc_modified_2)) // and node replies with a failure containing a new channel update sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets2.head._1, failure2))))) @@ -570,7 +570,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd1)) // the node replies with a temporary failure containing the same update as the one we already have (likely a balance issue) - val failure = TemporaryChannelFailure(update_bc) + val failure = TemporaryChannelFailure(Some(update_bc)) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))))) // we should temporarily exclude that channel assert(routerForwarder.expectMsgType[ChannelCouldNotRelay].hop.shortChannelId == update_bc.shortChannelId) @@ -603,7 +603,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // we change the cltv expiry val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, scid_bc, CltvExpiryDelta(42), htlcMinimumMsat = update_bc.htlcMinimumMsat, feeBaseMsat = update_bc.feeBaseMsat, feeProportionalMillionths = update_bc.feeProportionalMillionths, htlcMaximumMsat = update_bc.htlcMaximumMsat) - val failure = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified) + val failure = IncorrectCltvExpiry(CltvExpiry(5), Some(channelUpdate_bc_modified)) // and node replies with a failure containing a new channel update sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))))) @@ -643,7 +643,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // we disable the channel val channelUpdate_cd_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, scid_cd, CltvExpiryDelta(42), update_cd.htlcMinimumMsat, update_cd.feeBaseMsat, update_cd.feeProportionalMillionths, update_cd.htlcMaximumMsat, enable = false) - val failure = ChannelDisabled(channelUpdate_cd_disabled.messageFlags, channelUpdate_cd_disabled.channelFlags, channelUpdate_cd_disabled) + val failure = ChannelDisabled(channelUpdate_cd_disabled.messageFlags, channelUpdate_cd_disabled.channelFlags, Some(channelUpdate_cd_disabled)) val failureOnion = Sphinx.FailurePacket.wrap(Sphinx.FailurePacket.create(sharedSecrets1(1)._1, failure), sharedSecrets1.head._1) sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, failureOnion)))) @@ -878,7 +878,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(c, TemporaryNodeFailure())), Set(c), Set.empty), (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, PermanentChannelFailure())), Set.empty, Set(ChannelDesc(scid_bc, b, c))), (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(c, UnknownNextPeer())), Set.empty, Set(ChannelDesc(scid_cd, c, d))), - (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, update_bc))), Set.empty, Set.empty), + (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(update_bc)))), Set.empty, Set.empty), (RemoteFailure(defaultAmountMsat, blindedRoute_abc, Sphinx.DecryptedFailurePacket(b, InvalidOnionBlinding(randomBytes32()))), Set.empty, Set(ChannelDesc(blindedHop_bc.dummyId, blindedHop_bc.nodeId, blindedHop_bc.nextNodeId))), (RemoteFailure(defaultAmountMsat, blindedRoute_abc, Sphinx.DecryptedFailurePacket(blindedHop_bc.route.blindedNodeIds(1), InvalidOnionBlinding(randomBytes32()))), Set.empty, Set(ChannelDesc(blindedHop_bc.dummyId, blindedHop_bc.nodeId, blindedHop_bc.nextNodeId))), // unreadable remote failures -> blacklist all nodes except our direct peer, the final recipient or the last hop @@ -946,7 +946,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // we change the cltv expiry val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, scid_bc, CltvExpiryDelta(42), htlcMinimumMsat = update_bc.htlcMinimumMsat, feeBaseMsat = update_bc.feeBaseMsat, feeProportionalMillionths = update_bc.feeProportionalMillionths, htlcMaximumMsat = update_bc.htlcMaximumMsat) - val failure = IncorrectCltvExpiry(CltvExpiry(5), channelUpdate_bc_modified) + val failure = IncorrectCltvExpiry(CltvExpiry(5), Some(channelUpdate_bc_modified)) // and node replies with a failure containing a new channel update sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, Sphinx.FailurePacket.create(sharedSecrets1.head._1, failure))))) 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 4cdd3ef8f9..883be48544 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 @@ -194,7 +194,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a fwd1.message.replyTo ! RES_ADD_FAILED(fwd2.message, HtlcValueTooHighInFlight(channelIds(realScid1), 1000000000 msat, 1516977616 msat), Some(u1.channelUpdate)) // the relayer should give up - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(u1.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(u1.channelUpdate))), commit = true)) } test("fail to relay when we have no channel_update for the next channel") { f => @@ -249,7 +249,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(u.channelUpdate.messageFlags, u.channelUpdate.channelFlags, u.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(u.channelUpdate.messageFlags, u.channelUpdate.channelFlags, Some(u.channelUpdate))), commit = true)) } test("fail to relay when amount is below minimum") { f => @@ -262,7 +262,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(outgoingAmount, u.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(outgoingAmount, Some(u.channelUpdate))), commit = true)) } test("fail to relay blinded payment") { f => @@ -317,7 +317,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, u.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, Some(u.channelUpdate))), commit = true)) } test("fail to relay when fee is insufficient") { f => @@ -330,7 +330,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelUpdate(u) channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, u.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(u.channelUpdate))), commit = true)) } test("relay that would fail (fee insufficient) with a recent channel update but succeed with the previous update") { f => @@ -361,7 +361,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! Relay(r) // relay fails because the current update (u3) with higher fees occurred more than 10 minutes ago - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, u3.channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(u3.channelUpdate))), commit = true)) } test("fail to relay when there is a local error") { f => @@ -376,13 +376,13 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a case class TestCase(exc: ChannelException, update: ChannelUpdate, failure: FailureMessage) val testCases = Seq( - TestCase(ExpiryTooSmall(channelId1, CltvExpiry(100), CltvExpiry(0), BlockHeight(0)), u.channelUpdate, ExpiryTooSoon(u.channelUpdate)), + TestCase(ExpiryTooSmall(channelId1, CltvExpiry(100), CltvExpiry(0), BlockHeight(0)), u.channelUpdate, ExpiryTooSoon(Some(u.channelUpdate))), TestCase(ExpiryTooBig(channelId1, CltvExpiry(100), CltvExpiry(200), BlockHeight(0)), u.channelUpdate, ExpiryTooFar()), - TestCase(TooManyAcceptedHtlcs(channelId1, 10), u.channelUpdate, TemporaryChannelFailure(u.channelUpdate)), - TestCase(HtlcValueTooHighInFlight(channelId1, 250_000_000 msat, 300_000_000 msat), u.channelUpdate, TemporaryChannelFailure(u.channelUpdate)), - TestCase(InsufficientFunds(channelId1, r.amountToForward, 100 sat, 0 sat, 0 sat), u.channelUpdate, TemporaryChannelFailure(u.channelUpdate)), - TestCase(FeerateTooDifferent(channelId1, FeeratePerKw(1000 sat), FeeratePerKw(300 sat)), u.channelUpdate, TemporaryChannelFailure(u.channelUpdate)), - TestCase(ChannelUnavailable(channelId1), u_disabled.channelUpdate, ChannelDisabled(u_disabled.channelUpdate.messageFlags, u_disabled.channelUpdate.channelFlags, u_disabled.channelUpdate)) + TestCase(TooManyAcceptedHtlcs(channelId1, 10), u.channelUpdate, TemporaryChannelFailure(Some(u.channelUpdate))), + TestCase(HtlcValueTooHighInFlight(channelId1, 250_000_000 msat, 300_000_000 msat), u.channelUpdate, TemporaryChannelFailure(Some(u.channelUpdate))), + TestCase(InsufficientFunds(channelId1, r.amountToForward, 100 sat, 0 sat, 0 sat), u.channelUpdate, TemporaryChannelFailure(Some(u.channelUpdate))), + TestCase(FeerateTooDifferent(channelId1, FeeratePerKw(1000 sat), FeeratePerKw(300 sat)), u.channelUpdate, TemporaryChannelFailure(Some(u.channelUpdate))), + TestCase(ChannelUnavailable(channelId1), u_disabled.channelUpdate, ChannelDisabled(u_disabled.channelUpdate.messageFlags, u_disabled.channelUpdate.channelFlags, Some(u_disabled.channelUpdate))) ) testCases.foreach { testCase => @@ -436,7 +436,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val cmd4 = expectFwdAdd(register, channelUpdates(ShortChannelId(11111)).channelId, r.amountToForward, r.outgoingCltv).message cmd4.replyTo ! RES_ADD_FAILED(cmd4, HtlcValueTooHighInFlight(randomBytes32(), 100000000 msat, 100000000 msat), Some(channelUpdates(ShortChannelId(11111)).channelUpdate)) // all the suitable channels have been tried - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(channelUpdates(ShortChannelId(12345)).channelUpdate)), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true)) } { // higher amount payment (have to increased incoming htlc amount for fees to be sufficient) @@ -471,7 +471,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val payload = ChannelRelay.Standard(ShortChannelId(12345), 998900 msat, CltvExpiry(61)) 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)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(CltvExpiry(61), Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true)) } } @@ -490,7 +490,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a TestCase(HtlcResult.RemoteFail(UpdateFailHtlc(channelId1, downstream_htlc.id, hex"deadbeef")), CMD_FAIL_HTLC(r.add.id, Left(hex"deadbeef"), commit = true)), TestCase(HtlcResult.RemoteFailMalformed(UpdateFailMalformedHtlc(channelId1, downstream_htlc.id, ByteVector32.One, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 5)), CMD_FAIL_HTLC(r.add.id, Right(InvalidOnionHmac(ByteVector32.One)), commit = true)), TestCase(HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId1, downstream_htlc)), CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure()), commit = true)), - TestCase(HtlcResult.DisconnectedBeforeSigned(u_disabled.channelUpdate), CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(u_disabled.channelUpdate)), commit = true)), + TestCase(HtlcResult.DisconnectedBeforeSigned(u_disabled.channelUpdate), CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(u_disabled.channelUpdate))), commit = true)), TestCase(HtlcResult.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure()), commit = true)) ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala index aa6f36061b..a92d1fa695 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/FailureMessageCodecsSpec.scala @@ -52,17 +52,18 @@ class FailureMessageCodecsSpec extends AnyFunSuite { InvalidOnionHmac(ByteVector32(hex"1c9836f65130ee10a13da0db2d2acef8bc799978d351700f1a09aefc3ab221f7")) -> hex"c005 1c9836f65130ee10a13da0db2d2acef8bc799978d351700f1a09aefc3ab221f7", InvalidOnionKey(ByteVector32(hex"7568cf300a7b7458693904d50e67dc0c29a5116600d93e9979c3fa91e2b85395")) -> hex"c006 7568cf300a7b7458693904d50e67dc0c29a5116600d93e9979c3fa91e2b85395", InvalidOnionBlinding(ByteVector32(hex"d71cd923bb201254bc07dadde7e795b8c6b0b849325ee3c603e1bba2e5d2c100")) -> hex"c018 d71cd923bb201254bc07dadde7e795b8c6b0b849325ee3c603e1bba2e5d2c100", - TemporaryChannelFailure(channelUpdate) -> hex"1007 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + TemporaryChannelFailure(Some(channelUpdate)) -> hex"1007 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + TemporaryChannelFailure(None) -> hex"1007 0000", PermanentChannelFailure() -> hex"4008", RequiredChannelFeatureMissing() -> hex"4009", UnknownNextPeer() -> hex"400a", - AmountBelowMinimum(123456 msat, channelUpdate) -> hex"100b 000000000001e240 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", - FeeInsufficient(546463 msat, channelUpdate) -> hex"100c 000000000008569f 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", - ChannelDisabled(ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), channelUpdate) -> hex"1014 01 01 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", - IncorrectCltvExpiry(CltvExpiry(1211), channelUpdate) -> hex"100d 000004bb 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + AmountBelowMinimum(123456 msat, Some(channelUpdate)) -> hex"100b 000000000001e240 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + FeeInsufficient(546463 msat, Some(channelUpdate)) -> hex"100c 000000000008569f 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + ChannelDisabled(ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), Some(channelUpdate)) -> hex"1014 01 01 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + IncorrectCltvExpiry(CltvExpiry(1211), Some(channelUpdate)) -> hex"100d 000004bb 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", IncorrectOrUnknownPaymentDetails(123456 msat, BlockHeight(1105)) -> hex"400f 000000000001e240 00000451", IncorrectOrUnknownPaymentDetails(100 msat, BlockHeight(800_000), TlvStream(Set.empty[FailureMessageTlv], Set(GenericTlv(UInt64(34001), ByteVector.fill(300)(128))))) -> hex"400f 0000000000000064 000c3500 fd84d1 fd012c 808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080", - ExpiryTooSoon(channelUpdate) -> hex"100e 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + ExpiryTooSoon(Some(channelUpdate)) -> hex"100e 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", FinalIncorrectCltvExpiry(CltvExpiry(1234)) -> hex"0012 000004d2", FinalIncorrectHtlcAmount(25_000_000 msat) -> hex"0013 00000000017d7840", ExpiryTooFar() -> hex"0015", @@ -179,7 +180,7 @@ class FailureMessageCodecsSpec extends AnyFunSuite { test("support encoding of channel_update with/without type in failure messages") { val tmpChannelFailureWithoutType = hex"10070088cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0103000e00000000000003e800000001000000010000000008f0d180" val tmpChannelFailureWithType = hex"1007008a0102cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0103000e00000000000003e800000001000000010000000008f0d180" - val ref = TemporaryChannelFailure(ChannelUpdate(ByteVector64(hex"cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f4578219"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(14), 1000 msat, 1 msat, 1, 150_000_000 msat)) + val ref = TemporaryChannelFailure(Some(ChannelUpdate(ByteVector64(hex"cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f4578219"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759 unixsec, ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = false, isNode1 = false), CltvExpiryDelta(14), 1000 msat, 1 msat, 1, 150_000_000 msat))) val u1 = failureMessageCodec.decode(tmpChannelFailureWithoutType.toBitVector).require.value assert(u1 == ref) From 2816203aa0e32f709e298cc7f013a7ee2d556f03 Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 4 Jun 2024 16:34:01 +0200 Subject: [PATCH 2/2] Rename `update_opt` field --- .../fr/acinq/eclair/payment/PaymentEvents.scala | 6 +++--- .../eclair/payment/send/PaymentLifecycle.scala | 12 ++++++------ .../eclair/wire/protocol/FailureMessage.scala | 14 +++++++------- .../zeroconf/ZeroConfAliasIntegrationSpec.scala | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala index b38f3fb13f..595038e63f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala @@ -186,7 +186,7 @@ object PaymentFailure { */ def hasAlreadyFailedOnce(nodeId: PublicKey, failures: Seq[PaymentFailure]): Boolean = failures - .collectFirst { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(origin, u: Update)) if origin == nodeId => u.update } + .collectFirst { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(origin, u: Update)) if origin == nodeId => u.update_opt } .isDefined /** Ignore the channel outgoing from the given nodeId in the given route. */ @@ -211,7 +211,7 @@ object PaymentFailure { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(nodeId, _: Node)) => ignore + nodeId case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) => - if (failureMessage.update.forall(update => Announcements.checkSig(update, nodeId))) { + if (failureMessage.update_opt.forall(update => Announcements.checkSig(update, nodeId))) { val shouldIgnore = failureMessage match { case _: TemporaryChannelFailure => true case _: ChannelDisabled => true @@ -257,7 +257,7 @@ object PaymentFailure { // We're only interested in the last channel update received per channel. val updates = failures.foldLeft(Map.empty[ShortChannelId, ChannelUpdate]) { case (current, failure) => failure match { - case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => f.update match { + case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => f.update_opt match { case Some(update) => current.updated(update.shortChannelId, update) case None => current } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala index 2bd6c1b0f0..a4eebbee1a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentLifecycle.scala @@ -234,7 +234,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) => log.info(s"received 'Update' type error message from nodeId=$nodeId, retrying payment (failure=$failureMessage)") val failure = RemoteFailure(request.amount, route.fullRoute, e) - if (failureMessage.update.forall(update => Announcements.checkSig(update, nodeId))) { + if (failureMessage.update_opt.forall(update => Announcements.checkSig(update, nodeId))) { val recipient1 = handleUpdate(nodeId, failureMessage, d) val ignore1 = PaymentFailure.updateIgnored(failure, ignore) // let's try again, router will have updated its state @@ -248,7 +248,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A } } else { // this node is fishy, it gave us a bad channel update signature: let's filter it out. - log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update}") + log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update_opt}") request match { case _: SendPaymentToRoute => log.error("unexpected retry during SendPaymentToRoute") @@ -296,7 +296,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A val extraEdges1 = data.route.hops.find(_.nodeId == nodeId) match { case Some(hop) => hop.params match { case ann: HopRelayParams.FromAnnouncement => - failure.update match { + failure.update_opt match { case Some(update) if ann.channelUpdate.shortChannelId != update.shortChannelId => // it is possible that nodes in the route prefer using a different channel (to the same N+1 node) than the one we requested, that's fine log.info("received an update for a different channel than the one we asked: requested={} actual={} update={}", ann.channelUpdate.shortChannelId, update.shortChannelId, update) @@ -307,7 +307,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) case Some(_) if PaymentFailure.hasAlreadyFailedOnce(nodeId, data.failures) => // this node had already given us a new channel update and is still unhappy, it is probably messing with us, let's exclude it - log.warning("it is the second time nodeId={} answers with a new update, excluding it: old={} new={}", nodeId, ann.channelUpdate, failure.update) + log.warning("it is the second time nodeId={} answers with a new update, excluding it: old={} new={}", nodeId, ann.channelUpdate, failure.update_opt) router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration)) case Some(update) => log.info("got a new update for shortChannelId={}: old={} new={}", ann.channelUpdate.shortChannelId, ann.channelUpdate, update) @@ -318,9 +318,9 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A } data.recipient.extraEdges case hint: HopRelayParams.FromHint => - failure.update match { + failure.update_opt match { case Some(update) => - log.info("received an update for a routing hint (shortChannelId={} nodeId={} enabled={} update={})", update.shortChannelId, nodeId, update.channelFlags.isEnabled, failure.update) + log.info("received an update for a routing hint (shortChannelId={} nodeId={} enabled={} update={})", update.shortChannelId, nodeId, update.channelFlags.isEnabled, failure.update_opt) if (update.channelFlags.isEnabled) { data.recipient.extraEdges.map { case edge: ExtraEdge if edge.sourceNodeId == nodeId && edge.targetNodeId == hop.nextNodeId => edge.update(update) 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 cc5a8b66d1..4bc21c2aa9 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 @@ -46,7 +46,7 @@ sealed trait Perm extends FailureMessage sealed trait Node extends FailureMessage // Historically, this trait guaranteed that a channel update was provided. // This was changed in https://github.com/lightning/bolts/pull/1163. -sealed trait Update extends FailureMessage { def update: Option[ChannelUpdate] } +sealed trait Update extends FailureMessage { def update_opt: Option[ChannelUpdate] } case class InvalidRealm(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "realm was not understood by the processing node" } case class TemporaryNodeFailure(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "general temporary failure of the processing node" } @@ -56,17 +56,17 @@ case class InvalidOnionVersion(onionHash: ByteVector32, tlvs: TlvStream[FailureM case class InvalidOnionHmac(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "onion HMAC was incorrect when it reached the processing node" } case class InvalidOnionKey(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "ephemeral key was unparsable by the processing node" } case class InvalidOnionBlinding(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "the blinded onion didn't match the processing node's requirements" } -case class TemporaryChannelFailure(update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = s"channel is currently unavailable (scid=${update.map(_.shortChannelId)})" } +case class TemporaryChannelFailure(update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = s"channel is currently unavailable (scid=${update_opt.map(_.shortChannelId)})" } case class PermanentChannelFailure(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "channel is permanently unavailable" } case class RequiredChannelFeatureMissing(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "channel requires features not present in the onion" } case class UnknownNextPeer(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "processing node does not know the next peer in the route" } -case class AmountBelowMinimum(amount: MilliSatoshi, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment amount was below the minimum required by the channel" } -case class FeeInsufficient(amount: MilliSatoshi, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment fee was below the minimum required by the channel" } +case class AmountBelowMinimum(amount: MilliSatoshi, update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment amount was below the minimum required by the channel" } +case class FeeInsufficient(amount: MilliSatoshi, update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment fee was below the minimum required by the channel" } case class TrampolineFeeInsufficient(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "payment fee was below the minimum required by the trampoline node" } -case class ChannelDisabled(messageFlags: ChannelUpdate.MessageFlags, channelFlags: ChannelUpdate.ChannelFlags, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "channel is currently disabled" } -case class IncorrectCltvExpiry(expiry: CltvExpiry, update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry doesn't match the value in the onion" } +case class ChannelDisabled(messageFlags: ChannelUpdate.MessageFlags, channelFlags: ChannelUpdate.ChannelFlags, update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "channel is currently disabled" } +case class IncorrectCltvExpiry(expiry: CltvExpiry, update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry doesn't match the value in the onion" } case class IncorrectOrUnknownPaymentDetails(amount: MilliSatoshi, height: BlockHeight, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "incorrect payment details or unknown payment hash" } -case class ExpiryTooSoon(update: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } +case class ExpiryTooSoon(update_opt: Option[ChannelUpdate], tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } case class TrampolineExpiryTooSoon(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Node { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } case class FinalIncorrectCltvExpiry(expiry: CltvExpiry, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" } case class FinalIncorrectHtlcAmount(amount: MilliSatoshi, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "payment amount is incorrect in the final htlc" } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala index 117ef8224b..12c348966b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/zeroconf/ZeroConfAliasIntegrationSpec.scala @@ -280,7 +280,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience val failure = sendFailingPayment(alice, carol, 40_000_000 msat, hints = List(List(carolHint))) val failureWithChannelUpdate = failure.failures.collect { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f: Update)) => f } assert(failureWithChannelUpdate.length == 1) - assert(failureWithChannelUpdate.head.update.map(_.shortChannelId).contains(bobAlias)) + assert(failureWithChannelUpdate.head.update_opt.map(_.shortChannelId).contains(bobAlias)) } }