From 755ad64ee9f945a60f14170987db67b14508fc9d Mon Sep 17 00:00:00 2001 From: t-bast Date: Tue, 11 Oct 2022 15:08:48 +0200 Subject: [PATCH] Add tlv stream to onion failures Extend every onion failure with an optional tlv stream. See the specification here: https://github.com/lightning/bolts/pull/1021 --- .../fr/acinq/eclair/channel/fsm/Channel.scala | 4 +- .../payment/receive/MultiPartPaymentFSM.scala | 2 +- .../eclair/payment/relay/ChannelRelay.scala | 14 +- .../eclair/payment/relay/NodeRelay.scala | 22 +-- .../relay/PostRestartHtlcCleaner.scala | 4 +- .../acinq/eclair/payment/relay/Relayer.scala | 4 +- .../acinq/eclair/payment/send/Autoprobe.scala | 4 +- .../payment/send/PaymentInitiator.scala | 2 +- .../payment/send/PaymentLifecycle.scala | 2 +- .../eclair/wire/internal/CommandCodecs.scala | 48 ++++++- .../eclair/wire/protocol/FailureMessage.scala | 131 ++++++++++-------- .../eclair/channel/ChannelDataSpec.scala | 4 +- .../ChannelStateTestsHelperMethods.scala | 2 +- .../channel/states/e/NormalStateSpec.scala | 12 +- .../channel/states/f/ShutdownStateSpec.scala | 8 +- .../fr/acinq/eclair/crypto/SphinxSpec.scala | 82 ++++++----- .../fr/acinq/eclair/db/PaymentsDbSpec.scala | 2 +- .../eclair/db/PendingCommandsDbSpec.scala | 4 +- .../integration/ChannelIntegrationSpec.scala | 4 +- .../ZeroConfAliasIntegrationSpec.scala | 4 +- .../eclair/payment/MultiPartHandlerSpec.scala | 10 +- .../payment/MultiPartPaymentFSMSpec.scala | 6 +- .../MultiPartPaymentLifecycleSpec.scala | 14 +- .../eclair/payment/PaymentInitiatorSpec.scala | 8 +- .../eclair/payment/PaymentLifecycleSpec.scala | 18 +-- .../payment/PostRestartHtlcCleanerSpec.scala | 30 ++-- .../payment/relay/ChannelRelayerSpec.scala | 16 +-- .../payment/relay/NodeRelayerSpec.scala | 26 ++-- .../eclair/payment/relay/RelayerSpec.scala | 2 +- .../wire/internal/CommandCodecsSpec.scala | 71 ++++------ .../protocol/FailureMessageCodecsSpec.scala | 79 +++++++---- 31 files changed, 351 insertions(+), 288 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 51e7c93e0f..0ad4ee6530 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 @@ -884,11 +884,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val case PostRevocationAction.RelayHtlc(add) => // BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown. log.debug("closing in progress: failing {}", add) - self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure), commit = true) + self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), commit = true) case PostRevocationAction.RejectHtlc(add) => // BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown. log.debug("closing in progress: rejecting {}", add) - self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure), commit = true) + self ! CMD_FAIL_HTLC(add.id, Right(PermanentChannelFailure()), 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/receive/MultiPartPaymentFSM.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala index 4d5fca27f5..db28e8f4ab 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartPaymentFSM.scala @@ -50,7 +50,7 @@ class MultiPartPaymentFSM(nodeParams: NodeParams, paymentHash: ByteVector32, tot when(WAITING_FOR_HTLC) { case Event(PaymentTimeout, d: WaitingForHtlc) => log.warning("multi-part payment timed out (received {} expected {})", d.paidAmount, totalAmount) - goto(PAYMENT_FAILED) using PaymentFailed(protocol.PaymentTimeout, d.parts) + goto(PAYMENT_FAILED) using PaymentFailed(protocol.PaymentTimeout(), d.parts) case Event(part: PaymentPart, d: WaitingForHtlc) => require(part.paymentHash == paymentHash, s"invalid payment hash (expected $paymentHash, received ${part.paymentHash}") 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 a961e1852e..2c46ab7a7a 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 @@ -70,13 +70,13 @@ object ChannelRelay { def translateLocalError(error: Throwable, channelUpdate_opt: Option[ChannelUpdate]): FailureMessage = { (error, channelUpdate_opt) match { case (_: ExpiryTooSmall, Some(channelUpdate)) => ExpiryTooSoon(channelUpdate) - case (_: ExpiryTooBig, _) => ExpiryTooFar + case (_: ExpiryTooBig, _) => ExpiryTooFar() case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate) case (_: TooManyAcceptedHtlcs, 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 (_: ChannelUnavailable, None) => PermanentChannelFailure - case _ => TemporaryNodeFailure + case (_: ChannelUnavailable, None) => PermanentChannelFailure() + case _ => TemporaryNodeFailure() } } @@ -95,8 +95,8 @@ object ChannelRelay { case _ => CMD_FAIL_MALFORMED_HTLC(originHtlcId, 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 _: 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) } } @@ -143,7 +143,7 @@ class ChannelRelay private(nodeParams: NodeParams, Behaviors.receiveMessagePartial { case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, CMD_ADD_HTLC(_, _, _, _, _, _, o: Origin.ChannelRelayedHot, _)))) => context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${o.add.id}") - val cmdFail = CMD_FAIL_HTLC(o.add.id, Right(UnknownNextPeer), commit = true) + val cmdFail = CMD_FAIL_HTLC(o.add.id, Right(UnknownNextPeer()), commit = true) Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel) safeSendAndStop(o.add.channelId, cmdFail) @@ -277,7 +277,7 @@ class ChannelRelay private(nodeParams: NodeParams, def relayOrFail(outgoingChannel_opt: Option[OutgoingChannelParams]): RelayResult = { outgoingChannel_opt match { case None => - RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) + 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)) case Some(c) if r.amountToForward < c.channelUpdate.htlcMinimumMsat => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala index 5dcdfba6cc..8bc7f4829d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala @@ -110,11 +110,11 @@ object NodeRelay { def validateRelay(nodeParams: NodeParams, upstream: Upstream.Trampoline, payloadOut: IntermediatePayload.NodeRelay.Standard): Option[FailureMessage] = { val fee = nodeFee(nodeParams.relayParams.minTrampolineFees, payloadOut.amountToForward) if (upstream.amountIn - payloadOut.amountToForward < fee) { - Some(TrampolineFeeInsufficient) + Some(TrampolineFeeInsufficient()) } else if (upstream.expiryIn - payloadOut.outgoingCltv < nodeParams.channelConf.expiryDelta) { - Some(TrampolineExpiryTooSoon) + Some(TrampolineExpiryTooSoon()) } else if (payloadOut.outgoingCltv <= CltvExpiry(nodeParams.currentBlockHeight)) { - Some(TrampolineExpiryTooSoon) + Some(TrampolineExpiryTooSoon()) } else if (payloadOut.invoiceFeatures.isDefined && payloadOut.paymentSecret.isEmpty) { Some(InvalidOnionPayload(UInt64(8), 0)) // payment secret field is missing } else if (payloadOut.amountToForward <= MilliSatoshi(0)) { @@ -146,14 +146,14 @@ object NodeRelay { // We have direct channels to the target node, but not enough outgoing liquidity to use those channels. // The routing fee proposed by the sender was high enough to find alternative, indirect routes, but didn't yield // any result so we tell them that we don't have enough outgoing liquidity at the moment. - Some(TemporaryNodeFailure) - case LocalFailure(_, _, BalanceTooLow) :: Nil => Some(TrampolineFeeInsufficient) // a higher fee/cltv may find alternative, indirect routes - case _ if routeNotFound => Some(TrampolineFeeInsufficient) // if we couldn't find routes, it's likely that the fee/cltv was insufficient + Some(TemporaryNodeFailure()) + case LocalFailure(_, _, BalanceTooLow) :: Nil => Some(TrampolineFeeInsufficient()) // a higher fee/cltv may find alternative, indirect routes + case _ if routeNotFound => Some(TrampolineFeeInsufficient()) // if we couldn't find routes, it's likely that the fee/cltv was insufficient case _ => // Otherwise, we try to find a downstream error that we could decrypt. val outgoingNodeFailure = failures.collectFirst { case RemoteFailure(_, _, e) if e.originNode == nextPayload.outgoingNodeId => e.failureMessage } val otherNodeFailure = failures.collectFirst { case RemoteFailure(_, _, e) => e.failureMessage } - val failure = outgoingNodeFailure.getOrElse(otherNodeFailure.getOrElse(TemporaryNodeFailure)) + val failure = outgoingNodeFailure.getOrElse(otherNodeFailure.getOrElse(TemporaryNodeFailure())) Some(failure) } } @@ -226,17 +226,17 @@ class NodeRelay private(nodeParams: NodeParams, Behaviors.receiveMessagePartial { case WrappedCurrentBlockHeight(blockHeight) if blockHeight >= safetyBlock => context.log.warn(s"rejecting async payment at block $blockHeight; was not triggered ${nodeParams.relayParams.asyncPaymentsParams.cancelSafetyBeforeTimeout} safety blocks before upstream cltv expiry at ${upstream.expiryIn}") - rejectPayment(upstream, Some(TemporaryNodeFailure)) // TODO: replace failure type when async payment spec is finalized + rejectPayment(upstream, Some(TemporaryNodeFailure())) // TODO: replace failure type when async payment spec is finalized stopping() case WrappedCurrentBlockHeight(blockHeight) if blockHeight >= timeoutBlock => context.log.warn(s"rejecting async payment at block $blockHeight; was not triggered after waiting ${nodeParams.relayParams.asyncPaymentsParams.holdTimeoutBlocks} blocks") - rejectPayment(upstream, Some(TemporaryNodeFailure)) // TODO: replace failure type when async payment spec is finalized + rejectPayment(upstream, Some(TemporaryNodeFailure())) // TODO: replace failure type when async payment spec is finalized stopping() - case WrappedCurrentBlockHeight(blockHeight) => + case _: WrappedCurrentBlockHeight => Behaviors.same case CancelAsyncPayment => context.log.warn(s"payment sender canceled a waiting async payment") - rejectPayment(upstream, Some(TemporaryNodeFailure)) // TODO: replace failure type when async payment spec is finalized + rejectPayment(upstream, Some(TemporaryNodeFailure())) // TODO: replace failure type when async payment spec is finalized stopping() case RelayAsyncPayment => doSend(upstream, nextPayload, nextPacket) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala index 18a058beb9..e126c46900 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala @@ -116,7 +116,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = false).increment() if (e.currentState != CLOSING && e.currentState != CLOSED) { log.info(s"failing not relayed htlc=$htlc") - channel ! CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure), commit = true) + channel ! CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), commit = true) } else { log.info(s"would fail but upstream channel is closed for htlc=$htlc") } @@ -243,7 +243,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment() // We don't bother decrypting the downstream failure to forward a more meaningful error upstream, it's // very likely that it won't be actionable anyway because of our node restart. - PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true)) + PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), commit = true)) } } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala index e78002beec..b68d3930ab 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala @@ -72,7 +72,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym case Right(r: IncomingPaymentPacket.NodeRelayPacket) => if (!nodeParams.enableTrampolinePayment) { log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} to nodeId=${r.innerPayload.outgoingNodeId} reason=trampoline disabled") - PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, CMD_FAIL_HTLC(add.id, Right(RequiredNodeFeatureMissing), commit = true)) + PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, CMD_FAIL_HTLC(add.id, Right(RequiredNodeFeatureMissing()), commit = true)) } else { nodeRelayer ! NodeRelayer.Relay(r) } @@ -81,7 +81,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym val delay_opt = badOnion match { // We are the introduction point of a blinded path: we add a non-negligible delay to make it look like it // could come from a downstream node. - case InvalidOnionBlinding(_) if add.blinding_opt.isEmpty => Some(500.millis + Random.nextLong(1500).millis) + case _: InvalidOnionBlinding if add.blinding_opt.isEmpty => Some(500.millis + Random.nextLong(1500).millis) case _ => None } val cmdFail = CMD_FAIL_MALFORMED_HTLC(add.id, badOnion.onionHash, badOnion.code, delay_opt, commit = true) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala index 6250534eb9..d836964f26 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Autoprobe.scala @@ -19,7 +19,7 @@ package fr.acinq.eclair.payment.send import akka.actor.{Actor, ActorLogging, ActorRef, Props} import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.crypto.Sphinx.DecryptedFailurePacket -import fr.acinq.eclair.payment.{PaymentEvent, PaymentFailed, Bolt11Invoice, Invoice, RemoteFailure} +import fr.acinq.eclair.payment.{Bolt11Invoice, PaymentEvent, PaymentFailed, RemoteFailure} import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.IncorrectOrUnknownPaymentDetails import fr.acinq.eclair.{MilliSatoshiLong, NodeParams, TimestampSecond, randomBytes32, randomLong} @@ -73,7 +73,7 @@ class Autoprobe(nodeParams: NodeParams, router: ActorRef, paymentInitiator: Acto case paymentResult: PaymentEvent => paymentResult match { - case PaymentFailed(_, _, _ :+ RemoteFailure(_, _, DecryptedFailurePacket(targetNodeId, IncorrectOrUnknownPaymentDetails(_, _))), _) => + case PaymentFailed(_, _, _ :+ RemoteFailure(_, _, DecryptedFailurePacket(targetNodeId, _: IncorrectOrUnknownPaymentDetails)), _) => log.info(s"payment probe successful to node=$targetNodeId") case _ => log.info(s"payment probe failed with paymentResult=$paymentResult") diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala index 49ee4e9b08..226c7f3703 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/PaymentInitiator.scala @@ -130,7 +130,7 @@ class PaymentInitiator(nodeParams: NodeParams, outgoingPaymentFactory: PaymentIn NodeHop(pp.r.trampolineNodeId, pp.r.recipientNodeId, pp.r.trampolineAttempts.last._2, pp.r.trampolineAttempts.last._1) ) val decryptedFailures = pf.failures.collect { case RemoteFailure(_, _, Sphinx.DecryptedFailurePacket(_, f)) => f } - val shouldRetry = decryptedFailures.contains(TrampolineFeeInsufficient) || decryptedFailures.contains(TrampolineExpiryTooSoon) + val shouldRetry = decryptedFailures.contains(TrampolineFeeInsufficient()) || decryptedFailures.contains(TrampolineExpiryTooSoon()) if (shouldRetry) { pp.remainingAttempts match { case (trampolineFees, trampolineExpiryDelta) :: remaining => 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 561dd517df..e41af5168f 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 @@ -183,7 +183,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A router ! Router.RouteCouldRelay(stoppedRoute) } failureMessage match { - case TemporaryChannelFailure(update) => + case TemporaryChannelFailure(update, _) => d.route.hops.find(_.nodeId == nodeId) match { case Some(failingHop) if ChannelRelayParams.areSame(failingHop.params, ChannelRelayParams.FromAnnouncement(update), ignoreHtlcSize = true) => router ! Router.ChannelCouldNotRelay(stoppedRoute.amount, failingHop) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/CommandCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/CommandCodecs.scala index 6c79f83663..465ed4f16d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/CommandCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/CommandCodecs.scala @@ -19,7 +19,10 @@ package fr.acinq.eclair.wire.internal import akka.actor.ActorRef import fr.acinq.eclair.channel._ import fr.acinq.eclair.wire.protocol.CommonCodecs._ -import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.failureMessageCodec +import fr.acinq.eclair.wire.protocol.FailureMessageCodecs._ +import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.{channelFlagsCodec, messageFlagsCodec} +import fr.acinq.eclair.wire.protocol._ +import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong} import scodec.Codec import scodec.codecs._ @@ -27,6 +30,41 @@ import scala.concurrent.duration.FiniteDuration object CommandCodecs { + // A trailing tlv stream was added in https://github.com/lightning/bolts/pull/1021 which wasn't handled properly by + // our previous set of codecs because we didn't prefix failure messages with their length. + private val legacyFailureMessageCodec = discriminated[FailureMessage].by(uint16) + .typecase(PERM | 1, provide(InvalidRealm())) + .typecase(NODE | 2, provide(TemporaryNodeFailure())) + .typecase(PERM | NODE | 2, provide(PermanentNodeFailure())) + .typecase(PERM | NODE | 3, provide(RequiredNodeFeatureMissing())) + .typecase(BADONION | PERM | 4, (sha256 :: provide(TlvStream.empty[FailureMessageTlv])).as[InvalidOnionVersion]) + .typecase(BADONION | PERM | 5, (sha256 :: provide(TlvStream.empty[FailureMessageTlv])).as[InvalidOnionHmac]) + .typecase(BADONION | PERM | 6, (sha256 :: provide(TlvStream.empty[FailureMessageTlv])).as[InvalidOnionKey]) + .typecase(UPDATE | 7, (channelUpdateWithLengthCodec :: provide(TlvStream.empty[FailureMessageTlv])).as[TemporaryChannelFailure]) + .typecase(PERM | 8, provide(PermanentChannelFailure())) + .typecase(PERM | 9, provide(RequiredChannelFeatureMissing())) + .typecase(PERM | 10, provide(UnknownNextPeer())) + .typecase(UPDATE | 11, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[AmountBelowMinimum]) + .typecase(UPDATE | 12, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[FeeInsufficient]) + .typecase(UPDATE | 13, (("expiry" | cltvExpiry) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[IncorrectCltvExpiry]) + .typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[ExpiryTooSoon]) + .typecase(UPDATE | 20, (messageFlagsCodec :: channelFlagsCodec :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[ChannelDisabled]) + .typecase(PERM | 15, (("amountMsat" | withDefaultValue(optional(bitsRemaining, millisatoshi), 0 msat)) :: ("height" | withDefaultValue(optional(bitsRemaining, blockHeight), BlockHeight(0))) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[IncorrectOrUnknownPaymentDetails]) + .typecase(18, (("expiry" | cltvExpiry) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[FinalIncorrectCltvExpiry]) + .typecase(19, (("amountMsat" | millisatoshi) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[FinalIncorrectHtlcAmount]) + .typecase(21, provide(ExpiryTooFar())) + .typecase(PERM | 22, (("tag" | varint) :: ("offset" | uint16) :: ("tlvs" | provide(TlvStream.empty[FailureMessageTlv]))).as[InvalidOnionPayload]) + .typecase(23, provide(PaymentTimeout())) + .typecase(BADONION | PERM | 24, (sha256 :: provide(TlvStream.empty[FailureMessageTlv])).as[InvalidOnionBlinding]) + .typecase(NODE | 51, provide(TrampolineFeeInsufficient())) + .typecase(NODE | 52, provide(TrampolineExpiryTooSoon())) + + private val legacyCmdFailCodec: Codec[CMD_FAIL_HTLC] = + (("id" | int64) :: + ("reason" | either(bool, varsizebinarydata, legacyFailureMessageCodec)) :: + ("commit" | provide(false)) :: + ("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC] + val cmdFulfillCodec: Codec[CMD_FULFILL_HTLC] = (("id" | int64) :: ("r" | bytes32) :: @@ -35,7 +73,7 @@ object CommandCodecs { val cmdFailCodec: Codec[CMD_FAIL_HTLC] = (("id" | int64) :: - ("reason" | either(bool, varsizebinarydata, failureMessageCodec)) :: + ("reason" | either(bool8, varsizebinarydata, variableSizeBytes(uint16, failureMessageCodec))) :: ("commit" | provide(false)) :: ("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC] @@ -49,8 +87,10 @@ object CommandCodecs { ("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_MALFORMED_HTLC] val cmdCodec: Codec[HtlcSettlementCommand] = discriminated[HtlcSettlementCommand].by(uint16) - .typecase(0, cmdFulfillCodec) - .typecase(1, cmdFailCodec) + // NB: order matters! + .typecase(3, cmdFailCodec) .typecase(2, cmdFailMalformedCodec) + .typecase(1, legacyCmdFailCodec) + .typecase(0, cmdFulfillCodec) } \ No newline at end of file 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 7732398452..a02e404e14 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 @@ -31,9 +31,12 @@ import scodec.{Attempt, Codec} * Created by fabrice on 14/03/17. */ +sealed trait FailureMessageTlv extends Tlv + // @formatter:off sealed trait FailureMessage { def message: String + def tlvs: TlvStream[FailureMessageTlv] // We actually encode the failure message, which is a bit clunky and not particularly efficient. // It would be nice to be able to get that value from the discriminated codec directly. lazy val code: Int = failureMessageCodec.encode(this).flatMap(uint16.decode).require.value @@ -43,31 +46,31 @@ sealed trait Perm extends FailureMessage sealed trait Node extends FailureMessage sealed trait Update extends FailureMessage { def update: ChannelUpdate } -case object InvalidRealm extends Perm { def message = "realm was not understood by the processing node" } -case object TemporaryNodeFailure extends Node { def message = "general temporary failure of the processing node" } -case object PermanentNodeFailure extends Perm with Node { def message = "general permanent failure of the processing node" } -case object RequiredNodeFeatureMissing extends Perm with Node { def message = "processing node requires features that are missing from this onion" } -case class InvalidOnionVersion(onionHash: ByteVector32) extends BadOnion with Perm { def message = "onion version was not understood by the processing node" } -case class InvalidOnionHmac(onionHash: ByteVector32) extends BadOnion with Perm { def message = "onion HMAC was incorrect when it reached the processing node" } -case class InvalidOnionKey(onionHash: ByteVector32) extends BadOnion with Perm { def message = "ephemeral key was unparsable by the processing node" } -case class InvalidOnionBlinding(onionHash: ByteVector32) extends BadOnion with Perm { def message = "the blinded onion didn't match the processing node's requirements" } -case class TemporaryChannelFailure(update: ChannelUpdate) extends Update { def message = s"channel ${update.shortChannelId} is currently unavailable" } -case object PermanentChannelFailure extends Perm { def message = "channel is permanently unavailable" } -case object RequiredChannelFeatureMissing extends Perm { def message = "channel requires features not present in the onion" } -case object UnknownNextPeer extends Perm { def message = "processing node does not know the next peer in the route" } -case class AmountBelowMinimum(amount: MilliSatoshi, update: ChannelUpdate) extends Update { def message = s"payment amount was below the minimum required by the channel" } -case class FeeInsufficient(amount: MilliSatoshi, update: ChannelUpdate) extends Update { def message = s"payment fee was below the minimum required by the channel" } -case object TrampolineFeeInsufficient 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) extends Update { def message = "channel is currently disabled" } -case class IncorrectCltvExpiry(expiry: CltvExpiry, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" } -case class IncorrectOrUnknownPaymentDetails(amount: MilliSatoshi, height: BlockHeight) extends Perm { def message = "incorrect payment details or unknown payment hash" } -case class ExpiryTooSoon(update: ChannelUpdate) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" } -case object TrampolineExpiryTooSoon 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) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" } -case class FinalIncorrectHtlcAmount(amount: MilliSatoshi) extends FailureMessage { def message = "payment amount is incorrect in the final htlc" } -case object ExpiryTooFar extends FailureMessage { def message = "payment expiry is too far in the future" } -case class InvalidOnionPayload(tag: UInt64, offset: Int) extends Perm { def message = "onion per-hop payload is invalid" } -case object PaymentTimeout extends FailureMessage { def message = "the complete payment amount was not received within a reasonable time" } +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" } +case class PermanentNodeFailure(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm with Node { def message = "general permanent failure of the processing node" } +case class RequiredNodeFeatureMissing(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm with Node { def message = "processing node requires features that are missing from this onion" } +case class InvalidOnionVersion(onionHash: ByteVector32, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends BadOnion with Perm { def message = "onion version was not understood by the processing node" } +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 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 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 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 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" } +case class ExpiryTooFar(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "payment expiry is too far in the future" } +case class InvalidOnionPayload(tag: UInt64, offset: Int, tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends Perm { def message = "onion per-hop payload is invalid" } +case class PaymentTimeout(tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty) extends FailureMessage { def message = "the complete payment amount was not received within a reasonable time" } /** * We allow remote nodes to send us unknown failure codes (e.g. deprecated failure codes). @@ -75,7 +78,8 @@ case object PaymentTimeout extends FailureMessage { def message = "the complete * to decode the failure payload (but we can't extract a channel update or onion hash). */ sealed trait UnknownFailureMessage extends FailureMessage { - def message = "unknown failure message" + override def message = "unknown failure message" + override def tlvs: TlvStream[FailureMessageTlv] = TlvStream.empty override def toString = s"$message (${code.toHexString})" override def equals(obj: Any): Boolean = obj match { case f: UnknownFailureMessage => f.code == code @@ -96,38 +100,10 @@ object FailureMessageCodecs { // this codec supports both versions for decoding, and will encode with the message type val channelUpdateWithLengthCodec = variableSizeBytes(uint16, choice(channelUpdateCodecWithType, channelUpdateCodec)) - val failureMessageCodec = discriminatorWithDefault( - discriminated[FailureMessage].by(uint16) - .typecase(PERM | 1, provide(InvalidRealm)) - .typecase(NODE | 2, provide(TemporaryNodeFailure)) - .typecase(PERM | NODE | 2, provide(PermanentNodeFailure)) - .typecase(PERM | NODE | 3, provide(RequiredNodeFeatureMissing)) - .typecase(BADONION | PERM | 4, sha256.as[InvalidOnionVersion]) - .typecase(BADONION | PERM | 5, sha256.as[InvalidOnionHmac]) - .typecase(BADONION | PERM | 6, sha256.as[InvalidOnionKey]) - .typecase(UPDATE | 7, ("channelUpdate" | channelUpdateWithLengthCodec).as[TemporaryChannelFailure]) - .typecase(PERM | 8, provide(PermanentChannelFailure)) - .typecase(PERM | 9, provide(RequiredChannelFeatureMissing)) - .typecase(PERM | 10, provide(UnknownNextPeer)) - .typecase(UPDATE | 11, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[AmountBelowMinimum]) - .typecase(UPDATE | 12, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient]) - .typecase(UPDATE | 13, (("expiry" | cltvExpiry) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry]) - .typecase(UPDATE | 14, ("channelUpdate" | channelUpdateWithLengthCodec).as[ExpiryTooSoon]) - .typecase(UPDATE | 20, (messageFlagsCodec :: channelFlagsCodec :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled]) - .typecase(PERM | 15, (("amountMsat" | withDefaultValue(optional(bitsRemaining, millisatoshi), 0 msat)) :: ("height" | withDefaultValue(optional(bitsRemaining, blockHeight), BlockHeight(0)))).as[IncorrectOrUnknownPaymentDetails]) - // PERM | 16 (incorrect_payment_amount) has been deprecated because it allowed probing attacks: IncorrectOrUnknownPaymentDetails should be used instead. - // PERM | 17 (final_expiry_too_soon) has been deprecated because it allowed probing attacks: IncorrectOrUnknownPaymentDetails should be used instead. - .typecase(18, ("expiry" | cltvExpiry).as[FinalIncorrectCltvExpiry]) - .typecase(19, ("amountMsat" | millisatoshi).as[FinalIncorrectHtlcAmount]) - .typecase(21, provide(ExpiryTooFar)) - .typecase(PERM | 22, (("tag" | varint) :: ("offset" | uint16)).as[InvalidOnionPayload]) - .typecase(23, provide(PaymentTimeout)) - .typecase(BADONION | PERM | 24, sha256.as[InvalidOnionBlinding]) - // TODO: @t-bast: once fully spec-ed, these should probably include a NodeUpdate and use a different ID. - // We should update Phoenix and our nodes at the same time, or first update Phoenix to understand both new and old errors. - .typecase(NODE | 51, provide(TrampolineFeeInsufficient)) - .typecase(NODE | 52, provide(TrampolineExpiryTooSoon)), - uint16.xmap(code => { + val failureTlvsCodec: Codec[TlvStream[FailureMessageTlv]] = TlvCodecs.tlvStream(discriminated[FailureMessageTlv].by(varint)) + + val unknownFailureMessageCodec: Codec[UnknownFailureMessage] = uint16.xmap( + code => { val failureMessage = code match { // @formatter:off case fc if (fc & PERM) != 0 && (fc & NODE) != 0 => new UnknownFailureMessage with Perm with Node { override lazy val code = fc } @@ -136,8 +112,43 @@ object FailureMessageCodecs { case fc => new UnknownFailureMessage { override lazy val code = fc } // @formatter:on } - failureMessage.asInstanceOf[FailureMessage] - }, (_: FailureMessage).code) + failureMessage + }, + failure => failure.code + ) + + val failureMessageCodec = discriminatorWithDefault( + discriminated[FailureMessage].by(uint16) + .typecase(PERM | 1, failureTlvsCodec.as[InvalidRealm]) + .typecase(NODE | 2, failureTlvsCodec.as[TemporaryNodeFailure]) + .typecase(PERM | NODE | 2, failureTlvsCodec.as[PermanentNodeFailure]) + .typecase(PERM | NODE | 3, failureTlvsCodec.as[RequiredNodeFeatureMissing]) + .typecase(BADONION | PERM | 4, (sha256 :: failureTlvsCodec).as[InvalidOnionVersion]) + .typecase(BADONION | PERM | 5, (sha256 :: failureTlvsCodec).as[InvalidOnionHmac]) + .typecase(BADONION | PERM | 6, (sha256 :: failureTlvsCodec).as[InvalidOnionKey]) + .typecase(UPDATE | 7, (("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[TemporaryChannelFailure]) + .typecase(PERM | 8, failureTlvsCodec.as[PermanentChannelFailure]) + .typecase(PERM | 9, failureTlvsCodec.as[RequiredChannelFeatureMissing]) + .typecase(PERM | 10, failureTlvsCodec.as[UnknownNextPeer]) + .typecase(UPDATE | 11, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[AmountBelowMinimum]) + .typecase(UPDATE | 12, (("amountMsat" | millisatoshi) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[FeeInsufficient]) + .typecase(UPDATE | 13, (("expiry" | cltvExpiry) :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[IncorrectCltvExpiry]) + .typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[ExpiryTooSoon]) + .typecase(UPDATE | 20, (messageFlagsCodec :: channelFlagsCodec :: ("channelUpdate" | channelUpdateWithLengthCodec) :: ("tlvs" | failureTlvsCodec)).as[ChannelDisabled]) + .typecase(PERM | 15, (("amountMsat" | withDefaultValue(optional(bitsRemaining, millisatoshi), 0 msat)) :: ("height" | withDefaultValue(optional(bitsRemaining, blockHeight), BlockHeight(0))) :: ("tlvs" | failureTlvsCodec)).as[IncorrectOrUnknownPaymentDetails]) + // PERM | 16 (incorrect_payment_amount) has been deprecated because it allowed probing attacks: IncorrectOrUnknownPaymentDetails should be used instead. + // PERM | 17 (final_expiry_too_soon) has been deprecated because it allowed probing attacks: IncorrectOrUnknownPaymentDetails should be used instead. + .typecase(18, (("expiry" | cltvExpiry) :: ("tlvs" | failureTlvsCodec)).as[FinalIncorrectCltvExpiry]) + .typecase(19, (("amountMsat" | millisatoshi) :: ("tlvs" | failureTlvsCodec)).as[FinalIncorrectHtlcAmount]) + .typecase(21, failureTlvsCodec.as[ExpiryTooFar]) + .typecase(PERM | 22, (("tag" | varint) :: ("offset" | uint16) :: ("tlvs" | failureTlvsCodec)).as[InvalidOnionPayload]) + .typecase(23, failureTlvsCodec.as[PaymentTimeout]) + .typecase(BADONION | PERM | 24, (sha256 :: failureTlvsCodec).as[InvalidOnionBlinding]) + // TODO: @t-bast: once fully spec-ed, these should probably include a NodeUpdate and use a different ID. + // We should update Phoenix and our nodes at the same time, or first update Phoenix to understand both new and old errors. + .typecase(NODE | 51, failureTlvsCodec.as[TrampolineFeeInsufficient]) + .typecase(NODE | 52, failureTlvsCodec.as[TrampolineExpiryTooSoon]), + fallback = unknownFailureMessageCodec.upcast[FailureMessage] ) private def failureOnionPayload(payloadAndPadLength: Int): Codec[FailureMessage] = Codec( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala index e86490470a..285ac29211 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala @@ -248,7 +248,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel // at this point the pending incoming htlc is waiting for a preimage assert(lcp4.htlcTxs(remainingHtlcOutpoint) == None) - alice ! CMD_FAIL_HTLC(1, Right(UnknownNextPeer), replyTo_opt = Some(probe.ref)) + alice ! CMD_FAIL_HTLC(1, Right(UnknownNextPeer()), replyTo_opt = Some(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FAIL_HTLC]] val aliceClosing1 = alice.stateData.asInstanceOf[DATA_CLOSING] val lcp5 = aliceClosing1.localCommitPublished.get.copy(irrevocablySpent = lcp4.irrevocablySpent, claimHtlcDelayedTxs = lcp4.claimHtlcDelayedTxs) @@ -378,7 +378,7 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel } assert(!rcp3.isDone) - bob ! CMD_FAIL_HTLC(bobPendingHtlc.htlc.id, Right(UnknownNextPeer), replyTo_opt = Some(probe.ref)) + bob ! CMD_FAIL_HTLC(bobPendingHtlc.htlc.id, Right(UnknownNextPeer()), replyTo_opt = Some(probe.ref)) probe.expectMsgType[CommandSuccess[CMD_FAIL_HTLC]] val bobClosing1 = bob.stateData.asInstanceOf[DATA_CLOSING] val rcp4 = bobClosing1.remoteCommitPublished.get.copy(irrevocablySpent = rcp3.irrevocablySpent) 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 6baa9b8a67..e916e08875 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 @@ -392,7 +392,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually { } def failHtlc(id: Long, s: TestFSMRef[ChannelState, ChannelData, Channel], r: TestFSMRef[ChannelState, ChannelData, Channel], s2r: TestProbe, r2s: TestProbe): Unit = { - s ! CMD_FAIL_HTLC(id, Right(TemporaryNodeFailure)) + s ! CMD_FAIL_HTLC(id, Right(TemporaryNodeFailure())) val fail = s2r.expectMsgType[UpdateFailHtlc] s2r.forward(r) eventually(assert(r.stateData.asInstanceOf[PersistentChannelData].commitments.remoteChanges.proposed.contains(fail))) 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 8131ba92b4..e546570375 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 @@ -1403,7 +1403,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val (_, htlc) = addHtlc(150000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - bob ! CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)) + bob ! CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure())) val fail = bob2alice.expectMsgType[UpdateFailHtlc] bob2alice.forward(alice) bob ! CMD_SIGN() @@ -1690,7 +1690,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // actual test begins val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val cmd = CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)) + val cmd = CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure())) val Right(fail) = OutgoingPaymentPacket.buildHtlcFailure(Bob.nodeParams.privateKey, cmd, htlc) assert(fail.id == htlc.id) bob ! cmd @@ -1721,7 +1721,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure), replyTo_opt = Some(sender.ref)) + val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure()), replyTo_opt = Some(sender.ref)) bob ! c sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42))) assert(initialState == bob.stateData) @@ -1741,7 +1741,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob2alice.expectMsgType[CommitSig] // We cannot fail the HTLC, we must wait for the fulfill to be acked. - val c = CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure), replyTo_opt = Some(sender.ref)) + val c = CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), replyTo_opt = Some(sender.ref)) bob ! c sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), htlc.id))) } @@ -1751,7 +1751,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure), replyTo_opt = Some(sender.ref)) + val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure()), replyTo_opt = Some(sender.ref)) sender.send(bob, c) // this will fail sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42))) awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty) @@ -1821,7 +1821,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with import f._ val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - bob ! CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure)) + bob ! CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure())) val fail = bob2alice.expectMsgType[UpdateFailHtlc] // actual test begins diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index 4f79a07e74..0cfad25ea4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -208,7 +208,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv CMD_FAIL_HTLC") { f => import f._ val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - bob ! CMD_FAIL_HTLC(1, Right(PermanentChannelFailure)) + bob ! CMD_FAIL_HTLC(1, Right(PermanentChannelFailure())) val fail = bob2alice.expectMsgType[UpdateFailHtlc] awaitCond(bob.stateData == initialState.copy( commitments = initialState.commitments.copy( @@ -219,7 +219,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure), replyTo_opt = Some(sender.ref)) + val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure()), replyTo_opt = Some(sender.ref)) bob ! c sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42))) assert(initialState == bob.stateData) @@ -229,7 +229,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit import f._ val sender = TestProbe() val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure), replyTo_opt = Some(sender.ref)) + val c = CMD_FAIL_HTLC(42, Right(PermanentChannelFailure()), replyTo_opt = Some(sender.ref)) sender.send(bob, c) // this will fail sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42))) awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty) @@ -469,7 +469,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit test("recv RevokeAndAck (forward UpdateFailHtlc)") { f => import f._ - bob ! CMD_FAIL_HTLC(1, Right(PermanentChannelFailure)) + bob ! CMD_FAIL_HTLC(1, Right(PermanentChannelFailure())) val fail = bob2alice.expectMsgType[UpdateFailHtlc] bob2alice.forward(alice) bob ! CMD_SIGN() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala index cffd37fd7d..52b42e0f10 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedRoute, BlindedRouteDetails} import fr.acinq.eclair.wire.protocol import fr.acinq.eclair.wire.protocol._ -import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, UInt64, randomKey} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, UInt64, randomKey} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -261,49 +261,66 @@ class SphinxSpec extends AnyFunSuite { assert(FailurePacket.decrypt(packet, Seq(0, 2, 1).map(i => (sharedSecrets(i), publicKeys(i)))).isFailure) } - test("last node replies with a failure message (reference test vector)") { - for ((payloads, packetPayloadLength) <- Seq( - (referencePaymentPayloads, 1300), - (paymentPayloadsFull, 1300), - (trampolinePaymentPayloads, 400))) { - // route: origin -> node #0 -> node #1 -> node #2 -> node #3 -> node #4 - + test("last node replies with a short failure message (old reference test vector)") { + for ((payloads, packetPayloadLength) <- Seq((referencePaymentPayloads, 1300), (paymentPayloadsFull, 1300), (trampolinePaymentPayloads, 400))) { // origin build the onion packet val Success(PacketAndSecrets(packet, sharedSecrets)) = create(sessionKey, packetPayloadLength, publicKeys, payloads, associatedData) - // each node parses and forwards the packet - // node #0 val Right(DecryptedPacket(_, packet1, sharedSecret0)) = peel(privKeys(0), associatedData, packet) - // node #1 val Right(DecryptedPacket(_, packet2, sharedSecret1)) = peel(privKeys(1), associatedData, packet1) - // node #2 val Right(DecryptedPacket(_, packet3, sharedSecret2)) = peel(privKeys(2), associatedData, packet2) - // node #3 val Right(DecryptedPacket(_, packet4, sharedSecret3)) = peel(privKeys(3), associatedData, packet3) - // node #4 val Right(lastPacket@DecryptedPacket(_, _, sharedSecret4)) = peel(privKeys(4), associatedData, packet4) assert(lastPacket.isLastPacket) // node #4 want to reply with an error message - val error = FailurePacket.create(sharedSecret4, TemporaryNodeFailure) + val error = FailurePacket.create(sharedSecret4, TemporaryNodeFailure()) assert(error == hex"a5e6bd0c74cb347f10cce367f949098f2457d14c046fd8a22cb96efb30b0fdcda8cb9168b50f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4") - // error sent back to 3, 2, 1 and 0 val error1 = FailurePacket.wrap(error, sharedSecret3) assert(error1 == hex"c49a1ce81680f78f5f2000cda36268de34a3f0a0662f55b4e837c83a8773c22aa081bab1616a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca270") - val error2 = FailurePacket.wrap(error1, sharedSecret2) assert(error2 == hex"a5d3e8634cfe78b2307d87c6d90be6fe7855b4f2cc9b1dfb19e92e4b79103f61ff9ac25f412ddfb7466e74f81b3e545563cdd8f5524dae873de61d7bdfccd496af2584930d2b566b4f8d3881f8c043df92224f38cf094cfc09d92655989531524593ec6d6caec1863bdfaa79229b5020acc034cd6deeea1021c50586947b9b8e6faa83b81fbfa6133c0af5d6b07c017f7158fa94f0d206baf12dda6b68f785b773b360fd0497e16cc402d779c8d48d0fa6315536ef0660f3f4e1865f5b38ea49c7da4fd959de4e83ff3ab686f059a45c65ba2af4a6a79166aa0f496bf04d06987b6d2ea205bdb0d347718b9aeff5b61dfff344993a275b79717cd815b6ad4c0beb568c4ac9c36ff1c315ec1119a1993c4b61e6eaa0375e0aaf738ac691abd3263bf937e3") - val error3 = FailurePacket.wrap(error2, sharedSecret1) assert(error3 == hex"aac3200c4968f56b21f53e5e374e3a2383ad2b1b6501bbcc45abc31e59b26881b7dfadbb56ec8dae8857add94e6702fb4c3a4de22e2e669e1ed926b04447fc73034bb730f4932acd62727b75348a648a1128744657ca6a4e713b9b646c3ca66cac02cdab44dd3439890ef3aaf61708714f7375349b8da541b2548d452d84de7084bb95b3ac2345201d624d31f4d52078aa0fa05a88b4e20202bd2b86ac5b52919ea305a8949de95e935eed0319cf3cf19ebea61d76ba92532497fcdc9411d06bcd4275094d0a4a3c5d3a945e43305a5a9256e333e1f64dbca5fcd4e03a39b9012d197506e06f29339dfee3331995b21615337ae060233d39befea925cc262873e0530408e6990f1cbd233a150ef7b004ff6166c70c68d9f8c853c1abca640b8660db2921") - val error4 = FailurePacket.wrap(error3, sharedSecret0) assert(error4 == hex"9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d") // origin parses error packet and can see that it comes from node #4 val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error4, sharedSecrets) assert(pubkey == publicKeys(4)) - assert(failure == TemporaryNodeFailure) + assert(failure == TemporaryNodeFailure()) + } + } + + test("last node replies with a long failure message (reference test vector)") { + for ((payloads, packetPayloadLength) <- Seq((referencePaymentPayloads, 1300), (paymentPayloadsFull, 1300))) { + // origin build the onion packet + val Success(PacketAndSecrets(packet, sharedSecrets)) = create(sessionKey, packetPayloadLength, publicKeys, payloads, associatedData) + // each node parses and forwards the packet + val Right(DecryptedPacket(_, packet1, sharedSecret0)) = peel(privKeys(0), associatedData, packet) + val Right(DecryptedPacket(_, packet2, sharedSecret1)) = peel(privKeys(1), associatedData, packet1) + val Right(DecryptedPacket(_, packet3, sharedSecret2)) = peel(privKeys(2), associatedData, packet2) + val Right(DecryptedPacket(_, packet4, sharedSecret3)) = peel(privKeys(3), associatedData, packet3) + val Right(lastPacket@DecryptedPacket(_, _, sharedSecret4)) = peel(privKeys(4), associatedData, packet4) + assert(lastPacket.isLastPacket) + + // node #4 want to reply with an error message + val failure = IncorrectOrUnknownPaymentDetails(100 msat, BlockHeight(800000), TlvStream(Nil, Seq(GenericTlv(UInt64(34001), ByteVector.fill(300)(128))))) + val error = createCustomLengthFailurePacket(failure, sharedSecret4, 1024) + assert(error == hex"146e94a9086dbbed6a0ab6932d00c118a7195dbf69b7d7a12b0e6956fc54b5e0a989f165b5f12fd45edd73a5b0c48630ff5be69500d3d82a29c0803f0a0679a6a073c33a6fb8250090a3152eba3f11a85184fa87b67f1b0354d6f48e3b342e332a17b7710f342f342a87cf32eccdf0afc2160808d58abb5e5840d2c760c538e63a6f841970f97d2e6fe5b8739dc45e2f7f5f532f227bcc2988ab0f9cc6d3f12909cd5842c37bc8c7608475a5ebbe10626d5ecc1f3388ad5f645167b44a4d166f87863fe34918cea25c18059b4c4d9cb414b59f6bc50c1cea749c80c43e2344f5d23159122ed4ab9722503b212016470d9610b46c35dbeebaf2e342e09770b38392a803bc9d2e7c8d6d384ffcbeb74943fe3f64afb2a543a6683c7db3088441c531eeb4647518cb41992f8954f1269fb969630944928c2d2b45593731b5da0c4e70d04a0a57afe4af42e99912fbb4f8883a5ecb9cb29b883cb6bfa0f4db2279ff8c6d2b56a232f55ba28fe7dfa70a9ab0433a085388f25cce8d53de6a2fbd7546377d6ede9027ad173ba1f95767461a3689ef405ab608a21086165c64b02c1782b04a6dba2361a7784603069124e12f2f6dcb1ec7612a4fbf94c0e14631a2bef6190c3d5f35e0c4b32aa85201f449d830fd8f782ec758b0910428e3ec3ca1dba3b6c7d89f69e1ee1b9df3dfbbf6d361e1463886b38d52e8f43b73a3bd48c6f36f5897f514b93364a31d49d1d506340b1315883d425cb36f4ea553430d538fd6f3596d4afc518db2f317dd051abc0d4bfb0a7870c3db70f19fe78d6604bbf088fcb4613f54e67b038277fedcd9680eb97bdffc3be1ab2cbcbafd625b8a7ac34d8c190f98d3064ecd3b95b8895157c6a37f31ef4de094b2cb9dbf8ff1f419ba0ecacb1bb13df0253b826bec2ccca1e745dd3b3e7cc6277ce284d649e7b8285727735ff4ef6cca6c18e2714f4e2a1ac67b25213d3bb49763b3b94e7ebf72507b71fb2fe0329666477ee7cb7ebd6b88ad5add8b217188b1ca0fa13de1ec09cc674346875105be6e0e0d6c8928eb0df23c39a639e04e4aedf535c4e093f08b2c905a14f25c0c0fe47a5a1535ab9eae0d9d67bdd79de13a08d59ee05385c7ea4af1ad3248e61dd22f8990e9e99897d653dd7b1b1433a6d464ea9f74e377f2d8ce99ba7dbc753297644234d25ecb5bd528e2e2082824681299ac30c05354baaa9c3967d86d7c07736f87fc0f63e5036d47235d7ae12178ced3ae36ee5919c093a02579e4fc9edad2c446c656c790704bfc8e2c491a42500aa1d75c8d4921ce29b753f883e17c79b09ea324f1f32ddf1f3284cd70e847b09d90f6718c42e5c94484cc9cbb0df659d255630a3f5a27e7d5dd14fa6b974d1719aa98f01a20fb4b7b1c77b42d57fab3c724339d459ee4a1c6b5d3bd4e08624c786a257872acc9ad3ff62222f2265a658d9f2a007229a5293b67ec91c84c4b4407c228434bad8a815ca9b256c776bd2c9f") + val error1 = FailurePacket.wrap(error, sharedSecret3) + assert(error1 == hex"7512354d6a26781d25e65539772ba049b7ed7c530bf75ab7ef80cf974b978a07a1c3dabc61940011585323f70fa98cfa1d4c868da30b1f751e44a72d9b3f79809c8c51c9f0843daa8fe83587844fedeacb7348362003b31922cbb4d6169b2087b6f8d192d9cfe5363254cd1fde24641bde9e422f170c3eb146f194c48a459ae2889d706dc654235fa9dd20307ea54091d09970bf956c067a3bcc05af03c41e01af949a131533778bf6ee3b546caf2eabe9d53d0fb2e8cc952b7e0f5326a69ed2e58e088729a1d85971c6b2e129a5643f3ac43da031e655b27081f10543262cf9d72d6f64d5d96387ac0d43da3e3a03da0c309af121dcf3e99192efa754eab6960c256ffd4c546208e292e0ab9894e3605db098dc16b40f17c320aa4a0e42fc8b105c22f08c9bc6537182c24e32062c6cd6d7ec7062a0c2c2ecdae1588c82185cdc61d874ee916a7873ac54cddf929354f307e870011704a0e9fbc5c7802d6140134028aca0e78a7e2f3d9e5c7e49e20c3a56b624bfea51196ec9e88e4e56be38ff56031369f45f1e03be826d44a182f270c153ee0d9f8cf9f1f4132f33974e37c7887d5b857365c873cb218cbf20d4be3abdb2a2011b14add0a5672e01e5845421cf6dd6faca1f2f443757aae575c53ab797c2227ecdab03882bbbf4599318cefafa72fa0c9a0f5a51d13c9d0e5d25bfcfb0154ed25895260a9df8743ac188714a3f16960e6e2ff663c08bffda41743d50960ea2f28cda0bc3bd4a180e297b5b41c700b674cb31d99c7f2a1445e121e772984abff2bbe3f42d757ceeda3d03fb1ffe710aecabda21d738b1f4620e757e57b123dbc3c4aa5d9617dfa72f4a12d788ca596af14bea583f502f16fdc13a5e739afb0715424af2767049f6b9aa107f69c5da0e85f6d8c5e46507e14616d5d0b797c3dea8b74a1b12d4e47ba7f57f09d515f6c7314543f78b5e85329d50c5f96ee2f55bbe0df742b4003b24ccbd4598a64413ee4807dc7f2a9c0b92424e4ae1b418a3cdf02ea4da5c3b12139348aa7022cc8272a3a1714ee3e4ae111cffd1bdfd62c503c80bdf27b2feaea0d5ab8fe00f9cec66e570b00fd24b4a2ed9a5f6384f148a4d6325110a41ca5659ebc5b98721d298a52819b6fb150f273383f1c5754d320be428941922da790e17f482989c365c078f7f3ae100965e1b38c052041165295157e1a7c5b7a57671b842d4d85a7d971323ad1f45e17a16c4656d889fc75c12fc3d8033f598306196e29571e414281c5da19c12605f48347ad5b4648e371757cbe1c40adb93052af1d6110cfbf611af5c8fc682b7e2ade3bfca8b5c7717d19fc9f97964ba6025aebbc91a6671e259949dcf40984342118de1f6b514a7786bd4f6598ffbe1604cef476b2a4cb1343db608aca09d1d38fc23e98ee9c65e7f6023a8d1e61fd4f34f753454bd8e858c8ad6be6403edc599c220e03ca917db765980ac781e758179cd93983e9c1e769e4241d47c") + val error2 = FailurePacket.wrap(error1, sharedSecret2) + assert(error2 == hex"145bc1c63058f7204abbd2320d422e69fb1b3801a14312f81e5e29e6b5f4774cfed8a25241d3dfb7466e749c1b3261559e49090853612e07bd669dfb5f4c54162fa504138dabd6ebcf0db8017840c35f12a2cfb84f89cc7c8959a6d51815b1d2c5136cedec2e4106bb5f2af9a21bd0a02c40b44ded6e6a90a145850614fb1b0eef2a03389f3f2693bc8a755630fc81fff1d87a147052863a71ad5aebe8770537f333e07d841761ec448257f948540d8f26b1d5b66f86e073746106dfdbb86ac9475acf59d95ece037fba360670d924dce53aaa74262711e62a8fc9eb70cd8618fbedae22853d3053c7f10b1a6f75369d7f73c419baa7dbf9f1fc5895362dcc8b6bd60cca4943ef7143956c91992119bccbe1666a20b7de8a2ff30a46112b53a6bb79b763903ecbd1f1f74952fb1d8eb0950c504df31fe702679c23b463f82a921a2c931500ab08e686cffb2d87258d254fb17843959cccd265a57ba26c740f0f231bb76df932b50c12c10be90174b37d454a3f8b284c849e86578a6182c4a7b2e47dd57d44730a1be9fec4ad07287a397e28dce4fda57e9cdfdb2eb5afdf0d38ef19d982341d18d07a556bb16c1416f480a396f278373b8fd9897023a4ac506e65cf4c306377730f9c8ca63cf47565240b59c4861e52f1dab84d938e96fb31820064d534aca05fd3d2600834fe4caea98f2a748eb8f200af77bd9fbf46141952b9ddda66ef0ebea17ea1e7bb5bce65b6e71554c56dd0d4e14f4cf74c77a150776bf31e7419756c71e7421dc22efe9cf01de9e19fc8808d5b525431b944400db121a77994518d6025711cb25a18774068bba7faaa16d8f65c91bec8768848333156dcb4a08dfbbd9fef392da3e4de13d4d74e83a7d6e46cfe530ee7a6f711e2caf8ad5461ba8177b2ef0a518baf9058ff9156e6aa7b08d938bd8d1485a787809d7b4c8aed97be880708470cd2b2cdf8e2f13428cc4b04ef1f2acbc9562f3693b948d0aa94b0e6113cafa684f8e4a67dc431dfb835726874bef1de36f273f52ee694ec46b0700f77f8538067642a552968e866a72a3f2031ad116663ac17b172b446c5bc705b84777363a9a3fdc6443c07b2f4ef58858122168d4ebbaee920cefc312e1cea870ed6e15eec046ab2073bbf08b0a3366f55cfc6ad4681a12ab0946534e7b6f90ea8992d530ec3daa6b523b3cf03101c60cadd914f30dec932c1ef4341b5a8efac3c921e203574cfe0f1f83433fddb8ccfd273f7c3cab7bc27efe3bb61fdccd5146f1185364b9b621e7fb2b74b51f5ee6be72ab6ff46a6359dc2c855e61469724c1dbeb273df9d2e1c1fb74891239c0019dc12d5c7535f7238f963b761d7102b585372cf021b64c4fc85bfb3161e59d2e298bba44cfd34d6859d9dba9dc6271e5047d525468c814f2ae438474b0a977273036da1a2292f88fcfb89574a6bdca1185b40f8aa54026d5926725f99ef028da1be892e3586361efe15f4a148ff1bc9") + val error3 = FailurePacket.wrap(error2, sharedSecret1) + assert(error3 == hex"1b4b09a935ce7af95b336baae307f2b400e3a7e808d9b4cf421cc4b3955620acb69dcdb656128dae8857adbd4e6b37fbb1be9c1f2f02e61e9e59a630c4c77cf383cb37b07413aa4de2f2fbf5b40ae40a91a8f4c6d74aeacef1bb1be4ecbc26ec2c824d2bc45db4b9098e732a769788f1cff3f5b41b0d25c132d40dc5ad045ef0043b15332ca3c5a09de2cdb17455a0f82a8f20da08346282823dab062cdbd2111e238528141d69de13de6d83994fbc711e3e269df63a12d3a4177c5c149150eb4dc2f589cd8acabcddba14dec3b0dada12d663b36176cd3c257c5460bab93981ad99f58660efa9b31d7e63b39915329695b3fa60e0a3bdb93e7e29a54ca6a8f360d3848866198f9c3da3ba958e7730847fe1e6478ce8597848d3412b4ae48b06e05ba9a104e648f6eaf183226b5f63ed2e68f77f7e38711b393766a6fab7921b03eba82b5d7cb78e34dc961948d6161eadd7cf5d95d9c56df2ff5faa6ccf85eacdc9ff2fc3abafe41c365a5bd14fd486d6b5e2f24199319e7813e02e798877ffe31a70ae2398d9e31b9e3727e6c1a3c0d995c67d37bb6e72e9660aaaa9232670f382add2edd468927e3303b6142672546997fe105583e7c5a3c4c2b599731308b5416e6c9a3f3ba55b181ad0439d3535356108b059f2cb8742eed7a58d4eba9fe79eaa77c34b12aff1abdaea93197aabd0e74cb271269ca464b3b06aef1d6573df5e1224179616036b368677f26479376681b772d3760e871d99efd34cca5cd6beca95190d967da820b21e5bec60082ea46d776b0517488c84f26d12873912d1f68fafd67bcf4c298e43cfa754959780682a2db0f75f95f0598c0d04fd014c50e4beb86a9e37d95f2bba7e5065ae052dc306555bca203d104c44a538b438c9762de299e1c4ad30d5b4a6460a76484661fc907682af202cd69b9a4473813b2fdc1142f1403a49b7e69a650b7cde9ff133997dcc6d43f049ecac5fce097a21e2bce49c810346426585e3a5a18569b4cddd5ff6bdec66d0b69fcbc5ab3b137b34cc8aefb8b850a764df0e685c81c326611d901c392a519866e132bbb73234f6a358ba284fbafb21aa3605cacbaf9d0c901390a98b7a7dac9d4f0b405f7291c88b2ff45874241c90ac6c5fc895a440453c344d3a365cb929f9c91b9e39cb98b142444aae03a6ae8284c77eb04b0a163813d4c21883df3c0f398f47bf127b5525f222107a2d8fe55289f0cfd3f4bbad6c5387b0594ef8a966afc9e804ccaf75fe39f35c6446f7ee076d433f2f8a44dba1515acc78e589fa8c71b0a006fe14feebd51d0e0aa4e51110d16759eee86192eee90b34432130f387e0ccd2ee71023f1f641cddb571c690107e08f592039fe36d81336a421e89378f351e633932a2f5f697d25b620ffb8e84bb6478e9bd229bf3b164b48d754ae97bd23f319e3c56b3bcdaaeb3bd7fc02ec02066b324cb72a09b6b43dec1097f49d69d3c138ce6f1a6402898baf7568c") + val error4 = FailurePacket.wrap(error3, sharedSecret0) + assert(error4 == hex"2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4") + + // origin parses error packet and can see that it comes from node #4 + val Success(DecryptedFailurePacket(pubkey, parsedFailure)) = FailurePacket.decrypt(error4, sharedSecrets) + assert(pubkey == publicKeys(4)) + assert(parsedFailure == failure) } } @@ -317,7 +334,7 @@ class SphinxSpec extends AnyFunSuite { assert(lastPacket.isLastPacket) // node #4 want to reply with an error message using a custom length - val error = createCustomLengthFailurePacket(TemporaryNodeFailure, sharedSecret4, 1024) + val error = createCustomLengthFailurePacket(TemporaryNodeFailure(), sharedSecret4, 1024) assert(error == hex"4ca0784803691f89f7558ff4560ba55aa6b94486e5c5cf1d0922750ad01e185ba8cb9168b60f2fd45edd73c1b0c8b33002df376801ff58aaa94000bf8a86f92620f343baef38a580102395ae3abf9128d1047a0736ff9b83d456740ebbb4aeb3aa9737f18fb4afb4aa074fb26c4d702f42968888550a3bded8c05247e045b866baef0499f079fdaeef6538f31d44deafffdfd3afa2fb4ca9082b8f1c465371a9894dd8c243fb4847e004f5256b3e90e2edde4c9fb3082ddfe4d1e734cacd96ef0706bf63c9984e22dc98851bcccd1c3494351feb458c9c6af41c0044bea3c47552b1d992ae542b17a2d0bba1a096c78d169034ecb55b6e3a7263c26017f033031228833c1daefc0dedb8cf7c3e37c9c37ebfe42f3225c326e8bcfd338804c145b16e34e4f5984bc119af09d471a61f39e9e389c4120cadabc5d9b7b1355a8ccef050ca8ad72f642fc26919927b347808bade4b1c321b08bc363f20745ba2f97f0ced2996a232f55ba28fe7dfa70a9ab0433a085388f25cce8d53de6a2fbd7546377d6ede9027ad173ba1f95767461a3689ef405ab608a21086165c64b02c1782b04a6dba2361a7784603069124e12f2f6dcb1ec7612a4fbf94c0e14631a2bef6190c3d5f35e0c4b32aa85201f449d830fd8f782ec758b0910428e3ec3ca1dba3b6c7d89f69e1ee1b9df3dfbbf6d361e1463886b38d52e8f43b73a3bd48c6f36f5897f514b93364a31d49d1d506340b1315883d425cb36f4ea553430d538fd6f3596d4afc518db2f317dd051abc0d4bfb0a7870c3db70f19fe78d6604bbf088fcb4613f54e67b038277fedcd9680eb97bdffc3be1ab2cbcbafd625b8a7ac34d8c190f98d3064ecd3b95b8895157c6a37f31ef4de094b2cb9dbf8ff1f419ba0ecacb1bb13df0253b826bec2ccca1e745dd3b3e7cc6277ce284d649e7b8285727735ff4ef6cca6c18e2714f4e2a1ac67b25213d3bb49763b3b94e7ebf72507b71fb2fe0329666477ee7cb7ebd6b88ad5add8b217188b1ca0fa13de1ec09cc674346875105be6e0e0d6c8928eb0df23c39a639e04e4aedf535c4e093f08b2c905a14f25c0c0fe47a5a1535ab9eae0d9d67bdd79de13a08d59ee05385c7ea4af1ad3248e61dd22f8990e9e99897d653dd7b1b1433a6d464ea9f74e377f2d8ce99ba7dbc753297644234d25ecb5bd528e2e2082824681299ac30c05354baaa9c3967d86d7c07736f87fc0f63e5036d47235d7ae12178ced3ae36ee5919c093a02579e4fc9edad2c446c656c790704bfc8e2c491a42500aa1d75c8d4921ce29b753f883e17c79b09ea324f1f32ddf1f3284cd70e847b09d90f6718c42e5c94484cc9cbb0df659d255630a3f5a27e7d5dd14fa6b974d1719aa98f01a20fb4b7b1c77b42d57fab3c724339d459ee4a1c6b5d3bd4e08624c786a257872acc9ad3ff62222f2265a658d9f2a007229a5293b67ec91c84c4b4407c228434bad8a815ca9b256c776bd2c9f") val error1 = FailurePacket.wrap(error, sharedSecret3) assert(error1 == hex"2ddcd9ac6122dc79b8b96c5e0c20c40bb64d656a8785420bcdacd3cb67dd27bca081bab1626a0011585323930fa5b9fae0c85770a2279ff59ec427ad1bbff9001c0cd1497004bd2a0f68b50704cf6d6a4bf3c8b6a0833399a24b3456961ba00736785112594f65b6b2d44d9f5ea4e49b5e1ec2af978cbe31c67114440ac51a62081df0ed46d4a3df295da0b0fe25c0115019f03f15ec86fabb4c852f83449e812f141a9395b3f70b766ebbd4ec2fae2b6955bd8f32684c15abfe8fd3a6261e52650e8807a92158d9f1463261a925e4bfba44bd20b166d532f0017185c3a6ac7957adefe45559e3072c8dc35abeba835a8cb01a71a15c736911126f27d46a36168ca5ef7dccd4e2886212602b181463e0dd30185c96348f9743a02aca8ec27c0b90dca2700c1b46d3f10242ceb286acec56576cf0e22042426c5a61d80c0298dc5ce158f46e11eaf8f32cd44d5f1213d4738768f081978420697b454700ade1c093c02a6ca0e78a7e2f3d9e5c7e49e20c3a56b624bfea51196ec9e88e4e56be38ff56031369f45f1e03be826d44a182f270c153ee0d9f8cf9f1f4132f33974e37c7887d5b857365c873cb218cbf20d4be3abdb2a2011b14add0a5672e01e5845421cf6dd6faca1f2f443757aae575c53ab797c2227ecdab03882bbbf4599318cefafa72fa0c9a0f5a51d13c9d0e5d25bfcfb0154ed25895260a9df8743ac188714a3f16960e6e2ff663c08bffda41743d50960ea2f28cda0bc3bd4a180e297b5b41c700b674cb31d99c7f2a1445e121e772984abff2bbe3f42d757ceeda3d03fb1ffe710aecabda21d738b1f4620e757e57b123dbc3c4aa5d9617dfa72f4a12d788ca596af14bea583f502f16fdc13a5e739afb0715424af2767049f6b9aa107f69c5da0e85f6d8c5e46507e14616d5d0b797c3dea8b74a1b12d4e47ba7f57f09d515f6c7314543f78b5e85329d50c5f96ee2f55bbe0df742b4003b24ccbd4598a64413ee4807dc7f2a9c0b92424e4ae1b418a3cdf02ea4da5c3b12139348aa7022cc8272a3a1714ee3e4ae111cffd1bdfd62c503c80bdf27b2feaea0d5ab8fe00f9cec66e570b00fd24b4a2ed9a5f6384f148a4d6325110a41ca5659ebc5b98721d298a52819b6fb150f273383f1c5754d320be428941922da790e17f482989c365c078f7f3ae100965e1b38c052041165295157e1a7c5b7a57671b842d4d85a7d971323ad1f45e17a16c4656d889fc75c12fc3d8033f598306196e29571e414281c5da19c12605f48347ad5b4648e371757cbe1c40adb93052af1d6110cfbf611af5c8fc682b7e2ade3bfca8b5c7717d19fc9f97964ba6025aebbc91a6671e259949dcf40984342118de1f6b514a7786bd4f6598ffbe1604cef476b2a4cb1343db608aca09d1d38fc23e98ee9c65e7f6023a8d1e61fd4f34f753454bd8e858c8ad6be6403edc599c220e03ca917db765980ac781e758179cd93983e9c1e769e4241d47c") @@ -331,38 +348,27 @@ class SphinxSpec extends AnyFunSuite { // origin parses error packet and can see that it comes from node #4 val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error4, sharedSecrets) assert(pubkey == publicKeys(4)) - assert(failure == TemporaryNodeFailure) + assert(failure == TemporaryNodeFailure()) } test("intermediate node replies with a failure message (reference test vector)") { - for ((payloads, packetPayloadLength) <- Seq( - (referencePaymentPayloads, 1300), - (paymentPayloadsFull, 1300), - (trampolinePaymentPayloads, 400))) { - // route: origin -> node #0 -> node #1 -> node #2 -> node #3 -> node #4 - + for ((payloads, packetPayloadLength) <- Seq((referencePaymentPayloads, 1300), (paymentPayloadsFull, 1300), (trampolinePaymentPayloads, 400))) { // origin build the onion packet val Success(PacketAndSecrets(packet, sharedSecrets)) = create(sessionKey, packetPayloadLength, publicKeys, payloads, associatedData) - // each node parses and forwards the packet - // node #0 val Right(DecryptedPacket(_, packet1, sharedSecret0)) = peel(privKeys(0), associatedData, packet) - // node #1 val Right(DecryptedPacket(_, packet2, sharedSecret1)) = peel(privKeys(1), associatedData, packet1) - // node #2 val Right(DecryptedPacket(_, _, sharedSecret2)) = peel(privKeys(2), associatedData, packet2) // node #2 want to reply with an error message - val error = FailurePacket.create(sharedSecret2, InvalidRealm) - - // error sent back to 1 and 0 + val error = FailurePacket.create(sharedSecret2, InvalidRealm()) val error1 = FailurePacket.wrap(error, sharedSecret1) val error2 = FailurePacket.wrap(error1, sharedSecret0) // origin parses error packet and can see that it comes from node #2 val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error2, sharedSecrets) assert(pubkey == publicKeys(2)) - assert(failure == InvalidRealm) + assert(failure == InvalidRealm()) } } @@ -373,7 +379,7 @@ class SphinxSpec extends AnyFunSuite { val Right(DecryptedPacket(_, _, sharedSecret2)) = peel(privKeys(2), associatedData, packet2) // node #2 want to reply with an error message - val error = createCustomLengthFailurePacket(InvalidRealm, sharedSecret2, 1024) + val error = createCustomLengthFailurePacket(InvalidRealm(), sharedSecret2, 1024) assert(error == hex"f1ca7d3b281a71af53d4a0f83f22b618aae9f9c11b1f3302b13615c66d9aefcc5f1938ef23b9dfa61e3d576b149bedaf83058f85f06a3172a3223ad6c4732d96b32955da7d2feb4140e58d86fc0f2eb5d9d1878e6f8a7f65ab9212030e8e915573ebbd7f35e1a430890be7e67c3fb4bbf2def662fa625421e7b411c29ebe81ec67b77355596b05cc155755664e59c16e21410aabe53e80404a615f44ebb31b365ca77a6e91241667b26c6cad24fb2324cf64e8b9dd6e2ce65f1f098cfd1ef41ba2d4c7def0ff165a0e7c84e7597c40e3dffe97d417c144545a0e38ee33ebaae12cc0c14650e453d46bfc48c0514f354773435ee89b7b2810606eb73262c77a1d67f3633705178d79a1078c3a01b5fadc9651feb63603d19decd3a00c1f69af2dab2595931ca50d8280758b1cc91ba2dc43dbbc3d91bf25c08b46c2ecef7a32cec64d4b61ee3a629ef563afe058b71e71bcb69033948bc8728c5ebe65ec596e4f305b9fc159d53f723dfc95b57f3d51717f1c89af97a6d587e89e62efcc92198a1b2bd66e2d875505ea4046c04389f8cb0ee98f0af03af2652e2f3d9a9c48430f2891a4d9b16e7d18099e4a3dd334c24aba1e2450792c2f22092c170da549d43a440021e699bd6c20d8bbf1961100a01ebcce06a4609f5ad93066287acf68294cfa9ea7cea03a508983b134a9f0118b16409a61c06aaa95897d2067cb7cd59123f3e2ccf0e16091571d616c44818f118bb7835a679f5c0eea8cf1bd5479882b2c2a341ec26dbe5da87b3d37d66b1fbd176f71ab203a3b6eaf7f214d579e7d0e4a3e59089ebd26ba04a62403ae7a793516ec16d971d51c5c0107a917d1a70221e6de16edca7cb057c7d06902b5191f298aa4d478a0c3a6260c257eae504ebbf2b591688e6f3f77af770b6f566ae9868d2f26c12574d3bf9323af59f0fe0072ff94ae597c2aa6fbcbf0831989e02f9d3d1b9fd6dd97f509185d9ecbf272e38bd621ee94b97af8e1cd43853a8f6aa6e8372585c71bf88246d064ade524e1e0bd8496b620c4c2d3ae06b6b064c97536aaf8d515046229f72bee8aa398cd0cc21afd5449595016bef4c77cb1e2e9d31fe1ca3ffde06515e6a4331ccc84edf702e5777b10fc844faf17601a4be3235931f6feca4582a8d247c1d6e4773f8fb6de320cf902bbb1767192782dc550d8e266e727a2aa2a414b816d1826ea46af71701537193c22bbcc0123d7ff5a23b0aa8d7967f36fef27b14fe1866ff3ab215eb29e07af49e19174887d71da7e7fe1b7aa1b3c805c063e0fafedf125fa6c57e38cce33a3f7bb35fd8a9f0950de3c22e49743c05f40bc55f960b8a8b5e2fde4bb229f125538438de418cb318d13968532499118cb7dcaaf8b6d635ac4001273bdafd12c8ea0702fb2f0dac81dbaaf68c1c32266382b293fa3951cb952ed5c1bdc41750cdbc0bd62c51bb685616874e251f031a929c06faef5bfcb0857f815ae20620b823f0abecfb5") val error1 = FailurePacket.wrap(error, sharedSecret1) assert(error1 == hex"fedab5542d8cfc76425c1960d1676ac551116628b2859535ed74f8934d38b82c175c570b34788dbfd0048e4a41c2bb01acf21a928c09f96b801d011d5ff805731f476679849797e76d1ace72304509e05adbbcf0f74959d7d370af32fa27066b9a7a9cb91d92518f3bdabe35a8b3ecea116db79b0c011b70742599012741c4128ca6655eeaf7e6ff343fed810af0e069fa1650659d5864f8b9f1aea92f1fcc10b1b71f3b012e1e55e53056d7f5e092daf7eb1b9244d2de468f69730f3237ce39a84cfd0ee42b12e5ac7ca63fea15bee528125e135090988e55fda565f99f15787ab49ae2b536ca34b1732069a72f314c99836091c17f4e50afecc602184c1e656cf6eb752a4ded94df315a3e16e3d3e422517e9b9a5c566f8bf3eb6144a6778df0078b51887d8ea59b73416c59594f81f8bf1b0f1c98b3d9d5ed87fe76358a47df8a705fb3edddf64770c2d49744854a5ed0272d94cec1cd1b049a6dece2e4aade89d783634c259a330bc407af06368aece354d6fe73608716da08a037dec9c71c4c73bd4a6c86fd1820b54aa2602132a95495933a24e28b189219859ab46847340ad08968a70d5a0df8223aab06a6ea532a4cb25498f3687361a59b9896975c948e03ba60f5248a1f2f4d7aa6e8f00f82f6ca92273f6084cac56c51d4dda2511d64d88dcfd11df5a07ae6779d445f141f5759fca37e09826e2e481ed5dced02956104b219f839f508f60d8828250d0a3617b9d021fad48cde24a5cb42e3278dff0d95af795d4c71bccd344fa98129c9d6f53dd4f7acab78a98711fc5d04112ae971dedc97649608597b7e53369be2fe3f9b0e6b349b3fadcf9bd2a3d24b5e876c74e1006f7c330714ea5146986f3f73b09cae5cdf6277e23a34ecce0d92d909442743ef415be81050c341eb305e93b14b07b55c079766cd894ea00826ce50d5c45707870b0cf411113b8e4e43cf34caf79f3936fdfdbeae185ff52db69ca72442d892ed0e45b9fac939aa172bfa873cebee1e2196fe124597feb92880339ebca8233acaf3061591ed8cf290dfd9b0a06d7efc299993b9c680451992e15d2cb8b5b4a3dc1e511a39d781818144a9662bdfbd01371e898c454a8a092b7a0d32a8d58aec8134891a974ac7b297c3b4f94100083db891bde0ebc1e737dc6c33dad87cf20429d1b865c7e8ee5032d66a17c5a731d288dda8fc38e2c963c317f12a786ded3eac484dcc11b5c530dec0e4cc40ec4bb2c529555a51d8655a4de08fdde774781b5672150d1c771bf0916fc5df6ddb2f2e683e86aa23a52c0fc2efe72eeb1fa5f86ad7926685f40d57ab19b29e1ce5dce8c98ae35aacf740cacb257915fe8421ec09d0883d4ae41fe2695679264b0196e8d0b874d47e2fd675c9dfba26e666d407572e19a65c84ca54ac7235ef1bd4aedd9b0f6406cc7eeb08020e325f22396bc1a42d2de5ff71042b4e098cb0358741a50757a31c45de1f7ecf3a5e5e06f8b682f0") @@ -383,7 +389,7 @@ class SphinxSpec extends AnyFunSuite { // origin parses error packet and can see that it comes from node #2 val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error2, sharedSecrets) assert(pubkey == publicKeys(2)) - assert(failure == InvalidRealm) + assert(failure == InvalidRealm()) } test("create blinded route (reference test vector)") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala index 57dd15730d..ad5e3b74de 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PaymentsDbSpec.scala @@ -627,7 +627,7 @@ class PaymentsDbSpec extends AnyFunSuite { db.updateOutgoingPayment(PaymentFailed(s3.id, s3.paymentHash, Nil, 310 unixms)) val ss3 = s3.copy(status = OutgoingPaymentStatus.Failed(Nil, 310 unixms)) assert(db.getOutgoingPayment(s3.id).contains(ss3)) - db.updateOutgoingPayment(PaymentFailed(s4.id, s4.paymentHash, Seq(LocalFailure(s4.amount, Seq(hop_ab), new RuntimeException("woops")), RemoteFailure(s4.amount, Seq(hop_ab, hop_bc), Sphinx.DecryptedFailurePacket(carol, UnknownNextPeer))), 320 unixms)) + db.updateOutgoingPayment(PaymentFailed(s4.id, s4.paymentHash, Seq(LocalFailure(s4.amount, Seq(hop_ab), new RuntimeException("woops")), RemoteFailure(s4.amount, Seq(hop_ab, hop_bc), Sphinx.DecryptedFailurePacket(carol, UnknownNextPeer()))), 320 unixms)) val ss4 = s4.copy(status = OutgoingPaymentStatus.Failed(Seq(FailureSummary(FailureType.LOCAL, "woops", List(HopSummary(alice, bob, Some(ShortChannelId(42)))), Some(alice)), FailureSummary(FailureType.REMOTE, "processing node does not know the next peer in the route", List(HopSummary(alice, bob, Some(ShortChannelId(42))), HopSummary(bob, carol, None)), Some(carol))), 320 unixms)) assert(db.getOutgoingPayment(s4.id).contains(ss4)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/PendingCommandsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/PendingCommandsDbSpec.scala index d2ec57a89f..705252566f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/PendingCommandsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/PendingCommandsDbSpec.scala @@ -29,13 +29,11 @@ import org.scalatest.funsuite.AnyFunSuite import scala.util.Random - class PendingCommandsDbSpec extends AnyFunSuite { import PendingCommandsDbSpec._ import fr.acinq.eclair.TestDatabases.{forAllDbs, migrationCheck} - test("init database two times in a row") { forAllDbs { case sqlite: TestSqliteDatabases => @@ -137,7 +135,7 @@ object PendingCommandsDbSpec { val cmds = (0 until Random.nextInt(5)).map { _ => Random.nextInt(2) match { case 0 => CMD_FULFILL_HTLC(Random.nextLong(100_000), randomBytes32()) - case 1 => CMD_FAIL_HTLC(Random.nextLong(100_000), Right(UnknownNextPeer)) + case 1 => CMD_FAIL_HTLC(Random.nextLong(100_000), Right(UnknownNextPeer())) } } cmds.map(cmd => TestCase(channelId, cmd)) 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 9586d5053d..6d3558331f 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 @@ -279,7 +279,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { assert(failed.id == paymentId) assert(failed.paymentHash == htlc.paymentHash) assert(failed.failures.nonEmpty) - assert(failed.failures.head.asInstanceOf[RemoteFailure].e == DecryptedFailurePacket(nodes("C").nodeParams.nodeId, PermanentChannelFailure)) + assert(failed.failures.head.asInstanceOf[RemoteFailure].e == DecryptedFailurePacket(nodes("C").nodeParams.nodeId, PermanentChannelFailure())) // we then generate enough blocks to confirm all delayed transactions generateBlocks(25, Some(minerAddress)) // C should have 2 recv transactions: its main output and the htlc timeout @@ -331,7 +331,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { assert(failed.id == paymentId) assert(failed.paymentHash == htlc.paymentHash) assert(failed.failures.nonEmpty) - assert(failed.failures.head.asInstanceOf[RemoteFailure].e == DecryptedFailurePacket(nodes("C").nodeParams.nodeId, PermanentChannelFailure)) + assert(failed.failures.head.asInstanceOf[RemoteFailure].e == DecryptedFailurePacket(nodes("C").nodeParams.nodeId, PermanentChannelFailure())) // we then generate enough blocks to confirm all delayed transactions generateBlocks(25, Some(minerAddress)) // C should have 2 recv transactions: its main output and the htlc timeout 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 ea1ac36fda..527aa7fd28 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 @@ -166,7 +166,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcScidAlias = true, paymentWithoutHint = Left(Left(RouteNotFound)), // alice can't find a route to carol because bob-carol isn't announced paymentWithHint_opt = Some(Right(Ok)), // with a routing hint the payment works - paymentWithRealScidHint_opt = Some(Left(Right(UnknownNextPeer))) // if alice uses the real scid instead of the bob-carol alias, it doesn't work due to option_scid_alias + paymentWithRealScidHint_opt = Some(Left(Right(UnknownNextPeer()))) // if alice uses the real scid instead of the bob-carol alias, it doesn't work due to option_scid_alias ) } @@ -207,7 +207,7 @@ class ZeroConfAliasIntegrationSpec extends FixtureSpec with IntegrationPatience bcScidAlias = true, paymentWithoutHint = Left(Left(RouteNotFound)), // alice can't find a route to carol because bob-carol isn't announced paymentWithHint_opt = Some(Right(Ok)), // with a routing hint the payment works - paymentWithRealScidHint_opt = Some(Left(Right(UnknownNextPeer))) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias + paymentWithRealScidHint_opt = Some(Left(Right(UnknownNextPeer()))) // if alice uses the real scid instead of the b-c alias, it doesn't work due to option_scid_alias ) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala index cb64e22af3..7b2993ba4e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala @@ -532,8 +532,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val commands = f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil assert(commands.toSet == Set( - Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true)), - Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(1, Right(PaymentTimeout), commit = true)) + Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout()), commit = true)), + Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(1, Right(PaymentTimeout()), commit = true)) )) awaitCond({ f.sender.send(handler, GetPendingPayments) @@ -541,8 +541,8 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike }) // Extraneous HTLCs should be failed. - f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None)), Some(PaymentTimeout))) - f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, Right(PaymentTimeout), commit = true))) + f.sender.send(handler, MultiPartPaymentFSM.ExtraPaymentReceived(pr1.paymentHash, HtlcPart(1000 msat, UpdateAddHtlc(ByteVector32.One, 42, 200 msat, pr1.paymentHash, add1.cltvExpiry, add1.onionRoutingPacket, None)), Some(PaymentTimeout()))) + f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, Right(PaymentTimeout()), commit = true))) // The payment should still be pending in DB. val Some(incomingPayment) = nodeParams.db.payments.getIncomingPayment(pr1.paymentHash) @@ -629,7 +629,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None) f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createMultiPartPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata))) - f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout), commit = true))) + f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(0, Right(PaymentTimeout()), commit = true))) awaitCond({ f.sender.send(handler, GetPendingPayments) f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala index 6e2844f084..2f8f3dfa22 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentFSMSpec.scala @@ -59,7 +59,7 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike { val CurrentState(_, WAITING_FOR_HTLC) = monitor.expectMsgClass(classOf[CurrentState[_]]) val Transition(_, WAITING_FOR_HTLC, PAYMENT_FAILED) = monitor.expectMsgClass(classOf[Transition[_]]) - f.parent.expectMsg(MultiPartPaymentFailed(paymentHash, protocol.PaymentTimeout, Queue.empty)) + f.parent.expectMsg(MultiPartPaymentFailed(paymentHash, protocol.PaymentTimeout(), Queue.empty)) f.parent.expectNoMessage(50 millis) f.eventListener.expectNoMessage(50 millis) } @@ -74,7 +74,7 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike { val fail = f.parent.expectMsgType[MultiPartPaymentFailed] assert(fail.paymentHash == paymentHash) - assert(fail.failure == protocol.PaymentTimeout) + assert(fail.failure == protocol.PaymentTimeout()) assert(fail.parts.toSet == parts.toSet) f.parent.expectNoMessage(50 millis) @@ -91,7 +91,7 @@ class MultiPartPaymentFSMSpec extends TestKitBaseClass with AnyFunSuiteLike { f.parent.send(f.handler, extraPart) val fail = f.parent.expectMsgType[ExtraPaymentReceived[PaymentPart]] assert(fail.paymentHash == paymentHash) - assert(fail.failure == Some(protocol.PaymentTimeout)) + assert(fail.failure == Some(protocol.PaymentTimeout())) assert(fail.payment == extraPart) f.parent.expectNoMessage(50 millis) 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 ef13fd1ce7..b2101e107d 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 @@ -187,7 +187,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS childPayFsm.expectNoMessage(100 millis) val childId = payFsm.stateData.asInstanceOf[PaymentProgress].pending.keys.head - childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(failingRoute.amount, failingRoute.hops, Sphinx.DecryptedFailurePacket(b, PermanentChannelFailure))))) + childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(failingRoute.amount, failingRoute.hops, Sphinx.DecryptedFailurePacket(b, PermanentChannelFailure()))))) // We retry ignoring the failing channel. router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, routeParams = routeParams.copy(randomize = true), allowMultiPart = true, ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_be, b, e))), paymentContext = Some(cfg.paymentContext))) router.send(payFsm, RouteResponse(Seq(Route(400000 msat, hop_ac_1 :: hop_ce :: Nil), Route(600000 msat, hop_ad :: hop_de :: Nil)))) @@ -220,12 +220,12 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS childPayFsm.expectNoMessage(100 millis) val (failedId1, failedRoute1) :: (failedId2, failedRoute2) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq - childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(RemoteFailure(failedRoute1.amount, failedRoute1.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure))))) + childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(RemoteFailure(failedRoute1.amount, failedRoute1.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure()))))) // When we retry, we ignore the failing node and we let the router know about the remaining pending route. router.expectMsg(RouteRequest(nodeParams.nodeId, e, failedRoute1.amount, maxFee - failedRoute1.fee(false), ignore = Ignore(Set(b), Set.empty), pendingPayments = Seq(failedRoute2), allowMultiPart = true, routeParams = routeParams.copy(randomize = true), paymentContext = Some(cfg.paymentContext))) // The second part fails while we're still waiting for new routes. - childPayFsm.send(payFsm, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure))))) + childPayFsm.send(payFsm, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure()))))) // We receive a response to our first request, but it's now obsolete: we re-sent a new route request that takes into // account the latest failures. router.send(payFsm, RouteResponse(Seq(Route(failedRoute1.amount, hop_ac_1 :: hop_ce :: Nil)))) @@ -511,7 +511,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(UnreadableRemoteFailure(failedRoute1.amount, failedRoute1.hops)))) router.expectMsgType[RouteRequest] - val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout))))) + val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout()))))) assert(result.failures.length == 2) } @@ -546,7 +546,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS childPayFsm.expectMsgType[SendPaymentToRoute] val (failedId, failedRoute) :: (successId, successRoute) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq - childPayFsm.send(payFsm, PaymentFailed(failedId, paymentHash, Seq(RemoteFailure(failedRoute.amount, failedRoute.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout))))) + childPayFsm.send(payFsm, PaymentFailed(failedId, paymentHash, Seq(RemoteFailure(failedRoute.amount, failedRoute.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout()))))) awaitCond(payFsm.stateName == PAYMENT_ABORTED) sender.watch(payFsm) @@ -584,7 +584,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(payFsm.stateName == PAYMENT_SUCCEEDED) sender.watch(payFsm) - childPayFsm.send(payFsm, PaymentFailed(failedId, paymentHash, Seq(RemoteFailure(failedRoute.amount, failedRoute.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout))))) + childPayFsm.send(payFsm, PaymentFailed(failedId, paymentHash, Seq(RemoteFailure(failedRoute.amount, failedRoute.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout()))))) val result = sender.expectMsgType[PaymentSent] assert(result.parts.length == 1 && result.parts.head.id == childId) assert(result.amountWithFees < finalAmount) // we got the preimage without paying the full amount @@ -635,7 +635,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS assert(payFsm.stateData.asInstanceOf[PaymentAborted].pending.size == pending.size - 1) // Fail all remaining child payments. payFsm.stateData.asInstanceOf[PaymentAborted].pending.foreach(childId => - childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(pending(childId).amount, hop_ab_1 :: hop_be :: Nil, Sphinx.DecryptedFailurePacket(e, PaymentTimeout))))) + childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(pending(childId).amount, hop_ab_1 :: hop_be :: Nil, Sphinx.DecryptedFailurePacket(e, PaymentTimeout()))))) ) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala index f513d49706..68c405ab47 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala @@ -397,7 +397,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike sender.expectMsgType[PaymentIsPending] // Simulate a failure which should trigger a retry. - multiPartPayFsm.send(initiator, PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient))))) + multiPartPayFsm.send(initiator, PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient()))))) multiPartPayFsm.expectMsgType[SendPaymentConfig] val msg2 = multiPartPayFsm.expectMsgType[SendMultiPartPayment] assert(msg2.totalAmount == finalAmount + 25000.msat) @@ -429,13 +429,13 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike assert(msg1.totalAmount == finalAmount + 21000.msat) // Simulate a failure which should trigger a retry. - multiPartPayFsm.send(initiator, PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient))))) + multiPartPayFsm.send(initiator, PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient()))))) multiPartPayFsm.expectMsgType[SendPaymentConfig] val msg2 = multiPartPayFsm.expectMsgType[SendMultiPartPayment] assert(msg2.totalAmount == finalAmount + 25000.msat) // Simulate a failure that exhausts payment attempts. - val failed = PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg2.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure)))) + val failed = PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg2.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure())))) multiPartPayFsm.send(initiator, failed) sender.expectMsg(failed) eventListener.expectMsg(failed) @@ -455,7 +455,7 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val msg1 = multiPartPayFsm.expectMsgType[SendMultiPartPayment] assert(msg1.totalAmount == finalAmount + 21000.msat) // Trampoline node couldn't find a route for the given fee. - val failed = PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient)))) + val failed = PaymentFailed(cfg.parentId, invoice.paymentHash, Seq(RemoteFailure(msg1.totalAmount, Nil, Sphinx.DecryptedFailurePacket(b, TrampolineFeeInsufficient())))) multiPartPayFsm.send(initiator, failed) multiPartPayFsm.expectMsgType[SendPaymentConfig] val msg2 = multiPartPayFsm.expectMsgType[SendMultiPartPayment] 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 3bbcd723cb..dc3131a594 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 @@ -662,7 +662,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec { } test("payment failed (PermanentChannelFailure)") { routerFixture => - testPermanentFailure(routerFixture.router, PermanentChannelFailure) + testPermanentFailure(routerFixture.router, PermanentChannelFailure()) } test("payment failed (deprecated permanent failure)") { routerFixture => @@ -765,14 +765,14 @@ class PaymentLifecycleSpec extends BaseRouterSpec { test("filter errors properly") { () => val failures = Seq( LocalFailure(defaultAmountMsat, Nil, RouteNotFound), - RemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, Sphinx.DecryptedFailurePacket(a, TemporaryNodeFailure)), + RemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, Sphinx.DecryptedFailurePacket(a, TemporaryNodeFailure())), LocalFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, ChannelUnavailable(ByteVector32.Zeroes)), LocalFailure(defaultAmountMsat, Nil, RouteNotFound) ) val filtered = PaymentFailure.transformForUser(failures) val expected = Seq( LocalFailure(defaultAmountMsat, Nil, RouteNotFound), - RemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, Sphinx.DecryptedFailurePacket(a, TemporaryNodeFailure)), + RemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, Sphinx.DecryptedFailurePacket(a, TemporaryNodeFailure())), LocalFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, ChannelUnavailable(ByteVector32.Zeroes)) ) assert(filtered == expected) @@ -788,10 +788,10 @@ class PaymentLifecycleSpec extends BaseRouterSpec { // remote failure from final recipient -> all intermediate nodes behaved correctly (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(d, IncorrectOrUnknownPaymentDetails(100 msat, BlockHeight(42)))), Set.empty, Set.empty), // remote failures from intermediate nodes -> depending on the failure, ignore either the failing node or its outgoing channel - (RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, PermanentNodeFailure)), Set(b), Set.empty), - (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, PermanentNodeFailure())), Set(b), Set.empty), + (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), // unreadable remote failures -> blacklist all nodes except our direct peer and the final recipient (UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil), Set.empty, Set.empty), @@ -805,8 +805,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec { } val failures = Seq( - RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(c, TemporaryNodeFailure)), - RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, UnknownNextPeer)), + RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(c, TemporaryNodeFailure())), + RemoteFailure(defaultAmountMsat, route_abcd, Sphinx.DecryptedFailurePacket(b, UnknownNextPeer())), LocalFailure(defaultAmountMsat, route_abcd, new RuntimeException("fatal")) ) val ignore = PaymentFailure.updateIgnored(failures, Ignore.empty) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index 92b304967b..7fe489e4be 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -122,19 +122,19 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // channel 1 goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments))) val fails_ab_1 = channel.expectMsgType[CMD_FAIL_HTLC] :: channel.expectMsgType[CMD_FAIL_HTLC] :: Nil - assert(fails_ab_1.toSet == Set(CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure), commit = true))) + assert(fails_ab_1.toSet == Set(CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure()), commit = true))) channel.expectNoMessage(100 millis) // channel 2 goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, channels(1).commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels(1).commitments))) val fails_ab_2 = channel.expectMsgType[CMD_FAIL_HTLC] :: channel.expectMsgType[CMD_FAIL_HTLC] :: Nil - assert(fails_ab_2.toSet == Set(CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure), commit = true))) + assert(fails_ab_2.toSet == Set(CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure()), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure()), commit = true))) channel.expectNoMessage(100 millis) // let's assume that channel 1 was disconnected before having signed the fails, and gets connected again: system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments))) val fails_ab_1_bis = channel.expectMsgType[CMD_FAIL_HTLC] :: channel.expectMsgType[CMD_FAIL_HTLC] :: Nil - assert(fails_ab_1_bis.toSet == Set(CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure), commit = true))) + assert(fails_ab_1_bis.toSet == Set(CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true), CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure()), commit = true))) channel.expectNoMessage(100 millis) // let's now assume that channel 1 gets reconnected, and it had the time to fail the htlcs: @@ -184,10 +184,10 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // channel 1 goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments))) val expected1 = Set( - CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure), commit = true), + CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure()), commit = true), CMD_FULFILL_HTLC(3, preimage, commit = true), CMD_FULFILL_HTLC(5, preimage, commit = true), - CMD_FAIL_HTLC(7, Right(TemporaryNodeFailure), commit = true) + CMD_FAIL_HTLC(7, Right(TemporaryNodeFailure()), commit = true) ) val received1 = expected1.map(_ => channel.expectMsgType[Command]) assert(received1 == expected1) @@ -196,10 +196,10 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // channel 2 goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, channels(1).channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels(1).commitments))) val expected2 = Set( - CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure), commit = true), - CMD_FAIL_HTLC(3, Right(TemporaryNodeFailure), commit = true), + CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true), + CMD_FAIL_HTLC(3, Right(TemporaryNodeFailure()), commit = true), CMD_FULFILL_HTLC(4, preimage, commit = true), - CMD_FAIL_HTLC(9, Right(TemporaryNodeFailure), commit = true) + CMD_FAIL_HTLC(9, Right(TemporaryNodeFailure()), commit = true) ) val received2 = expected2.map(_ => channel.expectMsgType[Command]) assert(received2 == expected2) @@ -405,8 +405,8 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit system.eventStream.publish(ChannelStateChanged(channel_upstream_3.ref, data_upstream_3.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(data_upstream_3.commitments))) // Payment 1 should fail instantly. - channel_upstream_1.expectMsg(CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure), commit = true)) - channel_upstream_2.expectMsg(CMD_FAIL_HTLC(7, Right(TemporaryNodeFailure), commit = true)) + channel_upstream_1.expectMsg(CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure()), commit = true)) + channel_upstream_2.expectMsg(CMD_FAIL_HTLC(7, Right(TemporaryNodeFailure()), commit = true)) channel_upstream_1.expectNoMessage(100 millis) channel_upstream_2.expectNoMessage(100 millis) @@ -434,7 +434,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit val channelData = ChannelCodecsSpec.makeChannelDataNormal(htlc_ab, Map.empty) nodeParams.db.channels.addOrUpdateChannel(channelData) nodeParams.db.pendingCommands.addSettlementCommand(channelId_ab_1, CMD_FULFILL_HTLC(1, randomBytes32())) - nodeParams.db.pendingCommands.addSettlementCommand(channelId_ab_1, CMD_FAIL_HTLC(4, Right(PermanentChannelFailure))) + nodeParams.db.pendingCommands.addSettlementCommand(channelId_ab_1, CMD_FAIL_HTLC(4, Right(PermanentChannelFailure()))) val (_, postRestart) = f.createRelayer(nodeParams) sender.send(postRestart, PostRestartHtlcCleaner.GetBrokenHtlcs) @@ -529,7 +529,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.origin_1)) val fails = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil assert(fails.toSet == testCase.origin_1.htlcs.map { - case (channelId, htlcId) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true)) + case (channelId, htlcId) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), commit = true)) }.toSet) sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.origin_1)) @@ -541,7 +541,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.origin_2)) register.expectMsg(testCase.origin_2.htlcs.map { - case (channelId, htlcId) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure), commit = true)) + case (channelId, htlcId) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), commit = true)) }.head) register.expectNoMessage(100 millis) @@ -645,7 +645,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // Non-standard channel goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, channelId_ab_1, system.deadLetters, a, OFFLINE, NORMAL, Some(cs))) - channel.expectMsg(CMD_FAIL_HTLC(1L, Right(TemporaryNodeFailure), commit = true)) + channel.expectMsg(CMD_FAIL_HTLC(1L, Right(TemporaryNodeFailure()), commit = true)) channel.expectNoMessage(100 millis) } @@ -676,7 +676,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // Standard channel goes to NORMAL state: system.eventStream.publish(ChannelStateChanged(channel.ref, c.commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(c.commitments))) - channel.expectMsg(CMD_FAIL_HTLC(1L, Right(TemporaryNodeFailure), commit = true)) + channel.expectMsg(CMD_FAIL_HTLC(1L, Right(TemporaryNodeFailure()), commit = true)) channel.expectNoMessage(100 millis) } 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 e02eff4ada..c391c98f22 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 @@ -91,7 +91,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a if (success) { expectFwdAdd(register, lcu.channelId, outgoingAmount, outgoingExpiry) } else { - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) } } @@ -193,7 +193,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a 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)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryNodeFailure()), commit = true)) } test("fail to relay when we have no channel_update for the next channel") { f => @@ -204,7 +204,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) } test("fail to relay when register returns an error") { f => @@ -220,7 +220,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a 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)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) } test("fail to relay when the channel is advertised as unusable (down)") { f => @@ -235,7 +235,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a channelRelayer ! WrappedLocalChannelDown(d) channelRelayer ! Relay(r) - expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer), commit = true)) + expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)) } test("fail to relay when channel is disabled") { f => @@ -347,7 +347,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val testCases = Seq( TestCase(ExpiryTooSmall(channelId1, CltvExpiry(100), CltvExpiry(0), BlockHeight(0)), u.channelUpdate, ExpiryTooSoon(u.channelUpdate)), - TestCase(ExpiryTooBig(channelId1, CltvExpiry(100), CltvExpiry(200), BlockHeight(0)), u.channelUpdate, ExpiryTooFar), + TestCase(ExpiryTooBig(channelId1, CltvExpiry(100), CltvExpiry(200), BlockHeight(0)), u.channelUpdate, ExpiryTooFar()), 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)) @@ -458,9 +458,9 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val testCases = Seq( 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)), CMD_FAIL_MALFORMED_HTLC(r.add.id, ByteVector32.One, FailureMessageCodecs.BADONION, commit = true)), - TestCase(HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId1, downstream_htlc)), CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure), 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.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure), commit = true)) + TestCase(HtlcResult.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure()), commit = true)) ) testCases.foreach { testCase => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala index 78b52cf716..fc28fcefd9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala @@ -203,7 +203,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incomingMultiPart.dropRight(1).foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]](30 seconds) assert(fwd.channelId == p.add.channelId) - val failure = Right(PaymentTimeout) + val failure = Right(PaymentTimeout()) assert(fwd.message == CMD_FAIL_HTLC(p.add.id, failure, commit = true)) } @@ -286,7 +286,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon()), commit = true)) register.expectNoMessage(100 millis) } @@ -302,7 +302,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon()), commit = true)) register.expectNoMessage(100 millis) } @@ -323,7 +323,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl p.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineExpiryTooSoon()), commit = true)) } register.expectNoMessage(100 millis) @@ -346,7 +346,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incomingAsyncPayment.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure()), commit = true)) } register.expectNoMessage(100 millis) } @@ -408,7 +408,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incomingAsyncPayment.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure()), commit = true)) } register.expectNoMessage(100 millis) @@ -430,7 +430,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incomingAsyncPayment.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure()), commit = true)) } register.expectNoMessage(100 millis) } @@ -478,7 +478,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient()), commit = true)) register.expectNoMessage(100 millis) } @@ -496,7 +496,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl p.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient()), commit = true)) } register.expectNoMessage(100 millis) @@ -551,7 +551,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incomingMultiPart.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient()), commit = true)) } register.expectNoMessage(100 millis) @@ -576,7 +576,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl incoming.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TemporaryNodeFailure()), commit = true)) } register.expectNoMessage(100 millis) @@ -593,13 +593,13 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl router.expectMessageType[RouteRequest] // If we're having a hard time finding routes, raising the fee/cltv will likely help. - val failures = LocalFailure(outgoingAmount, Nil, RouteNotFound) :: RemoteFailure(outgoingAmount, Nil, Sphinx.DecryptedFailurePacket(outgoingNodeId, PermanentNodeFailure)) :: LocalFailure(outgoingAmount, Nil, RouteNotFound) :: Nil + val failures = LocalFailure(outgoingAmount, Nil, RouteNotFound) :: RemoteFailure(outgoingAmount, Nil, Sphinx.DecryptedFailurePacket(outgoingNodeId, PermanentNodeFailure())) :: LocalFailure(outgoingAmount, Nil, RouteNotFound) :: Nil payFSM ! PaymentFailed(relayId, incomingMultiPart.head.add.paymentHash, failures) incomingMultiPart.foreach { p => val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]] assert(fwd.channelId == p.add.channelId) - assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient), commit = true)) + assert(fwd.message == CMD_FAIL_HTLC(p.add.id, Right(TrampolineFeeInsufficient()), commit = true)) } register.expectNoMessage(100 millis) 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 d06d3f55fd..e95d6906fb 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 @@ -174,7 +174,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]].message assert(fail.id == add_ab.id) - assert(fail.reason == Right(RequiredNodeFeatureMissing)) + assert(fail.reason == Right(RequiredNodeFeatureMissing())) register.expectNoMessage(50 millis) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/CommandCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/CommandCodecsSpec.scala index b38cee448a..7ec5b857aa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/CommandCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/CommandCodecsSpec.scala @@ -16,16 +16,12 @@ package fr.acinq.eclair.wire.internal +import fr.acinq.bitcoin.scalacompat.ByteVector32 +import fr.acinq.eclair.UInt64 import fr.acinq.eclair.channel._ -import fr.acinq.eclair.wire.protocol.CommonCodecs.{bytes32, varsizebinarydata} -import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.failureMessageCodec -import fr.acinq.eclair.wire.protocol.{FailureMessageCodecs, TemporaryNodeFailure} -import fr.acinq.eclair.{randomBytes, randomBytes32} +import fr.acinq.eclair.wire.protocol.{FailureMessageCodecs, GenericTlv, TemporaryNodeFailure, TlvStream} import org.scalatest.funsuite.AnyFunSuite -import scodec.DecodeResult -import scodec.bits.BitVector -import scodec.codecs._ -import shapeless.HNil +import scodec.bits.{ByteVector, HexStringSyntax} /** * Created by PM on 31/05/2016. @@ -33,46 +29,37 @@ import shapeless.HNil class CommandCodecsSpec extends AnyFunSuite { - test("encode/decode all channel messages") { - val msgs: List[HtlcSettlementCommand] = - CMD_FULFILL_HTLC(1573L, randomBytes32()) :: - CMD_FAIL_HTLC(42456L, Left(randomBytes(145))) :: - CMD_FAIL_HTLC(253, Right(TemporaryNodeFailure)) :: - CMD_FAIL_MALFORMED_HTLC(7984, randomBytes32(), FailureMessageCodecs.BADONION) :: Nil + test("encode/decode all settlement commands") { + val testCases: Map[HtlcSettlementCommand, ByteVector] = Map( + CMD_FULFILL_HTLC(1573, ByteVector32(hex"e64e7c07667366e517886af99a25a5dd547014c95ba392ea4623fbf47fe00927")) -> hex"0000 0000000000000625 e64e7c07667366e517886af99a25a5dd547014c95ba392ea4623fbf47fe00927", + CMD_FAIL_HTLC(42456, Left(hex"d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44")) -> hex"0003 000000000000a5d8 00 0091 d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44", + CMD_FAIL_HTLC(253, Right(TemporaryNodeFailure())) -> hex"0003 00000000000000fd ff 0002 2002", + CMD_FAIL_HTLC(253, Right(TemporaryNodeFailure(TlvStream(Nil, Seq(GenericTlv(UInt64(17), hex"deadbeef")))))) -> hex"0003 00000000000000fd ff 0008 2002 1104deadbeef", + CMD_FAIL_MALFORMED_HTLC(7984, ByteVector32(hex"17cc093e177c7a7fcaa9e96ab407146c8886546a5690f945c98ac20c4ab3b4f3"), FailureMessageCodecs.BADONION) -> hex"0002 0000000000001f30 17cc093e177c7a7fcaa9e96ab407146c8886546a5690f945c98ac20c4ab3b4f38000", + ) - msgs.foreach { - msg => - val encoded = CommandCodecs.cmdCodec.encode(msg).require - val decoded = CommandCodecs.cmdCodec.decode(encoded).require - assert(msg == decoded.value) + testCases.foreach { case (command, bin) => + val encoded = CommandCodecs.cmdCodec.encode(command).require.bytes + assert(encoded == bin) + val decoded = CommandCodecs.cmdCodec.decode(bin.bits).require.value + assert(command == decoded) } } test("backward compatibility") { - val data32 = randomBytes32() - val data123 = randomBytes(123) + val data32 = ByteVector32(hex"e4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb") + val data123 = hex"fea75bb8cf45349eb544d8da832af5af30eefa671ec27cf2e4867bacada2dbe00a6ce5141164aa153ac8b4b25c75c3af15c4b5cb6a293607751a079bc546da17f654b76a74bc57b6b21ed73d2d3909f3682f01b85418a0f0ecddb759e9481d4563a572ac1ddcb77c64ae167d8dfbd889703cb5c33b4b9636bad472" + val testCases = Map( + hex"0000 000000000000002ae4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb" -> CMD_FULFILL_HTLC(42, data32, commit = false, None), + hex"0001 000000000000002a003dff53addc67a29a4f5aa26c6d41957ad798777d338f613e7972433dd656d16df00536728a08b2550a9d645a592e3ae1d78ae25ae5b5149b03ba8d03cde2a36d0bfb2a5bb53a5e2bdb590f6b9e969c84f9b41780dc2a0c5078766edbacf4a40ea2b1d2b9560eee5bbe32570b3ec6fdec44b81e5ae19da5cb1b5d6a3900" -> CMD_FAIL_HTLC(42, Left(data123), commit = false, None), + hex"0001 000000000000002a900100" -> CMD_FAIL_HTLC(42, Right(TemporaryNodeFailure())), + hex"0002 000000000000002ae4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb01c8" -> CMD_FAIL_MALFORMED_HTLC(42, data32, 456, None, commit = false, None), + ) - val legacyCmdFulfillCodec = - ("id" | int64) :: - ("r" | bytes32) :: - ("commit" | provide(false)) - assert(CommandCodecs.cmdFulfillCodec.decode(legacyCmdFulfillCodec.encode(42 :: data32 :: true :: HNil).require).require == - DecodeResult(CMD_FULFILL_HTLC(42, data32, commit = false, None), BitVector.empty)) - - val legacyCmdFailCodec = - ("id" | int64) :: - ("reason" | either(bool, varsizebinarydata, failureMessageCodec)) :: - ("commit" | provide(false)) - assert(CommandCodecs.cmdFailCodec.decode(legacyCmdFailCodec.encode(42 :: Left(data123) :: true :: HNil).require).require == - DecodeResult(CMD_FAIL_HTLC(42, Left(data123), commit = false, None), BitVector.empty)) - - val legacyCmdFailMalformedCodec = - ("id" | int64) :: - ("onionHash" | bytes32) :: - ("failureCode" | uint16) :: - ("commit" | provide(false)) - assert(CommandCodecs.cmdFailMalformedCodec.decode(legacyCmdFailMalformedCodec.encode(42 :: data32 :: 456 :: true :: HNil).require).require == - DecodeResult(CMD_FAIL_MALFORMED_HTLC(42, data32, 456, None, commit = false, None), BitVector.empty)) + testCases.foreach { case (bin, command) => + val decoded = CommandCodecs.cmdCodec.decode(bin.bits).require + assert(decoded.value == command) + } } } 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 50ea59776d..081e443078 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 @@ -19,7 +19,7 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, ByteVector64} import fr.acinq.eclair.crypto.Hmac256 import fr.acinq.eclair.wire.protocol.FailureMessageCodecs._ -import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, UInt64, randomBytes32, randomBytes64} +import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, ShortChannelId, TimestampSecond, TimestampSecondLong, UInt64, randomBytes32} import org.scalatest.funsuite.AnyFunSuite import scodec.bits._ @@ -28,34 +28,54 @@ import scodec.bits._ */ class FailureMessageCodecsSpec extends AnyFunSuite { - val channelUpdate = ChannelUpdate( - signature = randomBytes64(), - chainHash = Block.RegtestGenesisBlock.hash, - shortChannelId = ShortChannelId(12345), - timestamp = TimestampSecond(1234567L), - cltvExpiryDelta = CltvExpiryDelta(100), - messageFlags = ChannelUpdate.MessageFlags(dontForward = false), - channelFlags = ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), - htlcMinimumMsat = 1000 msat, - feeBaseMsat = 12 msat, - feeProportionalMillionths = 76, - htlcMaximumMsat = 150_000_000 msat) test("encode/decode all failure messages") { - val msgs: List[FailureMessage] = - InvalidRealm :: TemporaryNodeFailure :: PermanentNodeFailure :: RequiredNodeFeatureMissing :: - InvalidOnionVersion(randomBytes32()) :: InvalidOnionHmac(randomBytes32()) :: InvalidOnionKey(randomBytes32()) :: - TemporaryChannelFailure(channelUpdate) :: PermanentChannelFailure :: RequiredChannelFeatureMissing :: UnknownNextPeer :: - AmountBelowMinimum(123456 msat, channelUpdate) :: FeeInsufficient(546463 msat, channelUpdate) :: IncorrectCltvExpiry(CltvExpiry(1211), channelUpdate) :: ExpiryTooSoon(channelUpdate) :: - IncorrectOrUnknownPaymentDetails(123456 msat, BlockHeight(1105)) :: FinalIncorrectCltvExpiry(CltvExpiry(1234)) :: ChannelDisabled(ChannelUpdate.MessageFlags(dontForward = false), ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), channelUpdate) :: ExpiryTooFar :: InvalidOnionPayload(UInt64(561), 1105) :: PaymentTimeout :: - TrampolineFeeInsufficient :: TrampolineExpiryTooSoon :: Nil - - msgs.foreach { - msg => { - val encoded = failureMessageCodec.encode(msg).require - val decoded = failureMessageCodec.decode(encoded).require - assert(msg == decoded.value) - } + val channelUpdate = ChannelUpdate( + signature = ByteVector64(hex"3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e620"), + chainHash = Block.RegtestGenesisBlock.hash, + shortChannelId = ShortChannelId(12345), + timestamp = TimestampSecond(1234567L), + cltvExpiryDelta = CltvExpiryDelta(100), + messageFlags = ChannelUpdate.MessageFlags(dontForward = false), + channelFlags = ChannelUpdate.ChannelFlags(isEnabled = true, isNode1 = false), + htlcMinimumMsat = 1000 msat, + feeBaseMsat = 12 msat, + feeProportionalMillionths = 76, + htlcMaximumMsat = 150_000_000 msat) + val testCases = Map[FailureMessage, ByteVector]( + InvalidRealm() -> hex"4001", + TemporaryNodeFailure() -> hex"2002", + TemporaryNodeFailure(TlvStream(Nil, Seq(GenericTlv(UInt64(561), hex"deadbeef"), GenericTlv(UInt64(1105), hex"0102030405")))) -> hex"2002 fd023104deadbeef fd0451050102030405", + PermanentNodeFailure() -> hex"6002", + RequiredNodeFeatureMissing() -> hex"6003", + InvalidOnionVersion(ByteVector32(hex"d8db0e777047d814f569c8243073be42e56a411ebfe82fd877ba958fb068ae3e")) -> hex"c004 d8db0e777047d814f569c8243073be42e56a411ebfe82fd877ba958fb068ae3e", + 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", + 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", + IncorrectOrUnknownPaymentDetails(123456 msat, BlockHeight(1105)) -> hex"400f 000000000001e240 00000451", + IncorrectOrUnknownPaymentDetails(100 msat, BlockHeight(800_000), TlvStream(Nil, Seq(GenericTlv(UInt64(34001), ByteVector.fill(300)(128))))) -> hex"400f 0000000000000064 000c3500 fd84d1 fd012c 808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080", + ExpiryTooSoon(channelUpdate) -> hex"100e 008a 0102 3eedbba335d4fb4c772c95bf6a0de91b86950fabbb3a9f203beef44f30547f89eaf5dbf434520b4dcab5a0641589aa5483cdfd24902295310383f9ab8835e62006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f00000000000030390012d6870101006400000000000003e80000000c0000004c0000000008f0d180", + FinalIncorrectCltvExpiry(CltvExpiry(1234)) -> hex"0012 000004d2", + FinalIncorrectHtlcAmount(25_000_000 msat) -> hex"0013 00000000017d7840", + ExpiryTooFar() -> hex"0015", + InvalidOnionPayload(UInt64(561), 1105) -> hex"4016 fd0231 0451", + PaymentTimeout() -> hex"0017", + TrampolineFeeInsufficient() -> hex"2033", + TrampolineExpiryTooSoon() -> hex"2034", + ) + testCases.foreach { case (msg, bin) => + val encoded = failureMessageCodec.encode(msg).require + assert(encoded.bytes == bin) + val decoded = failureMessageCodec.decode(encoded).require + assert(msg == decoded.value) } } @@ -127,9 +147,9 @@ class FailureMessageCodecsSpec extends AnyFunSuite { test("decode failure onion packet with arbitrary length") { val codec = failureOnionCodec(Hmac256(ByteVector32.Zeroes)) val testCases = Seq( - InvalidRealm -> hex"7bfb2aa46218240684f623322ae48af431d06986c82e210bb0cee83c7ddb2ba8 0002 4001 0002 0000", + InvalidRealm() -> hex"7bfb2aa46218240684f623322ae48af431d06986c82e210bb0cee83c7ddb2ba8 0002 4001 0002 0000", IncorrectOrUnknownPaymentDetails(1105 msat, BlockHeight(1729)) -> hex"c508151d550a6a7fb121542b7c383fd7f18381832499c419de436e131c1f3a76 000e 400f 0000000000000451 000006c1 0004 deadbeef", - InvalidRealm -> hex"6f9e2c0e44b3692dac37523c6ff054cc9b26ecab1a78ed6906a46848bffc2bd5 0002 4001 00ff 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + InvalidRealm() -> hex"6f9e2c0e44b3692dac37523c6ff054cc9b26ecab1a78ed6906a46848bffc2bd5 0002 4001 00ff 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", IncorrectOrUnknownPaymentDetails(1105 msat, BlockHeight(1729)) -> hex"bb2873dad5447927774cb7de99f43c0b5f54f6e298b5be4d7ca88677b8f0817d 000e 400f 0000000000000451 000006c1 00ff 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", ) @@ -168,4 +188,5 @@ class FailureMessageCodecsSpec extends AnyFunSuite { val u2 = failureMessageCodec.decode(bin.toBitVector).require.value assert(u2 == ref) } + }