mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 14:22:39 +01:00
Refactor Sphinx failures (#2955)
* Return unwrapped decryption failure onion When we fail to decrypt an onion failure packet, we should return the result of the unwrapping process. When using trampoline, this will let us properly re-encrypt the failure and relay it upstream to the previous trampoline node, until it reaches the sender. * Refactor HTLC failure creation We refactor the shared secret extraction to a dedicated function. * Refactor HTLC failure reason We previously used an `Either[ByteVector, FailureMessage]` to encode: - a downstream error that we couldn't decrypt and must re-wrap (left) - a local error that we must encrypt (right) This won't be sufficient for trampoline, because we will need to handle the following cases: - a downstream error that we couldn't decrypt and must re-wrap - a local error for the node who created the *outer* onion (which we encrypt with the sphinx shared secret of the outer onion) - a local error for the node who created the *trampoline* onion (which we encrypt with the sphinx shared secret of the trampoline onion and then with the shared secret of the outer onion) We thus introduce a trait, which currently only contains the first two cases. We will extend this trait when adding support for trampoline failures. This is a pure refactoring without any behavior changes so far, which will simplify the future trampoline changes. * Include trampoline onion in final trampoline payloads When we receive a (non-blinded) payment that uses trampoline, we keep the trampoline onion to be able to distinguish this payment from a non-trampoline payment.
This commit is contained in:
parent
4d930c776b
commit
2ad22602fd
39 changed files with 349 additions and 261 deletions
|
@ -7,7 +7,6 @@ This section contains how-to guides for more advanced scenarios:
|
|||
* [Manage Bitcoin Core's private keys](./ManagingBitcoinCoreKeys.md)
|
||||
* [Use Tor with Eclair](./Tor.md)
|
||||
* [Multipart Payments](./MultipartPayments.md)
|
||||
* [Trampoline Payments](./TrampolinePayments.md)
|
||||
* [Monitoring Eclair](./Monitoring.md)
|
||||
* [PostgreSQL Configuration](./PostgreSQL.md)
|
||||
* [Perform Circular Rebalancing](./CircularRebalancing.md)
|
||||
|
|
|
@ -26,7 +26,7 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS
|
|||
import fr.acinq.eclair.io.Peer
|
||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureMessage, FundingCreated, FundingSigned, Init, LiquidityAds, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, Stfu, TxInitRbf, TxSignatures, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureReason, FundingCreated, FundingSigned, Init, LiquidityAds, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, Stfu, TxInitRbf, TxSignatures, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, TimestampMilli, UInt64}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
@ -215,7 +215,7 @@ final case class CMD_ADD_HTLC(replyTo: ActorRef,
|
|||
|
||||
sealed trait HtlcSettlementCommand extends HasOptionalReplyToCommand with ForbiddenCommandDuringQuiescenceNegotiation with ForbiddenCommandWhenQuiescent { def id: Long }
|
||||
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand
|
||||
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], delay_opt: Option[FiniteDuration] = None, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand
|
||||
final case class CMD_FAIL_HTLC(id: Long, reason: FailureReason, delay_opt: Option[FiniteDuration] = None, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand
|
||||
final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HtlcSettlementCommand
|
||||
final case class CMD_UPDATE_FEE(feeratePerKw: FeeratePerKw, commit: Boolean = false, replyTo_opt: Option[ActorRef] = None) extends HasOptionalReplyToCommand with ForbiddenCommandDuringQuiescenceNegotiation with ForbiddenCommandWhenQuiescent
|
||||
final case class CMD_SIGN(replyTo_opt: Option[ActorRef] = None) extends HasOptionalReplyToCommand with ForbiddenCommandWhenQuiescent
|
||||
|
|
|
@ -643,7 +643,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
|||
case PostRevocationAction.RejectHtlc(add) =>
|
||||
log.debug("rejecting incoming htlc {}", add)
|
||||
// NB: we don't set commit = true, we will sign all updates at once afterwards.
|
||||
self ! CMD_FAIL_HTLC(add.id, Right(TemporaryChannelFailure(Some(d.channelUpdate))), commit = true)
|
||||
self ! CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(TemporaryChannelFailure(Some(d.channelUpdate))), commit = true)
|
||||
case PostRevocationAction.RelayFailure(result) =>
|
||||
log.debug("forwarding {} to relayer", result)
|
||||
relayer ! result
|
||||
|
@ -1544,11 +1544,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
|||
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, FailureReason.LocalFailure(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, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)
|
||||
case PostRevocationAction.RelayFailure(result) =>
|
||||
log.debug("forwarding {} to relayer", result)
|
||||
relayer ! result
|
||||
|
|
|
@ -272,6 +272,13 @@ object Sphinx extends Logging {
|
|||
*/
|
||||
case class DecryptedFailurePacket(originNode: PublicKey, failureMessage: FailureMessage)
|
||||
|
||||
/**
|
||||
* The downstream failure could not be decrypted.
|
||||
*
|
||||
* @param unwrapped encrypted failure packet after unwrapping using our shared secrets.
|
||||
*/
|
||||
case class CannotDecryptFailurePacket(unwrapped: ByteVector)
|
||||
|
||||
object FailurePacket {
|
||||
|
||||
/**
|
||||
|
@ -314,18 +321,18 @@ object Sphinx extends Logging {
|
|||
*
|
||||
* @param packet failure packet.
|
||||
* @param sharedSecrets nodes shared secrets.
|
||||
* @return Success(secret, failure message) if the origin of the packet could be identified and the packet
|
||||
* decrypted, Failure otherwise.
|
||||
* @return failure message if the origin of the packet could be identified and the packet decrypted, the unwrapped
|
||||
* failure packet otherwise.
|
||||
*/
|
||||
def decrypt(packet: ByteVector, sharedSecrets: Seq[(ByteVector32, PublicKey)]): Try[DecryptedFailurePacket] = Try {
|
||||
def decrypt(packet: ByteVector, sharedSecrets: Seq[(ByteVector32, PublicKey)]): Either[CannotDecryptFailurePacket, DecryptedFailurePacket] = {
|
||||
@tailrec
|
||||
def loop(packet: ByteVector, secrets: Seq[(ByteVector32, PublicKey)]): DecryptedFailurePacket = secrets match {
|
||||
case Nil => throw new RuntimeException(s"couldn't parse error packet=$packet with sharedSecrets=$sharedSecrets")
|
||||
def loop(packet: ByteVector, secrets: Seq[(ByteVector32, PublicKey)]): Either[CannotDecryptFailurePacket, DecryptedFailurePacket] = secrets match {
|
||||
case Nil => Left(CannotDecryptFailurePacket(packet))
|
||||
case (secret, pubkey) :: tail =>
|
||||
val packet1 = wrap(packet, secret)
|
||||
val um = generateKey("um", secret)
|
||||
FailureMessageCodecs.failureOnionCodec(Hmac256(um)).decode(packet1.toBitVector) match {
|
||||
case Attempt.Successful(value) => DecryptedFailurePacket(pubkey, value.value)
|
||||
case Attempt.Successful(value) => Right(DecryptedFailurePacket(pubkey, value.value))
|
||||
case _ => loop(packet1, tail)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@ object FailureSummary {
|
|||
def apply(f: PaymentFailure): FailureSummary = f match {
|
||||
case LocalFailure(_, route, t) => FailureSummary(FailureType.LOCAL, t.getMessage, route.map(h => HopSummary(h)).toList, route.headOption.map(_.nodeId))
|
||||
case RemoteFailure(_, route, e) => FailureSummary(FailureType.REMOTE, e.failureMessage.message, route.map(h => HopSummary(h)).toList, Some(e.originNode))
|
||||
case UnreadableRemoteFailure(_, route) => FailureSummary(FailureType.UNREADABLE_REMOTE, "could not decrypt failure onion", route.map(h => HopSummary(h)).toList, None)
|
||||
case UnreadableRemoteFailure(_, route, _) => FailureSummary(FailureType.UNREADABLE_REMOTE, "could not decrypt failure onion", route.map(h => HopSummary(h)).toList, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ import fr.acinq.eclair.remote.EclairInternalsSerializer.RemoteTypes
|
|||
import fr.acinq.eclair.router.Router
|
||||
import fr.acinq.eclair.wire.protocol
|
||||
import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure
|
||||
import fr.acinq.eclair.wire.protocol.{AddFeeCredit, ChannelTlv, CurrentFeeCredit, Error, HasChannelId, HasTemporaryChannelId, LightningMessage, LiquidityAds, NodeAddress, OnTheFlyFundingFailureMessage, OnionMessage, OnionRoutingPacket, RecommendedFeerates, RoutingMessage, SpliceInit, TemporaryChannelFailure, TlvStream, TxAbort, UnknownMessage, Warning, WillAddHtlc, WillFailHtlc, WillFailMalformedHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{AddFeeCredit, ChannelTlv, CurrentFeeCredit, Error, FailureReason, HasChannelId, HasTemporaryChannelId, LightningMessage, LiquidityAds, NodeAddress, OnTheFlyFundingFailureMessage, OnionMessage, OnionRoutingPacket, RecommendedFeerates, RoutingMessage, SpliceInit, TemporaryChannelFailure, TlvStream, TxAbort, UnknownMessage, Warning, WillAddHtlc, WillFailHtlc, WillFailMalformedHtlc}
|
||||
|
||||
/**
|
||||
* This actor represents a logical peer. There is one [[Peer]] per unique remote node id at all time.
|
||||
|
@ -300,8 +300,8 @@ class Peer(val nodeParams: NodeParams,
|
|||
pending.proposed.find(_.htlc.id == msg.id) match {
|
||||
case Some(htlc) =>
|
||||
val failure = msg match {
|
||||
case msg: WillFailHtlc => Left(msg.reason)
|
||||
case msg: WillFailMalformedHtlc => Right(createBadOnionFailure(msg.onionHash, msg.failureCode))
|
||||
case msg: WillFailHtlc => FailureReason.EncryptedDownstreamFailure(msg.reason)
|
||||
case msg: WillFailMalformedHtlc => FailureReason.LocalFailure(createBadOnionFailure(msg.onionHash, msg.failureCode))
|
||||
}
|
||||
htlc.createFailureCommands(Some(failure)).foreach { case (channelId, cmd) => PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, cmd) }
|
||||
val proposed1 = pending.proposed.filterNot(_.htlc.id == msg.id)
|
||||
|
|
|
@ -19,6 +19,7 @@ package fr.acinq.eclair.payment
|
|||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||
import fr.acinq.eclair.MilliSatoshi
|
||||
import fr.acinq.eclair.channel.CMD_FAIL_HTLC
|
||||
import fr.acinq.eclair.wire.protocol.FailureReason
|
||||
import kamon.Kamon
|
||||
|
||||
object Monitoring {
|
||||
|
@ -127,14 +128,14 @@ object Monitoring {
|
|||
val Malformed = "MalformedHtlc"
|
||||
|
||||
def apply(cmdFail: CMD_FAIL_HTLC): String = cmdFail.reason match {
|
||||
case Left(_) => Remote
|
||||
case Right(f) => f.getClass.getSimpleName
|
||||
case _: FailureReason.EncryptedDownstreamFailure => Remote
|
||||
case FailureReason.LocalFailure(f) => f.getClass.getSimpleName
|
||||
}
|
||||
|
||||
def apply(pf: PaymentFailure): String = pf match {
|
||||
case LocalFailure(_, _, t) => t.getClass.getSimpleName
|
||||
case RemoteFailure(_, _, e) => e.failureMessage.getClass.getSimpleName
|
||||
case UnreadableRemoteFailure(_, _) => "UnreadableRemoteFailure"
|
||||
case _: UnreadableRemoteFailure => "UnreadableRemoteFailure"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ case class LocalFailure(amount: MilliSatoshi, route: Seq[Hop], t: Throwable) ext
|
|||
case class RemoteFailure(amount: MilliSatoshi, route: Seq[Hop], e: Sphinx.DecryptedFailurePacket) extends PaymentFailure
|
||||
|
||||
/** A remote node failed the payment but we couldn't decrypt the failure (e.g. a malicious node tampered with the message). */
|
||||
case class UnreadableRemoteFailure(amount: MilliSatoshi, route: Seq[Hop]) extends PaymentFailure
|
||||
case class UnreadableRemoteFailure(amount: MilliSatoshi, route: Seq[Hop], failurePacket: ByteVector) extends PaymentFailure
|
||||
|
||||
object PaymentFailure {
|
||||
|
||||
|
@ -235,7 +235,7 @@ object PaymentFailure {
|
|||
}
|
||||
case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, _)) =>
|
||||
ignoreNodeOutgoingEdge(nodeId, hops, ignore)
|
||||
case UnreadableRemoteFailure(_, hops) =>
|
||||
case UnreadableRemoteFailure(_, hops, _) =>
|
||||
// We don't know which node is sending garbage, let's blacklist all nodes except:
|
||||
// - the one we are directly connected to: it would be too restrictive for retries
|
||||
// - the final recipient: they have no incentive to send garbage since they want that payment
|
||||
|
|
|
@ -238,7 +238,8 @@ object IncomingPaymentPacket {
|
|||
case innerPayload =>
|
||||
// We merge contents from the outer and inner payloads.
|
||||
// We must use the inner payload's total amount and payment secret because the payment may be split between multiple trampoline payments (#reckless).
|
||||
Right(FinalPacket(add, FinalPayload.Standard.createPayload(outerPayload.amount, innerPayload.totalAmount, innerPayload.expiry, innerPayload.paymentSecret, innerPayload.paymentMetadata)))
|
||||
val trampolinePacket = outerPayload.records.get[OnionPaymentPayloadTlv.TrampolineOnion].map(_.packet)
|
||||
Right(FinalPacket(add, FinalPayload.Standard.createPayload(outerPayload.amount, innerPayload.totalAmount, innerPayload.expiry, innerPayload.paymentSecret, innerPayload.paymentMetadata, trampolinePacket)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -334,14 +335,24 @@ object OutgoingPaymentPacket {
|
|||
}
|
||||
}
|
||||
|
||||
private def buildHtlcFailure(nodeSecret: PrivateKey, reason: Either[ByteVector, FailureMessage], add: UpdateAddHtlc): Either[CannotExtractSharedSecret, ByteVector] = {
|
||||
Sphinx.peel(nodeSecret, Some(add.paymentHash), add.onionRoutingPacket) match {
|
||||
case Right(Sphinx.DecryptedPacket(_, _, sharedSecret)) =>
|
||||
val encryptedReason = reason match {
|
||||
case Left(forwarded) => Sphinx.FailurePacket.wrap(forwarded, sharedSecret)
|
||||
case Right(failure) => Sphinx.FailurePacket.create(sharedSecret, failure)
|
||||
private def buildHtlcFailure(nodeSecret: PrivateKey, reason: FailureReason, add: UpdateAddHtlc): Either[CannotExtractSharedSecret, ByteVector] = {
|
||||
extractSharedSecret(nodeSecret, add).map(sharedSecret => {
|
||||
reason match {
|
||||
case FailureReason.EncryptedDownstreamFailure(packet) => Sphinx.FailurePacket.wrap(packet, sharedSecret)
|
||||
case FailureReason.LocalFailure(failure) => Sphinx.FailurePacket.create(sharedSecret, failure)
|
||||
}
|
||||
Right(encryptedReason)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* We decrypt the onion again to extract the shared secret used to encrypt onion failures.
|
||||
* We could avoid this by storing the shared secret after the initial onion decryption, but we would have to store it
|
||||
* in the database since we must be able to fail HTLCs after restarting our node.
|
||||
* It's simpler to extract it again from the encrypted onion.
|
||||
*/
|
||||
private def extractSharedSecret(nodeSecret: PrivateKey, add: UpdateAddHtlc): Either[CannotExtractSharedSecret, ByteVector32] = {
|
||||
Sphinx.peel(nodeSecret, Some(add.paymentHash), add.onionRoutingPacket) match {
|
||||
case Right(Sphinx.DecryptedPacket(_, _, sharedSecret)) => Right(sharedSecret)
|
||||
case Left(_) => Left(CannotExtractSharedSecret(add.channelId, add))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
ctx.self ! ProcessPacket(add, payload, Some(IncomingStandardPayment(invoice, paymentPreimage, PaymentType.KeySend, TimestampMilli.now(), IncomingPaymentStatus.Pending)))
|
||||
case _ =>
|
||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, "InvoiceNotFound").increment()
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
|
||||
case RejectPacket(add, failure) if doHandle(add.paymentHash) =>
|
||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, failure.getClass.getSimpleName).increment()
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, Right(failure), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(failure), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||
|
||||
case MultiPartPaymentFSM.MultiPartPaymentFailed(paymentHash, failure, parts) if doHandle(paymentHash) =>
|
||||
|
@ -155,7 +155,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
log.warning("payment with paidAmount={} failed ({})", parts.map(_.amount).sum, failure)
|
||||
pendingPayments.get(paymentHash).foreach { case (_, handler: ActorRef) => handler ! PoisonPill }
|
||||
parts.collect {
|
||||
case p: MultiPartPaymentFSM.HtlcPart => PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FAIL_HTLC(p.htlc.id, Right(failure), commit = true))
|
||||
case p: MultiPartPaymentFSM.HtlcPart => PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FAIL_HTLC(p.htlc.id, FailureReason.LocalFailure(failure), commit = true))
|
||||
}
|
||||
pendingPayments = pendingPayments - paymentHash
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(paymentHash))) {
|
||||
failure match {
|
||||
case Some(failure) => p match {
|
||||
case p: MultiPartPaymentFSM.HtlcPart => PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FAIL_HTLC(p.htlc.id, Right(failure), commit = true))
|
||||
case p: MultiPartPaymentFSM.HtlcPart => PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FAIL_HTLC(p.htlc.id, FailureReason.LocalFailure(failure), commit = true))
|
||||
}
|
||||
case None => p match {
|
||||
// NB: this case shouldn't happen unless the sender violated the spec, so it's ok that we take a slightly more
|
||||
|
@ -186,7 +186,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FULFILL_HTLC(p.htlc.id, record.paymentPreimage, commit = true))
|
||||
ctx.system.eventStream.publish(received)
|
||||
} else {
|
||||
val cmdFail = CMD_FAIL_HTLC(p.htlc.id, Right(IncorrectOrUnknownPaymentDetails(received.amount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(p.htlc.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(received.amount, nodeParams.currentBlockHeight)), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, cmdFail)
|
||||
}
|
||||
})
|
||||
|
@ -219,7 +219,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
|||
parts.collect {
|
||||
case p: MultiPartPaymentFSM.HtlcPart =>
|
||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, "InvoiceNotFound").increment()
|
||||
val cmdFail = CMD_FAIL_HTLC(p.htlc.id, Right(IncorrectOrUnknownPaymentDetails(received.amount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(p.htlc.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(received.amount, nodeParams.currentBlockHeight)), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, cmdFail)
|
||||
}
|
||||
}
|
||||
|
@ -521,7 +521,7 @@ object MultiPartHandler {
|
|||
|
||||
private def validateStandardPayment(nodeParams: NodeParams, add: UpdateAddHtlc, payload: FinalPayload.Standard, record: IncomingStandardPayment)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
|
||||
// We send the same error regardless of the failure to avoid probing attacks.
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val commonOk = validateCommon(nodeParams, add, payload, record)
|
||||
val secretOk = validatePaymentSecret(add, payload, record.invoice)
|
||||
if (commonOk && secretOk) None else Some(cmdFail)
|
||||
|
@ -529,7 +529,7 @@ object MultiPartHandler {
|
|||
|
||||
private def validateBlindedPayment(nodeParams: NodeParams, add: UpdateAddHtlc, payload: FinalPayload.Blinded, record: IncomingBlindedPayment)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
|
||||
// We send the same error regardless of the failure to avoid probing attacks.
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
|
||||
val commonOk = validateCommon(nodeParams, add, payload, record)
|
||||
if (commonOk) None else Some(cmdFail)
|
||||
}
|
||||
|
|
|
@ -99,11 +99,11 @@ object ChannelRelay {
|
|||
|
||||
def translateRelayFailure(originHtlcId: Long, fail: HtlcResult.Fail): CMD_FAIL_HTLC = {
|
||||
fail match {
|
||||
case f: HtlcResult.RemoteFail => CMD_FAIL_HTLC(originHtlcId, Left(f.fail.reason), commit = true)
|
||||
case f: HtlcResult.RemoteFailMalformed => CMD_FAIL_HTLC(originHtlcId, Right(createBadOnionFailure(f.fail.onionHash, f.fail.failureCode)), commit = true)
|
||||
case _: HtlcResult.OnChainFail => CMD_FAIL_HTLC(originHtlcId, Right(PermanentChannelFailure()), commit = true)
|
||||
case HtlcResult.ChannelFailureBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(PermanentChannelFailure()), commit = true)
|
||||
case f: HtlcResult.DisconnectedBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(TemporaryChannelFailure(Some(f.channelUpdate))), commit = true)
|
||||
case f: HtlcResult.RemoteFail => CMD_FAIL_HTLC(originHtlcId, FailureReason.EncryptedDownstreamFailure(f.fail.reason), commit = true)
|
||||
case f: HtlcResult.RemoteFailMalformed => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(createBadOnionFailure(f.fail.onionHash, f.fail.failureCode)), commit = true)
|
||||
case _: HtlcResult.OnChainFail => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)
|
||||
case HtlcResult.ChannelFailureBeforeSigned => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)
|
||||
case f: HtlcResult.DisconnectedBeforeSigned => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(TemporaryChannelFailure(Some(f.channelUpdate))), commit = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerUnavailable) =>
|
||||
Metrics.recordPaymentRelayFailed(Tags.FailureType.WakeUp, Tags.RelayType.Channel)
|
||||
context.log.info("rejecting htlc: failed to wake-up remote peer")
|
||||
safeSendAndStop(r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true))
|
||||
safeSendAndStop(r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) =>
|
||||
context.self ! DoRelay
|
||||
relay(Some(r.remoteFeatures), Seq.empty)
|
||||
|
@ -201,7 +201,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
Behaviors.receiveMessagePartial {
|
||||
case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) =>
|
||||
context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}")
|
||||
val cmdFail = CMD_FAIL_HTLC(upstream.add.id, Right(UnknownNextPeer()), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(upstream.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true)
|
||||
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
|
||||
safeSendAndStop(upstream.add.channelId, cmdFail)
|
||||
|
||||
|
@ -260,7 +260,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
case Some(_) =>
|
||||
// We are the introduction node: we add a delay to make it look like it could come from further downstream.
|
||||
val delay = Some(Random.nextLong(1000).millis)
|
||||
CMD_FAIL_HTLC(cmd.id, Right(failure), delay, commit = true)
|
||||
CMD_FAIL_HTLC(cmd.id, FailureReason.LocalFailure(failure), delay, commit = true)
|
||||
case None =>
|
||||
// We are not the introduction node.
|
||||
CMD_FAIL_MALFORMED_HTLC(cmd.id, failure.onionHash, failure.code, commit = true)
|
||||
|
@ -293,9 +293,9 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
// Otherwise we return the error for the first channel tried.
|
||||
.getOrElse(previousFailures.head)
|
||||
.failure
|
||||
CMD_FAIL_HTLC(r.add.id, Right(translateLocalError(error.t, error.channelUpdate)), commit = true)
|
||||
CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(translateLocalError(error.t, error.channelUpdate)), commit = true)
|
||||
} else {
|
||||
CMD_FAIL_HTLC(r.add.id, Right(UnknownNextPeer()), commit = true)
|
||||
CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true)
|
||||
}
|
||||
walletNodeId_opt match {
|
||||
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
|
||||
|
@ -326,7 +326,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
channel.channelUpdate,
|
||||
relayResult match {
|
||||
case _: RelaySuccess => "success"
|
||||
case RelayFailure(CMD_FAIL_HTLC(_, Right(failureReason), _, _, _)) => failureReason
|
||||
case RelayFailure(CMD_FAIL_HTLC(_, FailureReason.LocalFailure(failureReason), _, _, _)) => failureReason
|
||||
case other => other
|
||||
})
|
||||
(channel, relayResult)
|
||||
|
@ -373,7 +373,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
case Some(fail) =>
|
||||
RelayFailure(fail)
|
||||
case None if !update.channelFlags.isEnabled =>
|
||||
RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(update.messageFlags, update.channelFlags, Some(update))), commit = true))
|
||||
RelayFailure(CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(ChannelDisabled(update.messageFlags, update.channelFlags, Some(update))), commit = true))
|
||||
case None =>
|
||||
val origin = Origin.Hot(addResponseAdapter.toClassic, upstream)
|
||||
RelaySuccess(outgoingChannel.channelId, CMD_ADD_HTLC(addResponseAdapter.toClassic, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextPathKey_opt, confidence, fundingFee_opt = None, origin, commit = true))
|
||||
|
@ -389,11 +389,11 @@ class ChannelRelay private(nodeParams: NodeParams,
|
|||
val expiryDeltaOk = update.cltvExpiryDelta <= r.expiryDelta || prevUpdate_opt.exists(_.cltvExpiryDelta <= r.expiryDelta)
|
||||
val feesOk = nodeFee(update.relayFees, r.amountToForward) <= r.relayFeeMsat || prevUpdate_opt.exists(u => nodeFee(u.relayFees, r.amountToForward) <= r.relayFeeMsat)
|
||||
if (!htlcMinimumOk) {
|
||||
Some(CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(r.amountToForward, Some(update))), commit = true))
|
||||
Some(CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(AmountBelowMinimum(r.amountToForward, Some(update))), commit = true))
|
||||
} else if (!expiryDeltaOk) {
|
||||
Some(CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, Some(update))), commit = true))
|
||||
Some(CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(IncorrectCltvExpiry(r.outgoingCltv, Some(update))), commit = true))
|
||||
} else if (!feesOk) {
|
||||
Some(CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(update))), commit = true))
|
||||
Some(CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(FeeInsufficient(r.add.amountMsat, Some(update))), commit = true))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -476,7 +476,7 @@ class NodeRelay private(nodeParams: NodeParams,
|
|||
|
||||
private def rejectHtlc(htlcId: Long, channelId: ByteVector32, amount: MilliSatoshi, failure: Option[FailureMessage] = None): Unit = {
|
||||
val failureMessage = failure.getOrElse(IncorrectOrUnknownPaymentDetails(amount, nodeParams.currentBlockHeight))
|
||||
val cmd = CMD_FAIL_HTLC(htlcId, Right(failureMessage), commit = true)
|
||||
val cmd = CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(failureMessage), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, channelId, cmd)
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import fr.acinq.eclair.payment.Monitoring.Metrics
|
|||
import fr.acinq.eclair.wire.protocol.LiquidityAds.PaymentDetails
|
||||
import fr.acinq.eclair.wire.protocol._
|
||||
import fr.acinq.eclair.{Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, ToMilliSatoshiConversion}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
|
@ -93,22 +92,25 @@ object OnTheFlyFunding {
|
|||
def maxFees(htlcMinimum: MilliSatoshi): MilliSatoshi = htlc.amount - htlcMinimum
|
||||
|
||||
/** Create commands to fail all upstream HTLCs. */
|
||||
def createFailureCommands(failure_opt: Option[Either[ByteVector, FailureMessage]]): Seq[(ByteVector32, CMD_FAIL_HTLC)] = upstream match {
|
||||
def createFailureCommands(failure_opt: Option[FailureReason]): Seq[(ByteVector32, CMD_FAIL_HTLC)] = upstream match {
|
||||
case _: Upstream.Local => Nil
|
||||
case u: Upstream.Hot.Channel =>
|
||||
val failure = htlc.pathKey_opt match {
|
||||
case Some(_) => Right(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket)))
|
||||
case None => failure_opt.getOrElse(Right(UnknownNextPeer()))
|
||||
case Some(_) => FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket)))
|
||||
case None => failure_opt.getOrElse(FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
}
|
||||
Seq(u.add.channelId -> CMD_FAIL_HTLC(u.add.id, failure, commit = true))
|
||||
case u: Upstream.Hot.Trampoline =>
|
||||
// In the trampoline case, we currently ignore downstream failures: we should add dedicated failures to the
|
||||
// BOLTs to better handle those cases.
|
||||
val failure = failure_opt match {
|
||||
case Some(f) => f.getOrElse(TemporaryNodeFailure())
|
||||
case None => UnknownNextPeer()
|
||||
case Some(f) => f match {
|
||||
case _: FailureReason.EncryptedDownstreamFailure => FailureReason.LocalFailure(TemporaryNodeFailure())
|
||||
case _: FailureReason.LocalFailure => f
|
||||
}
|
||||
u.received.map(_.add).map(add => add.channelId -> CMD_FAIL_HTLC(add.id, Right(failure), commit = true))
|
||||
case None => FailureReason.LocalFailure(UnknownNextPeer())
|
||||
}
|
||||
u.received.map(_.add).map(add => add.channelId -> CMD_FAIL_HTLC(add.id, failure, commit = true))
|
||||
}
|
||||
|
||||
/** Create commands to fulfill all upstream HTLCs. */
|
||||
|
|
|
@ -28,7 +28,7 @@ import fr.acinq.eclair.db._
|
|||
import fr.acinq.eclair.payment.Monitoring.Tags
|
||||
import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, PaymentFailed, PaymentSent}
|
||||
import fr.acinq.eclair.transactions.DirectedHtlc.outgoing
|
||||
import fr.acinq.eclair.wire.protocol.{FailureMessage, InvalidOnionBlinding, TemporaryNodeFailure, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{FailureMessage, FailureReason, InvalidOnionBlinding, TemporaryNodeFailure, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{CustomCommitmentsPlugin, Feature, Features, Logs, MilliSatoshiLong, NodeParams, TimestampMilli}
|
||||
|
||||
import scala.concurrent.Promise
|
||||
|
@ -136,7 +136,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
|
|||
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
|
||||
CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true)
|
||||
case None =>
|
||||
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), commit = true)
|
||||
CMD_FAIL_HTLC(htlc.id, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true)
|
||||
}
|
||||
channel ! cmd
|
||||
} else {
|
||||
|
@ -278,7 +278,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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,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} 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, FailureReason.LocalFailure(RequiredNodeFeatureMissing()), commit = true))
|
||||
} else {
|
||||
nodeRelayer ! NodeRelayer.Relay(r, originNode)
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
|
|||
// 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.
|
||||
val delay = Some(500.millis + Random.nextLong(1500).millis)
|
||||
CMD_FAIL_HTLC(add.id, Right(InvalidOnionBlinding(badOnion.onionHash)), delay, commit = true)
|
||||
CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(InvalidOnionBlinding(badOnion.onionHash)), delay, commit = true)
|
||||
case _ =>
|
||||
CMD_FAIL_MALFORMED_HTLC(add.id, badOnion.onionHash, badOnion.code, commit = true)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym
|
|||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||
case Left(failure) =>
|
||||
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=$failure")
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, Right(failure), commit = true)
|
||||
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(failure), commit = true)
|
||||
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import fr.acinq.eclair.router._
|
|||
import fr.acinq.eclair.wire.protocol._
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/**
|
||||
* Created by PM on 26/08/2016.
|
||||
|
@ -166,14 +165,14 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
|||
private def handleRemoteFail(d: WaitingForComplete, fail: UpdateFailHtlc) = {
|
||||
import d._
|
||||
((Sphinx.FailurePacket.decrypt(fail.reason, sharedSecrets) match {
|
||||
case success@Success(e) =>
|
||||
case success@Right(e) =>
|
||||
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(RemoteFailure(request.amount, Nil, e))).increment()
|
||||
success
|
||||
case failure@Failure(_) =>
|
||||
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(UnreadableRemoteFailure(request.amount, Nil))).increment()
|
||||
case failure@Left(e) =>
|
||||
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(UnreadableRemoteFailure(request.amount, Nil, e.unwrapped))).increment()
|
||||
failure
|
||||
}) match {
|
||||
case res@Success(Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
case res@Right(Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
// We have discovered some liquidity information with this payment: we update the router accordingly.
|
||||
val stoppedRoute = route.stopAt(nodeId)
|
||||
if (stoppedRoute.hops.length > 1) {
|
||||
|
@ -198,39 +197,39 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
|||
res
|
||||
case res => res
|
||||
}) match {
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) if nodeId == recipient.nodeId =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) if nodeId == recipient.nodeId =>
|
||||
// if destination node returns an error, we fail the payment immediately
|
||||
log.warning(s"received an error message from target nodeId=$nodeId, failing the payment (failure=$failureMessage)")
|
||||
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ RemoteFailure(request.amount, route.fullRoute, e))))
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) if route.finalHop_opt.collect { case h: NodeHop if h.nodeId == nodeId => h }.nonEmpty =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) if route.finalHop_opt.collect { case h: NodeHop if h.nodeId == nodeId => h }.nonEmpty =>
|
||||
// if trampoline node returns an error, we fail the payment immediately
|
||||
log.warning(s"received an error message from trampoline nodeId=$nodeId, failing the payment (failure=$failureMessage)")
|
||||
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ RemoteFailure(request.amount, route.fullRoute, e))))
|
||||
case res if failures.size + 1 >= request.maxAttempts =>
|
||||
// otherwise we never try more than maxAttempts, no matter the kind of error returned
|
||||
val failure = res match {
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
log.info(s"received an error message from nodeId=$nodeId (failure=$failureMessage)")
|
||||
failureMessage match {
|
||||
case failureMessage: Update => handleUpdate(nodeId, failureMessage, d)
|
||||
case _ =>
|
||||
}
|
||||
RemoteFailure(request.amount, route.fullRoute, e)
|
||||
case Failure(t) =>
|
||||
log.warning(s"cannot parse returned error ${fail.reason.toHex} with sharedSecrets=$sharedSecrets: ${t.getMessage}")
|
||||
UnreadableRemoteFailure(request.amount, route.fullRoute)
|
||||
case Left(Sphinx.CannotDecryptFailurePacket(unwrapped)) =>
|
||||
log.warning(s"cannot parse returned error ${fail.reason.toHex} with sharedSecrets=$sharedSecrets: unwrapped=$unwrapped")
|
||||
UnreadableRemoteFailure(request.amount, route.fullRoute, unwrapped)
|
||||
}
|
||||
log.warning(s"too many failed attempts, failing the payment")
|
||||
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ failure)))
|
||||
case Failure(t) =>
|
||||
log.warning(s"cannot parse returned error: ${t.getMessage}, route=${route.printNodes()}")
|
||||
val failure = UnreadableRemoteFailure(request.amount, route.fullRoute)
|
||||
case Left(Sphinx.CannotDecryptFailurePacket(unwrapped)) =>
|
||||
log.warning(s"cannot parse returned error: unwrapped=$unwrapped, route=${route.printNodes()}")
|
||||
val failure = UnreadableRemoteFailure(request.amount, route.fullRoute, unwrapped)
|
||||
retry(failure, d)
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Node)) =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Node)) =>
|
||||
log.info(s"received 'Node' type error message from nodeId=$nodeId, trying to route around it (failure=$failureMessage)")
|
||||
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||
retry(failure, d)
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage: Update)) =>
|
||||
log.info(s"received 'Update' type error message from nodeId=$nodeId, retrying payment (failure=$failureMessage)")
|
||||
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||
if (failureMessage.update_opt.forall(update => Announcements.checkSig(update, nodeId))) {
|
||||
|
@ -257,7 +256,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
|||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(request, failures :+ failure, ignore + nodeId)
|
||||
}
|
||||
}
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, _: InvalidOnionBlinding)) =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, _: InvalidOnionBlinding)) =>
|
||||
// there was a failure inside the blinded route we used: we cannot know why it failed, so let's ignore it.
|
||||
log.info(s"received an error coming from nodeId=$nodeId inside the blinded route, retrying with different blinded routes")
|
||||
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||
|
@ -270,7 +269,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
|||
router ! RouteRequest(nodeParams.nodeId, recipient, request.routeParams, ignore1, paymentContext = Some(cfg.paymentContext))
|
||||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(request, failures :+ failure, ignore1)
|
||||
}
|
||||
case Success(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
case Right(e@Sphinx.DecryptedFailurePacket(nodeId, failureMessage)) =>
|
||||
log.info(s"received an error message from nodeId=$nodeId, trying to use a different channel (failure=$failureMessage)")
|
||||
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||
retry(failure, d)
|
||||
|
@ -316,7 +315,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
|||
router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration))
|
||||
}
|
||||
data.recipient.extraEdges
|
||||
case hint: HopRelayParams.FromHint =>
|
||||
case _: HopRelayParams.FromHint =>
|
||||
failure.update_opt match {
|
||||
case Some(update) =>
|
||||
log.info("received an update for a routing hint (shortChannelId={} nodeId={} enabled={} update={})", update.shortChannelId, nodeId, update.channelFlags.isEnabled, failure.update_opt)
|
||||
|
|
|
@ -78,7 +78,7 @@ case class ClearRecipient(nodeId: PublicKey,
|
|||
ClearRecipient.validateRoute(nodeId, route).map(_ => {
|
||||
val finalPayload = nextTrampolineOnion_opt match {
|
||||
case Some(trampolinePacket) => NodePayload(nodeId, FinalPayload.Standard.createTrampolinePayload(route.amount, totalAmount, expiry, paymentSecret, trampolinePacket))
|
||||
case None => NodePayload(nodeId, FinalPayload.Standard.createPayload(route.amount, totalAmount, expiry, paymentSecret, paymentMetadata_opt, customTlvs))
|
||||
case None => NodePayload(nodeId, FinalPayload.Standard.createPayload(route.amount, totalAmount, expiry, paymentSecret, paymentMetadata_opt, trampolineOnion_opt = None, customTlvs = customTlvs))
|
||||
}
|
||||
Recipient.buildPayloads(PaymentPayloads(route.amount, expiry, Seq(finalPayload), None), route.hops)
|
||||
})
|
||||
|
|
|
@ -30,9 +30,18 @@ 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 legacyCmdFailCodec: Codec[CMD_FAIL_HTLC] =
|
||||
private val cmdFailWithoutLengthCodec: Codec[CMD_FAIL_HTLC] =
|
||||
(("id" | int64) ::
|
||||
("reason" | either(bool, varsizebinarydata, provide(TemporaryNodeFailure()).upcast[FailureMessage])) ::
|
||||
("reason" | either(bool, varsizebinarydata, provide(TemporaryNodeFailure()).upcast[FailureMessage]).xmap[FailureReason](
|
||||
{
|
||||
case Left(packet) => FailureReason.EncryptedDownstreamFailure(packet)
|
||||
case Right(f) => FailureReason.LocalFailure(f)
|
||||
},
|
||||
{
|
||||
case FailureReason.EncryptedDownstreamFailure(packet) => Left(packet)
|
||||
case FailureReason.LocalFailure(f) => Right(f)
|
||||
}
|
||||
)) ::
|
||||
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
||||
("commit" | provide(false)) ::
|
||||
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC]
|
||||
|
@ -43,9 +52,27 @@ object CommandCodecs {
|
|||
("commit" | provide(false)) ::
|
||||
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FULFILL_HTLC]
|
||||
|
||||
// We previously supported only two types of HTLC failures, represented by an Either[ByteVector, FailureMessage].
|
||||
private val cmdFailEitherCodec: Codec[CMD_FAIL_HTLC] =
|
||||
(("id" | int64) ::
|
||||
("reason" | either(bool8, varsizebinarydata, variableSizeBytes(uint16, failureMessageCodec)).xmap[FailureReason](
|
||||
{
|
||||
case Left(packet) => FailureReason.EncryptedDownstreamFailure(packet)
|
||||
case Right(f) => FailureReason.LocalFailure(f)
|
||||
},
|
||||
{
|
||||
case FailureReason.EncryptedDownstreamFailure(packet) => Left(packet)
|
||||
case FailureReason.LocalFailure(f) => Right(f)
|
||||
}
|
||||
)) ::
|
||||
// No need to delay commands after a restart, we've been offline which already created a random delay.
|
||||
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
||||
("commit" | provide(false)) ::
|
||||
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC]
|
||||
|
||||
private val cmdFailCodec: Codec[CMD_FAIL_HTLC] =
|
||||
(("id" | int64) ::
|
||||
("reason" | either(bool8, varsizebinarydata, variableSizeBytes(uint16, failureMessageCodec))) ::
|
||||
("reason" | failureReasonCodec) ::
|
||||
// No need to delay commands after a restart, we've been offline which already created a random delay.
|
||||
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
||||
("commit" | provide(false)) ::
|
||||
|
@ -60,9 +87,10 @@ object CommandCodecs {
|
|||
|
||||
val cmdCodec: Codec[HtlcSettlementCommand] = discriminated[HtlcSettlementCommand].by(uint16)
|
||||
// NB: order matters!
|
||||
.typecase(3, cmdFailCodec)
|
||||
.typecase(4, cmdFailCodec)
|
||||
.typecase(3, cmdFailEitherCodec)
|
||||
.typecase(2, cmdFailMalformedCodec)
|
||||
.typecase(1, legacyCmdFailCodec)
|
||||
.typecase(1, cmdFailWithoutLengthCodec)
|
||||
.typecase(0, cmdFulfillCodec)
|
||||
|
||||
}
|
|
@ -31,6 +31,17 @@ import scodec.{Attempt, Codec, Err}
|
|||
* Created by fabrice on 14/03/17.
|
||||
*/
|
||||
|
||||
// @formatter:off
|
||||
/** Reason for failing an HTLC, which will be encrypted into a failure onion packet. */
|
||||
sealed trait FailureReason
|
||||
object FailureReason {
|
||||
/** An encrypted failure coming from downstream which we should re-encrypt and forward upstream. */
|
||||
case class EncryptedDownstreamFailure(packet: ByteVector) extends FailureReason
|
||||
/** A local failure that should be encrypted for the node that created the payment onion. */
|
||||
case class LocalFailure(failure: FailureMessage) extends FailureReason
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
sealed trait FailureMessageTlv extends Tlv
|
||||
|
||||
// @formatter:off
|
||||
|
@ -157,6 +168,10 @@ object FailureMessageCodecs {
|
|||
fallback = unknownFailureMessageCodec.upcast[FailureMessage]
|
||||
)
|
||||
|
||||
val failureReasonCodec: Codec[FailureReason] = discriminated[FailureReason].by(uint8)
|
||||
.typecase(0, varsizebinarydata.as[FailureReason.EncryptedDownstreamFailure])
|
||||
.typecase(1, variableSizeBytes(uint16, failureMessageCodec).as[FailureReason.LocalFailure])
|
||||
|
||||
private def failureOnionPayload(payloadAndPadLength: Int): Codec[FailureMessage] = Codec(
|
||||
encoder = f => variableSizeBytes(uint16, failureMessageCodec).encode(f).flatMap(bits => {
|
||||
val payloadLength = bits.bytes.length - 2
|
||||
|
|
|
@ -431,6 +431,7 @@ object PaymentOnion {
|
|||
val paymentSecret = records.get[PaymentData].map(_.secret).orElse(records.get[KeySend].map(_.paymentPreimage)).get
|
||||
val paymentPreimage = records.get[KeySend].map(_.paymentPreimage)
|
||||
val paymentMetadata = records.get[PaymentMetadata].map(_.data)
|
||||
val isTrampoline = records.get[TrampolineOnion].nonEmpty
|
||||
}
|
||||
|
||||
object Standard {
|
||||
|
@ -442,12 +443,13 @@ object PaymentOnion {
|
|||
Right(Standard(records))
|
||||
}
|
||||
|
||||
def createPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, paymentMetadata: Option[ByteVector] = None, customTlvs: Set[GenericTlv] = Set.empty): Standard = {
|
||||
def createPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, paymentSecret: ByteVector32, paymentMetadata: Option[ByteVector] = None, trampolineOnion_opt: Option[OnionRoutingPacket] = None, customTlvs: Set[GenericTlv] = Set.empty): Standard = {
|
||||
val tlvs: Set[OnionPaymentPayloadTlv] = Set(
|
||||
Some(AmountToForward(amount)),
|
||||
Some(OutgoingCltv(expiry)),
|
||||
Some(PaymentData(paymentSecret, totalAmount)),
|
||||
paymentMetadata.map(m => PaymentMetadata(m))
|
||||
paymentMetadata.map(m => PaymentMetadata(m)),
|
||||
trampolineOnion_opt.map(o => TrampolineOnion(o)),
|
||||
).flatten
|
||||
Standard(TlvStream(tlvs, customTlvs))
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import fr.acinq.eclair.channel.Helpers.Closing
|
|||
import fr.acinq.eclair.channel.fsm.Channel
|
||||
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.wire.protocol.{CommitSig, RevokeAndAck, UnknownNextPeer, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{CommitSig, FailureReason, RevokeAndAck, UnknownNextPeer, UpdateAddHtlc}
|
||||
import fr.acinq.eclair.{MilliSatoshiLong, NodeParams, TestKitBaseClass}
|
||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||
import scodec.bits.ByteVector
|
||||
|
@ -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, FailureReason.LocalFailure(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, FailureReason.LocalFailure(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)
|
||||
|
|
|
@ -27,7 +27,7 @@ import fr.acinq.eclair.crypto.ShaChain
|
|||
import fr.acinq.eclair.crypto.keymanager.LocalChannelKeyManager
|
||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||
import fr.acinq.eclair.wire.protocol.{IncorrectOrUnknownPaymentDetails, UpdateAddHtlc, UpdateFailHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{FailureReason, IncorrectOrUnknownPaymentDetails, UpdateAddHtlc, UpdateFailHtlc}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
|
||||
|
@ -199,7 +199,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
assert(bc4.availableBalanceForSend == b)
|
||||
assert(bc4.availableBalanceForReceive == a - p - htlcOutputFee)
|
||||
|
||||
val cmdFail = CMD_FAIL_HTLC(0, Right(IncorrectOrUnknownPaymentDetails(p, BlockHeight(42))))
|
||||
val cmdFail = CMD_FAIL_HTLC(0, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(p, BlockHeight(42))))
|
||||
val Right((bc5, fail: UpdateFailHtlc)) = bc4.sendFail(cmdFail, bob.underlyingActor.nodeParams.privateKey)
|
||||
assert(bc5.availableBalanceForSend == b)
|
||||
assert(bc5.availableBalanceForReceive == a - p - htlcOutputFee) // a's balance won't return to previous before she acknowledges the fail
|
||||
|
@ -322,7 +322,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
assert(bc8.availableBalanceForSend == b + p1 - p3) // as soon as we have the fulfill, the balance increases
|
||||
assert(bc8.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee - htlcOutputFee)
|
||||
|
||||
val cmdFail2 = CMD_FAIL_HTLC(1, Right(IncorrectOrUnknownPaymentDetails(p2, BlockHeight(42))))
|
||||
val cmdFail2 = CMD_FAIL_HTLC(1, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(p2, BlockHeight(42))))
|
||||
val Right((bc9, fail2: UpdateFailHtlc)) = bc8.sendFail(cmdFail2, bob.underlyingActor.nodeParams.privateKey)
|
||||
assert(bc9.availableBalanceForSend == b + p1 - p3)
|
||||
assert(bc9.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee - htlcOutputFee) // a's balance won't return to previous before she acknowledges the fail
|
||||
|
|
|
@ -429,7 +429,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, FailureReason.LocalFailure(TemporaryNodeFailure()))
|
||||
val fail = s2r.expectMsgType[UpdateFailHtlc]
|
||||
s2r.forward(r)
|
||||
eventually(assert(r.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.changes.remoteChanges.proposed.contains(fail)))
|
||||
|
|
|
@ -191,7 +191,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
|
|||
val (preimage, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||
val cmd = c match {
|
||||
case FulfillHtlc => CMD_FULFILL_HTLC(add.id, preimage)
|
||||
case FailHtlc => CMD_FAIL_HTLC(add.id, Left(randomBytes32()))
|
||||
case FailHtlc => CMD_FAIL_HTLC(add.id, FailureReason.EncryptedDownstreamFailure(randomBytes(252)))
|
||||
}
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
val sender = initiateQuiescence(f, sendInitialStfu)
|
||||
|
|
|
@ -42,7 +42,7 @@ import fr.acinq.eclair.router.Announcements
|
|||
import fr.acinq.eclair.transactions.DirectedHtlc.{incoming, outgoing}
|
||||
import fr.acinq.eclair.transactions.Transactions
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, TemporaryNodeFailure, TlvStream, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc, Warning}
|
||||
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, FailureReason, PermanentChannelFailure, RevokeAndAck, Shutdown, TemporaryNodeFailure, TlvStream, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc, Warning}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
import scodec.bits._
|
||||
|
@ -1527,7 +1527,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, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
bob2alice.forward(alice)
|
||||
bob ! CMD_SIGN()
|
||||
|
@ -1811,7 +1811,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, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||
val Right(fail) = OutgoingPaymentPacket.buildHtlcFailure(Bob.nodeParams.privateKey, cmd, htlc)
|
||||
assert(fail.id == htlc.id)
|
||||
bob ! cmd
|
||||
|
@ -1841,7 +1841,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
|
||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val cmd = CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure()), delay_opt = Some(50 millis))
|
||||
val cmd = CMD_FAIL_HTLC(htlc.id, FailureReason.LocalFailure(PermanentChannelFailure()), delay_opt = Some(50 millis))
|
||||
val Right(fail) = OutgoingPaymentPacket.buildHtlcFailure(Bob.nodeParams.privateKey, cmd, htlc)
|
||||
assert(fail.id == htlc.id)
|
||||
bob ! cmd
|
||||
|
@ -1854,7 +1854,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, FailureReason.LocalFailure(PermanentChannelFailure()), replyTo_opt = Some(sender.ref))
|
||||
bob ! c
|
||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||
assert(initialState == bob.stateData)
|
||||
|
@ -1874,7 +1874,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, FailureReason.LocalFailure(TemporaryNodeFailure()), replyTo_opt = Some(sender.ref))
|
||||
bob ! c
|
||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), htlc.id)))
|
||||
}
|
||||
|
@ -1884,7 +1884,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, FailureReason.LocalFailure(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)
|
||||
|
@ -1938,7 +1938,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, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
|
||||
// actual test begins
|
||||
|
@ -2045,7 +2045,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
// Bob receives a failure with a completely invalid onion error (missing mac)
|
||||
bob ! CMD_FAIL_HTLC(htlc.id, Left(ByteVector.fill(561)(42)))
|
||||
bob ! CMD_FAIL_HTLC(htlc.id, FailureReason.EncryptedDownstreamFailure(ByteVector.fill(561)(42)))
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
assert(fail.id == htlc.id)
|
||||
// We propagate failure upstream (hopefully the sender knows how to unwrap them).
|
||||
|
|
|
@ -657,7 +657,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
|
||||
// We simulate a pending failure on that HTLC.
|
||||
// Even if we get close to expiring upstream we shouldn't close the channel, because we have nothing to lose.
|
||||
bob ! CMD_FAIL_HTLC(htlc.id, Right(IncorrectOrUnknownPaymentDetails(0 msat, BlockHeight(0))))
|
||||
bob ! CMD_FAIL_HTLC(htlc.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(0 msat, BlockHeight(0))))
|
||||
bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt)
|
||||
|
||||
bob2blockchain.expectNoMessage(250 millis)
|
||||
|
|
|
@ -32,7 +32,7 @@ import fr.acinq.eclair.payment._
|
|||
import fr.acinq.eclair.payment.relay.Relayer._
|
||||
import fr.acinq.eclair.payment.send.SpontaneousRecipient
|
||||
import fr.acinq.eclair.transactions.Transactions.ClaimLocalAnchorOutputTx
|
||||
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ClosingSigned, CommitSig, Error, FailureMessageCodecs, FailureReason, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32}
|
||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||
import org.scalatest.{Outcome, Tag}
|
||||
|
@ -243,7 +243,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, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
awaitCond(bob.stateData == initialState
|
||||
.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fail)
|
||||
|
@ -254,7 +254,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, FailureReason.LocalFailure(PermanentChannelFailure()), replyTo_opt = Some(sender.ref))
|
||||
bob ! c
|
||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||
assert(initialState == bob.stateData)
|
||||
|
@ -264,7 +264,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, FailureReason.LocalFailure(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)
|
||||
|
@ -503,7 +503,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, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
bob2alice.forward(alice)
|
||||
bob ! CMD_SIGN()
|
||||
|
|
|
@ -229,19 +229,19 @@ class SphinxSpec extends AnyFunSuite {
|
|||
val packet1 = FailurePacket.create(sharedSecrets.head, expected.failureMessage)
|
||||
assert(packet1.length == 292)
|
||||
|
||||
val Success(decrypted1) = FailurePacket.decrypt(packet1, Seq(0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
val Right(decrypted1) = FailurePacket.decrypt(packet1, Seq(0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
assert(decrypted1 == expected)
|
||||
|
||||
val packet2 = FailurePacket.wrap(packet1, sharedSecrets(1))
|
||||
assert(packet2.length == 292)
|
||||
|
||||
val Success(decrypted2) = FailurePacket.decrypt(packet2, Seq(1, 0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
val Right(decrypted2) = FailurePacket.decrypt(packet2, Seq(1, 0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
assert(decrypted2 == expected)
|
||||
|
||||
val packet3 = FailurePacket.wrap(packet2, sharedSecrets(2))
|
||||
assert(packet3.length == 292)
|
||||
|
||||
val Success(decrypted3) = FailurePacket.decrypt(packet3, Seq(2, 1, 0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
val Right(decrypted3) = FailurePacket.decrypt(packet3, Seq(2, 1, 0).map(i => (sharedSecrets(i), publicKeys(i))))
|
||||
assert(decrypted3 == expected)
|
||||
}
|
||||
|
||||
|
@ -258,7 +258,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
sharedSecrets(1)),
|
||||
sharedSecrets(2))
|
||||
|
||||
assert(FailurePacket.decrypt(packet, Seq(0, 2, 1).map(i => (sharedSecrets(i), publicKeys(i)))).isFailure)
|
||||
assert(FailurePacket.decrypt(packet, Seq(0, 2, 1).map(i => (sharedSecrets(i), publicKeys(i)))).isLeft)
|
||||
}
|
||||
|
||||
test("last node replies with a short failure message (old reference test vector)") {
|
||||
|
@ -286,7 +286,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
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)
|
||||
val Right(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error4, sharedSecrets)
|
||||
assert(pubkey == publicKeys(4))
|
||||
assert(failure == TemporaryNodeFailure())
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
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)
|
||||
val Right(DecryptedFailurePacket(pubkey, parsedFailure)) = FailurePacket.decrypt(error4, sharedSecrets)
|
||||
assert(pubkey == publicKeys(4))
|
||||
assert(parsedFailure == failure)
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
assert(error4 == hex"751c187d145e5498306824f193c6bf9ed4a974fa85b3cc5d32d549ce494c1e7b3a06a19f8a9145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12dc942b5cf1db059d3e73d63967e464b5d5cfd4052de195387de93535e88a2e618e15a7c521d67ce2cc836c49118f205c99f18570504504221e337a29e2716fb28671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4")
|
||||
|
||||
// origin parses error packet and can see that it comes from node #4
|
||||
val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error4, sharedSecrets)
|
||||
val Right(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error4, sharedSecrets)
|
||||
assert(pubkey == publicKeys(4))
|
||||
assert(failure == TemporaryNodeFailure())
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
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)
|
||||
val Right(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error2, sharedSecrets)
|
||||
assert(pubkey == publicKeys(2))
|
||||
assert(failure == InvalidRealm())
|
||||
}
|
||||
|
@ -387,7 +387,7 @@ class SphinxSpec extends AnyFunSuite {
|
|||
assert(error2 == hex"c843486107187673b4586f5cdaad43ad84fbac03b39df51bbf9169b2bd682b409a855b2feb0545705f12eba9dbaecee84e328a9c2e4c3086bb1d0909d1f2e4f8a0e9c6be9541e94a849a0887756b984031dcb74d11c20d437a55daf3ee4109dea68ad74f9b742e7571d5e4d1b2ea4f7094787cf361b448a22a547ea85b833aae20f3ba79fb41c6636414c2092d41dd5328e2c1a1c754cb1f0d297628219f91fe946169f593ce7fce79103945d4d24adce46c083ab24757870356af55fcd3d22b9cfd83c45d409eb3081b218448d5dca3a201cf89ac88c9b66049d7c262b32081d3aba2098ea853bfa173ec23aa9253e083dfa881ef487b76780435c1b9f8a1d794557f0ac91d261d280bfb8513ad0c4dab0d7152eb9ee36ae63b8d384613684326d8735dc559f31cecb21b1d55bbcf7a281127adbedd0210b243325fd291cb82d443beec8f4b96aaee4b1a619724d7456b756d391e8fd3256d2b0766e39a435eb4d6d144c7fca1c73105710266e31120565444dfd6e9099e44d73a0f28419809577a267bbbc6671f723669d00c35c8e60fad88d89d4a7477a0c30f9839485197ed76338330f2ca00cf0e31c59da4eeebef977f429ad2c61acac35939866dac5b1df1c3c487ebaf961340c0c1dbc4bedebde7ee0633c3f480b7df265a3d90e78a4bcb9497f4228169fadb647e77afe6f43aa129286bb21767f6e75ac5c092473f99f2cf8b4e191f300c70b210e077a0385d483971bc0c66f5c119c0731a8753793ad12703d9cc5153eb1c8f25b71ee88a8d1d4433aa8f8277366c82111dbebfe0f548411588d54c3606742330d3d84a2f107df98d60995297de11672f6300b11444a04e252d69d8187772798afc6a9cd8b245a5ebd51bf0659f18c57daf1d1f724d2f15d524ab6902fb17a8fa6cee8e01df67735eac34bb0efc183dcb8d2a7cb401bd786c32a17f14c9d9ffc02b4f58c4ebab898a78b4913647d4cb5bafe6f7f27b5a256d1635c10f0ca71796610068c090c270c20bb18ec9d205e640d7655bdf5c9aeae20d7f9426eade0733c19d0aa577caf31f9d5be0a99ed0c509e84ccb555389ca69f09c3e66694a4ea2785f8d839d7dfff08b2c21aff89a023161cb1ebdd1e7a46d6380c0ddbc88eb3526e624fadcd222ecaa09566c2678158f933f03623299fec134a880d39a9d82ba2b29211e7787b3f32d478df856389a02cb68b66fc0dfc0b52353e7360f31e5457a6a9dd34512e912afeb5a92f3cbd3883b62c37e3ba5e4e8b688033150103c810740d130a5597c8a4a16311f50cfb3a919aac1e0a1096f20a14a536c55068ad38f40e62fc6f178b2fee67ca2cbd8afa29ef6c89b217aee02419ca26d59b604521a55e37c0a5a693fbc3ebcba23cd62479ddf62e5521847a2b4ac5e7686ef662c29cf8a8983660530942ee9a6c53b55e08af0b43467989693cefe6267fd524435152c01c9b93aebdec6146366a94162f99ac4c7157c15b988")
|
||||
|
||||
// origin parses error packet and can see that it comes from node #2
|
||||
val Success(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error2, sharedSecrets)
|
||||
val Right(DecryptedFailurePacket(pubkey, failure)) = FailurePacket.decrypt(error2, sharedSecrets)
|
||||
assert(pubkey == publicKeys(2))
|
||||
assert(failure == InvalidRealm())
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import fr.acinq.eclair.db.sqlite.SqlitePendingCommandsDb
|
|||
import fr.acinq.eclair.db.sqlite.SqliteUtils.{setVersion, using}
|
||||
import fr.acinq.eclair.randomBytes32
|
||||
import fr.acinq.eclair.wire.internal.CommandCodecs.cmdCodec
|
||||
import fr.acinq.eclair.wire.protocol.{FailureMessageCodecs, UnknownNextPeer}
|
||||
import fr.acinq.eclair.wire.protocol.{FailureMessageCodecs, FailureReason, UnknownNextPeer}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
import scala.util.Random
|
||||
|
@ -53,8 +53,8 @@ class PendingCommandsDbSpec extends AnyFunSuite {
|
|||
val channelId2 = randomBytes32()
|
||||
val msg0 = CMD_FULFILL_HTLC(0, randomBytes32())
|
||||
val msg1 = CMD_FULFILL_HTLC(1, randomBytes32())
|
||||
val msg2 = CMD_FAIL_HTLC(2, Left(randomBytes32()))
|
||||
val msg3 = CMD_FAIL_HTLC(3, Left(randomBytes32()))
|
||||
val msg2 = CMD_FAIL_HTLC(2, FailureReason.EncryptedDownstreamFailure(randomBytes32()))
|
||||
val msg3 = CMD_FAIL_HTLC(3, FailureReason.EncryptedDownstreamFailure(randomBytes32()))
|
||||
val msg4 = CMD_FAIL_MALFORMED_HTLC(4, randomBytes32(), FailureMessageCodecs.BADONION)
|
||||
|
||||
assert(db.listSettlementCommands(channelId1).toSet == Set.empty)
|
||||
|
@ -135,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), FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
}
|
||||
}
|
||||
cmds.map(cmd => TestCase(channelId, cmd))
|
||||
|
|
|
@ -199,7 +199,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, invoice.paymentHash, CltvExpiryDelta(3).toCltvExpiry(nodeParams.currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(amountMsat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(amountMsat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
|
||||
eventListener.expectNoMessage(100 milliseconds)
|
||||
|
@ -374,7 +374,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
|
||||
assert(incoming.invoice.isExpired() && incoming.status == IncomingPaymentStatus.Expired)
|
||||
}
|
||||
|
@ -389,7 +389,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -404,7 +404,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, lowCltvExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -418,7 +418,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash.reverse, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -432,7 +432,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 999 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(999 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(999 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -446,7 +446,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 2001 msat, add.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(2001 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(2001 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -461,7 +461,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, invoice.paymentSecret.reverse, invoice.paymentMetadata)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -477,7 +477,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
assert(receivePayment.paymentHash == invoice.paymentHash)
|
||||
receivePayment.replyTo ! GetIncomingPaymentActor.RejectPayment("non blinded payment")
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||
}
|
||||
|
||||
|
@ -493,7 +493,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, 5000 msat, invoice.paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, randomBytes32(), None)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
|
||||
}
|
||||
|
||||
|
@ -537,7 +537,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
assert(payment.payload.pathId == pathId)
|
||||
payment.replyTo ! GetIncomingPaymentActor.RejectPayment("internal error")
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
}
|
||||
|
||||
test("PaymentHandler should reject incoming blinded payment with unexpected expiry") { f =>
|
||||
|
@ -560,7 +560,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val payment = IncomingBlindedPayment(MinimalBolt12Invoice(invoice.records), preimage, PaymentType.Blinded, TimestampMilli.now(), IncomingPaymentStatus.Pending)
|
||||
receivePayment.replyTo ! GetIncomingPaymentActor.ProcessPayment(payment)
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(5000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
|
||||
}
|
||||
|
||||
|
@ -587,8 +587,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, FailureReason.LocalFailure(PaymentTimeout()), commit = true)),
|
||||
Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(1, FailureReason.LocalFailure(PaymentTimeout()), commit = true))
|
||||
))
|
||||
awaitCond({
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
|
@ -597,7 +597,7 @@ 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, 1.0, None)), Some(PaymentTimeout())))
|
||||
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, Right(PaymentTimeout()), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(42, FailureReason.LocalFailure(PaymentTimeout()), commit = true)))
|
||||
|
||||
// The payment should still be pending in DB.
|
||||
val Some(incomingPayment) = nodeParams.db.payments.getIncomingPayment(pr1.paymentHash)
|
||||
|
@ -621,7 +621,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||
|
||||
f.register.expectMsgAllOf(
|
||||
Register.Forward(null, add2.channelId, CMD_FAIL_HTLC(add2.id, Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), commit = true)),
|
||||
Register.Forward(null, add2.channelId, CMD_FAIL_HTLC(add2.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)), commit = true)),
|
||||
Register.Forward(null, add1.channelId, CMD_FULFILL_HTLC(add1.id, preimage, commit = true)),
|
||||
Register.Forward(null, add3.channelId, CMD_FULFILL_HTLC(add3.id, preimage, commit = true))
|
||||
)
|
||||
|
@ -684,7 +684,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
|
||||
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, invoice.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
f.sender.send(handler, IncomingPaymentPacket.FinalPacket(add1, FinalPayload.Standard.createPayload(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, FailureReason.LocalFailure(PaymentTimeout()), commit = true)))
|
||||
awaitCond({
|
||||
f.sender.send(handler, GetPendingPayments)
|
||||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
||||
|
@ -770,7 +770,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, payload))
|
||||
|
||||
f.register.expectMsg(Register.Forward(null, add.channelId, CMD_FAIL_HTLC(add.id, Right(IncorrectOrUnknownPaymentDetails(42000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
f.register.expectMsg(Register.Forward(null, add.channelId, CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(42000 msat, nodeParams.currentBlockHeight)), commit = true)))
|
||||
assert(nodeParams.db.payments.getIncomingPayment(paymentHash).isEmpty)
|
||||
}
|
||||
|
||||
|
@ -785,7 +785,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
sender.send(handlerWithoutMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, add.amountMsat, add.cltvExpiry, paymentSecret, None)))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.id == add.id)
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
}
|
||||
|
||||
test("PaymentHandler should reject incoming multi-part payment if the invoice doesn't exist") { f =>
|
||||
|
@ -799,7 +799,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, FinalPayload.Standard.createPayload(add.amountMsat, 1000 msat, add.cltvExpiry, paymentSecret, Some(hex"012345"))))
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.id == add.id)
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
}
|
||||
|
||||
test("PaymentHandler should fail fulfilling incoming payments if the invoice doesn't exist") { f =>
|
||||
|
@ -816,7 +816,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
|||
sender.send(handlerWithoutMpp, fulfill)
|
||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||
assert(cmd.id == add.id)
|
||||
assert(cmd.reason == Right(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
assert(cmd.reason == FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -385,7 +385,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
|||
val failures = Seq(
|
||||
LocalFailure(finalAmount, Nil, ChannelUnavailable(randomBytes32())),
|
||||
RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48)))))),
|
||||
UnreadableRemoteFailure(finalAmount, Nil)
|
||||
UnreadableRemoteFailure(finalAmount, Nil, randomBytes(292))
|
||||
)
|
||||
val extraEdges1 = Seq(
|
||||
ExtraEdge(a, b, ShortChannelId(1), 10 msat, 0, CltvExpiryDelta(12), 1 msat, None),
|
||||
|
@ -421,14 +421,14 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
|||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
|
||||
val (failedId1, failedRoute1) = payFsm.stateData.asInstanceOf[PaymentProgress].pending.head
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(UnreadableRemoteFailure(failedRoute1.amount, failedRoute1.hops))))
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(UnreadableRemoteFailure(failedRoute1.amount, failedRoute1.hops, randomBytes(292)))))
|
||||
router.expectMsgType[RouteRequest]
|
||||
router.send(payFsm, RouteResponse(Seq(Route(500_000 msat, hop_ad :: hop_de :: Nil, None))))
|
||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
|
||||
assert(!payFsm.stateData.asInstanceOf[PaymentProgress].pending.contains(failedId1))
|
||||
val (failedId2, failedRoute2) = payFsm.stateData.asInstanceOf[PaymentProgress].pending.head
|
||||
val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(UnreadableRemoteFailure(failedRoute2.amount, failedRoute2.hops))))
|
||||
val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(UnreadableRemoteFailure(failedRoute2.amount, failedRoute2.hops, randomBytes(292)))))
|
||||
assert(result.failures.length >= 3)
|
||||
assert(result.failures.contains(LocalFailure(finalAmount, Nil, RetryExhausted)))
|
||||
|
||||
|
@ -517,7 +517,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
|||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
|
||||
val (failedId1, failedRoute1) :: (failedId2, failedRoute2) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(UnreadableRemoteFailure(failedRoute1.amount, failedRoute1.hops))))
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(UnreadableRemoteFailure(failedRoute1.amount, failedRoute1.hops, randomBytes(292)))))
|
||||
router.expectMsgType[RouteRequest]
|
||||
|
||||
val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout())))))
|
||||
|
@ -535,7 +535,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(UnreadableRemoteFailure(failedRoute.amount, failedRoute.fullRoute))))
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId, paymentHash, Seq(UnreadableRemoteFailure(failedRoute.amount, failedRoute.fullRoute, randomBytes(292)))))
|
||||
router.expectMsgType[RouteRequest]
|
||||
|
||||
val result = fulfillPendingPayments(f, 1, e, finalAmount)
|
||||
|
|
|
@ -44,6 +44,7 @@ import fr.acinq.eclair.router.Router._
|
|||
import fr.acinq.eclair.router._
|
||||
import fr.acinq.eclair.transactions.Scripts
|
||||
import fr.acinq.eclair.wire.protocol._
|
||||
import org.scalatest.Inside.inside
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import java.util.UUID
|
||||
|
@ -338,7 +339,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
|||
assert(ignore1.nodes.isEmpty)
|
||||
|
||||
register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd1))
|
||||
sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, randomBytes32())))) // unparsable message
|
||||
sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, randomBytes(292))))) // unparsable message
|
||||
|
||||
// then the payment lifecycle will ask for a new route excluding all intermediate nodes
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, cfg).copy(ignore = Ignore(Set(c), Set.empty)))
|
||||
|
@ -350,10 +351,17 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
|||
assert(ignore2.nodes == Set(c))
|
||||
// and reply a 2nd time with an unparsable failure
|
||||
register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd2))
|
||||
sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, defaultPaymentHash)))) // unparsable message
|
||||
sender.send(paymentFSM, addCompleted(HtlcResult.RemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, randomBytes(292))))) // unparsable message
|
||||
|
||||
// we allow 2 tries, so we send a 2nd request to the router
|
||||
assert(sender.expectMsgType[PaymentFailed].failures == UnreadableRemoteFailure(route.amount, route.hops) :: UnreadableRemoteFailure(route.amount, route.hops) :: Nil)
|
||||
inside(sender.expectMsgType[PaymentFailed]) { e =>
|
||||
assert(e.failures.length == 2)
|
||||
e.failures.foreach(f => {
|
||||
assert(f.isInstanceOf[UnreadableRemoteFailure])
|
||||
assert(f.amount == route.amount)
|
||||
assert(f.route == route.hops)
|
||||
})
|
||||
}
|
||||
awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status.isInstanceOf[OutgoingPaymentStatus.Failed])) // after last attempt the payment is failed
|
||||
|
||||
val metrics = metricsListener.expectMsgType[PathFindingExperimentMetrics]
|
||||
|
@ -881,11 +889,11 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
|||
(RemoteFailure(defaultAmountMsat, blindedRoute_abc, Sphinx.DecryptedFailurePacket(b, InvalidOnionBlinding(randomBytes32()))), Set.empty, Set(ChannelDesc(blindedHop_bc.dummyId, blindedHop_bc.nodeId, blindedHop_bc.nextNodeId))),
|
||||
(RemoteFailure(defaultAmountMsat, blindedRoute_abc, Sphinx.DecryptedFailurePacket(blindedHop_bc.resolved.route.blindedNodeIds(1), InvalidOnionBlinding(randomBytes32()))), Set.empty, Set(ChannelDesc(blindedHop_bc.dummyId, blindedHop_bc.nodeId, blindedHop_bc.nextNodeId))),
|
||||
// unreadable remote failures -> blacklist all nodes except our direct peer, the final recipient or the last hop
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil), Set.empty, Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: Nil), Set(c), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: channelHopFromUpdate(d, e, update_de) :: Nil), Set(c, d), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: NodeHop(d, e, CltvExpiryDelta(24), 0 msat) :: Nil), Set(c), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: blindedHop_de :: Nil), Set(c), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: Nil, ByteVector.empty), Set.empty, Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: Nil, ByteVector.empty), Set(c), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: channelHopFromUpdate(d, e, update_de) :: Nil, ByteVector.empty), Set(c, d), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: NodeHop(d, e, CltvExpiryDelta(24), 0 msat) :: Nil, ByteVector.empty), Set(c), Set.empty),
|
||||
(UnreadableRemoteFailure(defaultAmountMsat, channelHopFromUpdate(a, b, update_ab) :: channelHopFromUpdate(b, c, update_bc) :: channelHopFromUpdate(c, d, update_cd) :: blindedHop_de :: Nil, ByteVector.empty), Set(c), Set.empty),
|
||||
)
|
||||
|
||||
for ((failure, expectedNodes, expectedChannels) <- testCases) {
|
||||
|
|
|
@ -35,7 +35,6 @@ import fr.acinq.eclair.router.Router.{NodeHop, Route}
|
|||
import fr.acinq.eclair.transactions.Transactions
|
||||
import fr.acinq.eclair.transactions.Transactions.InputInfo
|
||||
import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo}
|
||||
import fr.acinq.eclair.wire.protocol.OnionPaymentPayloadTlv.{AmountToForward, OutgoingCltv, PaymentData}
|
||||
import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload}
|
||||
import fr.acinq.eclair.wire.protocol._
|
||||
import fr.acinq.eclair.{BlockHeight, Bolt11Feature, Bolt12Feature, CltvExpiry, CltvExpiryDelta, EncodedNodeId, Features, MilliSatoshi, MilliSatoshiLong, ShortChannelId, TestConstants, TimestampMilli, TimestampSecondLong, UInt64, nodeFee, randomBytes32, randomKey}
|
||||
|
@ -44,7 +43,6 @@ import org.scalatest.funsuite.AnyFunSuite
|
|||
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
|
||||
/**
|
||||
* Created by PM on 31/05/2016.
|
||||
|
@ -312,7 +310,12 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
|||
val add_e = UpdateAddHtlc(randomBytes32(), 4, amount_de, paymentHash, expiry_de, packet_e, None, 1.0, None)
|
||||
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||
assert(add_e2 == add_e)
|
||||
assert(payload_e == FinalPayload.Standard(TlvStream(AmountToForward(finalAmount), OutgoingCltv(finalExpiry), PaymentData(paymentSecret, finalAmount), OnionPaymentPayloadTlv.PaymentMetadata(hex"010203"))))
|
||||
assert(payload_e.isInstanceOf[FinalPayload.Standard])
|
||||
assert(payload_e.amount == finalAmount)
|
||||
assert(payload_e.expiry == finalExpiry)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentSecret == paymentSecret)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentMetadata.contains(hex"010203"))
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].isTrampoline)
|
||||
}
|
||||
|
||||
test("build outgoing trampoline payment with non-trampoline recipient") {
|
||||
|
@ -355,7 +358,12 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
|||
val add_e = UpdateAddHtlc(randomBytes32(), 4, amount_de, paymentHash, expiry_de, packet_e, None, 1.0, None)
|
||||
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||
assert(add_e2 == add_e)
|
||||
assert(payload_e == FinalPayload.Standard(TlvStream(AmountToForward(finalAmount), OutgoingCltv(finalExpiry), PaymentData(invoice.paymentSecret, finalAmount), OnionPaymentPayloadTlv.PaymentMetadata(hex"010203"))))
|
||||
assert(payload_e.isInstanceOf[FinalPayload.Standard])
|
||||
assert(payload_e.amount == finalAmount)
|
||||
assert(payload_e.expiry == finalExpiry)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentSecret == invoice.paymentSecret)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentMetadata.contains(hex"010203"))
|
||||
assert(!payload_e.asInstanceOf[FinalPayload.Standard].isTrampoline)
|
||||
}
|
||||
|
||||
test("build outgoing trampoline payment with non-trampoline recipient and dummy trampoline packet") {
|
||||
|
@ -412,7 +420,12 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
|||
val add_e = UpdateAddHtlc(randomBytes32(), 4, amount_de, paymentHash, expiry_de, packet_e, None, 1.0, None)
|
||||
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||
assert(add_e2 == add_e)
|
||||
assert(payload_e == FinalPayload.Standard(TlvStream(AmountToForward(finalAmount), OutgoingCltv(finalExpiry), PaymentData(invoice.paymentSecret, finalAmount), OnionPaymentPayloadTlv.PaymentMetadata(hex"010203"))))
|
||||
assert(payload_e.isInstanceOf[FinalPayload.Standard])
|
||||
assert(payload_e.amount == finalAmount)
|
||||
assert(payload_e.expiry == finalExpiry)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentSecret == invoice.paymentSecret)
|
||||
assert(payload_e.asInstanceOf[FinalPayload.Standard].paymentMetadata.contains(hex"010203"))
|
||||
assert(!payload_e.asInstanceOf[FinalPayload.Standard].isTrampoline)
|
||||
}
|
||||
|
||||
test("fail to build outgoing payment with invalid route") {
|
||||
|
@ -649,15 +662,15 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
|||
|
||||
// e returns a failure
|
||||
val failure = IncorrectOrUnknownPaymentDetails(finalAmount, BlockHeight(currentBlockCount))
|
||||
val Right(fail_e: UpdateFailHtlc) = buildHtlcFailure(priv_e.privateKey, CMD_FAIL_HTLC(add_e.id, Right(failure)), add_e)
|
||||
val Right(fail_e: UpdateFailHtlc) = buildHtlcFailure(priv_e.privateKey, CMD_FAIL_HTLC(add_e.id, FailureReason.LocalFailure(failure)), add_e)
|
||||
assert(fail_e.id == add_e.id)
|
||||
val Right(fail_d: UpdateFailHtlc) = buildHtlcFailure(priv_d.privateKey, CMD_FAIL_HTLC(add_d.id, Left(fail_e.reason)), add_d)
|
||||
val Right(fail_d: UpdateFailHtlc) = buildHtlcFailure(priv_d.privateKey, CMD_FAIL_HTLC(add_d.id, FailureReason.EncryptedDownstreamFailure(fail_e.reason)), add_d)
|
||||
assert(fail_d.id == add_d.id)
|
||||
val Right(fail_c: UpdateFailHtlc) = buildHtlcFailure(priv_c.privateKey, CMD_FAIL_HTLC(add_c.id, Left(fail_d.reason)), add_c)
|
||||
val Right(fail_c: UpdateFailHtlc) = buildHtlcFailure(priv_c.privateKey, CMD_FAIL_HTLC(add_c.id, FailureReason.EncryptedDownstreamFailure(fail_d.reason)), add_c)
|
||||
assert(fail_c.id == add_c.id)
|
||||
val Right(fail_b: UpdateFailHtlc) = buildHtlcFailure(priv_b.privateKey, CMD_FAIL_HTLC(add_b.id, Left(fail_c.reason)), add_b)
|
||||
val Right(fail_b: UpdateFailHtlc) = buildHtlcFailure(priv_b.privateKey, CMD_FAIL_HTLC(add_b.id, FailureReason.EncryptedDownstreamFailure(fail_c.reason)), add_b)
|
||||
assert(fail_b.id == add_b.id)
|
||||
val Success(Sphinx.DecryptedFailurePacket(failingNode, decryptedFailure)) = Sphinx.FailurePacket.decrypt(fail_b.reason, payment.sharedSecrets)
|
||||
val Right(Sphinx.DecryptedFailurePacket(failingNode, decryptedFailure)) = Sphinx.FailurePacket.decrypt(fail_b.reason, payment.sharedSecrets)
|
||||
assert(failingNode == e)
|
||||
assert(decryptedFailure == failure)
|
||||
}
|
||||
|
@ -679,21 +692,21 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
|||
assert(payload_e.isInstanceOf[FinalPayload.Blinded])
|
||||
|
||||
// nodes after the introduction node cannot send `update_fail_htlc` messages
|
||||
val Right(fail_e: UpdateFailMalformedHtlc) = buildHtlcFailure(priv_e.privateKey, CMD_FAIL_HTLC(add_e.id, Right(TemporaryNodeFailure())), add_e)
|
||||
val Right(fail_e: UpdateFailMalformedHtlc) = buildHtlcFailure(priv_e.privateKey, CMD_FAIL_HTLC(add_e.id, FailureReason.LocalFailure(TemporaryNodeFailure())), add_e)
|
||||
assert(fail_e.id == add_e.id)
|
||||
assert(fail_e.onionHash == Sphinx.hash(add_e.onionRoutingPacket))
|
||||
assert(fail_e.failureCode == InvalidOnionBlinding(fail_e.onionHash).code)
|
||||
val Right(fail_d: UpdateFailMalformedHtlc) = buildHtlcFailure(priv_d.privateKey, CMD_FAIL_HTLC(add_d.id, Right(UnknownNextPeer())), add_d)
|
||||
val Right(fail_d: UpdateFailMalformedHtlc) = buildHtlcFailure(priv_d.privateKey, CMD_FAIL_HTLC(add_d.id, FailureReason.LocalFailure(UnknownNextPeer())), add_d)
|
||||
assert(fail_d.id == add_d.id)
|
||||
assert(fail_d.onionHash == Sphinx.hash(add_d.onionRoutingPacket))
|
||||
assert(fail_d.failureCode == InvalidOnionBlinding(fail_d.onionHash).code)
|
||||
// only the introduction node is allowed to send an `update_fail_htlc` message
|
||||
val failure = InvalidOnionBlinding(Sphinx.hash(add_c.onionRoutingPacket))
|
||||
val Right(fail_c: UpdateFailHtlc) = buildHtlcFailure(priv_c.privateKey, CMD_FAIL_HTLC(add_c.id, Right(failure)), add_c)
|
||||
val Right(fail_c: UpdateFailHtlc) = buildHtlcFailure(priv_c.privateKey, CMD_FAIL_HTLC(add_c.id, FailureReason.LocalFailure(failure)), add_c)
|
||||
assert(fail_c.id == add_c.id)
|
||||
val Right(fail_b: UpdateFailHtlc) = buildHtlcFailure(priv_b.privateKey, CMD_FAIL_HTLC(add_b.id, Left(fail_c.reason)), add_b)
|
||||
val Right(fail_b: UpdateFailHtlc) = buildHtlcFailure(priv_b.privateKey, CMD_FAIL_HTLC(add_b.id, FailureReason.EncryptedDownstreamFailure(fail_c.reason)), add_b)
|
||||
assert(fail_b.id == add_b.id)
|
||||
val Success(Sphinx.DecryptedFailurePacket(failingNode, decryptedFailure)) = Sphinx.FailurePacket.decrypt(fail_b.reason, payment.sharedSecrets)
|
||||
val Right(Sphinx.DecryptedFailurePacket(failingNode, decryptedFailure)) = Sphinx.FailurePacket.decrypt(fail_b.reason, payment.sharedSecrets)
|
||||
assert(failingNode == c)
|
||||
assert(decryptedFailure == failure)
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ 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)))
|
||||
channel.expectMsgAllOf(
|
||||
CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_HTLC(1, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_MALFORMED_HTLC(4, ByteVector32.Zeroes, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 24, commit = true)
|
||||
)
|
||||
channel.expectNoMessage(100 millis)
|
||||
|
@ -130,15 +130,15 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
|||
// 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)))
|
||||
channel.expectMsgAllOf(
|
||||
CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure()), commit = true)
|
||||
CMD_FAIL_HTLC(0, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_HTLC(4, FailureReason.LocalFailure(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)))
|
||||
channel.expectMsgAllOf(
|
||||
CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_HTLC(1, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_MALFORMED_HTLC(4, ByteVector32.Zeroes, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 24, commit = true)
|
||||
)
|
||||
channel.expectNoMessage(100 millis)
|
||||
|
@ -225,10 +225,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, FailureReason.LocalFailure(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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true)
|
||||
)
|
||||
val received1 = expected1.map(_ => channel.expectMsgType[Command])
|
||||
assert(received1 == expected1)
|
||||
|
@ -237,10 +237,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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FAIL_HTLC(3, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||
CMD_FULFILL_HTLC(4, preimage, commit = true),
|
||||
CMD_FAIL_HTLC(9, Right(TemporaryNodeFailure()), commit = true)
|
||||
CMD_FAIL_HTLC(9, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true)
|
||||
)
|
||||
val received2 = expected2.map(_ => channel.expectMsgType[Command])
|
||||
assert(received2 == expected2)
|
||||
|
@ -447,8 +447,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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
channel_upstream_2.expectMsg(CMD_FAIL_HTLC(7, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
channel_upstream_1.expectNoMessage(100 millis)
|
||||
channel_upstream_2.expectNoMessage(100 millis)
|
||||
|
||||
|
@ -475,7 +475,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
|||
)
|
||||
val channelData = ChannelCodecsSpec.makeChannelDataNormal(htlc_ab, Map.empty)
|
||||
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, FailureReason.LocalFailure(PermanentChannelFailure())))
|
||||
|
||||
val (_, postRestart) = f.createRelayer(nodeParams)
|
||||
postRestart ! PostRestartHtlcCleaner.Init(List(channelData))
|
||||
|
@ -593,7 +593,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
|||
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.upstream_1))
|
||||
val fails = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil
|
||||
assert(fails.toSet == testCase.upstream_1.originHtlcs.map {
|
||||
case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), commit = true))
|
||||
case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
}.toSet)
|
||||
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.upstream_1))
|
||||
|
@ -605,7 +605,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
|||
|
||||
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.upstream_2))
|
||||
register.expectMsg(testCase.upstream_2.originHtlcs.map {
|
||||
case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, Right(TemporaryNodeFailure()), commit = true))
|
||||
case Upstream.Cold.Channel(channelId, htlcId, _) => Register.Forward(null, channelId, CMD_FAIL_HTLC(htlcId, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
}.head)
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -736,7 +736,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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
channel.expectNoMessage(100 millis)
|
||||
}
|
||||
|
||||
|
@ -754,8 +754,8 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
|||
}
|
||||
// @formatter:on
|
||||
|
||||
val cmd1 = CMD_FAIL_HTLC(id = 0L, reason = Left(ByteVector.empty), replyTo_opt = None)
|
||||
val cmd2 = CMD_FAIL_HTLC(id = 1L, reason = Left(ByteVector.empty), replyTo_opt = None)
|
||||
val cmd1 = CMD_FAIL_HTLC(id = 0L, reason = FailureReason.EncryptedDownstreamFailure(ByteVector.empty), replyTo_opt = None)
|
||||
val cmd2 = CMD_FAIL_HTLC(id = 1L, reason = FailureReason.EncryptedDownstreamFailure(ByteVector.empty), replyTo_opt = None)
|
||||
val nodeParams1 = nodeParams.copy(pluginParams = List(pluginParams))
|
||||
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd1)
|
||||
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd2)
|
||||
|
|
|
@ -116,7 +116,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
if (success) {
|
||||
expectFwdAdd(register, lcu.channelId, outgoingAmount, outgoingExpiry, 7)
|
||||
} 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, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
fwd1.message.replyTo ! RES_ADD_FAILED(fwd2.message, HtlcValueTooHighInFlight(channelIds(realScid1), 1000000000 msat, 1516977616 msat), Some(u1.channelUpdate))
|
||||
|
||||
// the relayer should give up
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(u1.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(TemporaryChannelFailure(Some(u1.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when we have no channel_update for the next channel") { f =>
|
||||
|
@ -359,7 +359,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
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, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when register returns an error") { f =>
|
||||
|
@ -375,7 +375,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
|
||||
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, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when the channel is advertised as unusable (down)") { f =>
|
||||
|
@ -390,7 +390,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! WrappedLocalChannelDown(d)
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
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, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when channel is disabled") { f =>
|
||||
|
@ -403,7 +403,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(u.channelUpdate.messageFlags, u.channelUpdate.channelFlags, Some(u.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(ChannelDisabled(u.channelUpdate.messageFlags, u.channelUpdate.channelFlags, Some(u.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when amount is below minimum") { f =>
|
||||
|
@ -416,7 +416,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(AmountBelowMinimum(outgoingAmount, Some(u.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(AmountBelowMinimum(outgoingAmount, Some(u.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay blinded payment") { f =>
|
||||
|
@ -436,7 +436,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
assert(cmd.message.isInstanceOf[CMD_FAIL_HTLC])
|
||||
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
||||
assert(fail.id == r.add.id)
|
||||
assert(fail.reason == Right(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
assert(fail.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
assert(fail.delay_opt.nonEmpty)
|
||||
} else {
|
||||
assert(cmd.message.isInstanceOf[CMD_FAIL_MALFORMED_HTLC])
|
||||
|
@ -464,7 +464,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0)
|
||||
assert(switchboard.expectMessageType[Switchboard.GetPeerInfo].remoteNodeId == outgoingNodeId)
|
||||
val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fail.message.reason.contains(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
assert(fail.message.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
|
||||
cleanUpWakeUpActors(peerReadyManager, switchboard)
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(r.outgoingCltv, Some(u.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(IncorrectCltvExpiry(r.outgoingCltv, Some(u.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when fee is insufficient") { f =>
|
||||
|
@ -505,7 +505,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(u.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(FeeInsufficient(r.add.amountMsat, Some(u.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("relay that would fail (fee insufficient) with a recent channel update but succeed with the previous update") { f =>
|
||||
|
@ -536,7 +536,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
|
||||
// relay fails because the current update (u3) with higher fees occurred more than 10 minutes ago
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(FeeInsufficient(r.add.amountMsat, Some(u3.channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(FeeInsufficient(r.add.amountMsat, Some(u3.channelUpdate))), commit = true))
|
||||
}
|
||||
|
||||
test("fail to relay when there is a local error") { f =>
|
||||
|
@ -565,7 +565,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
|
||||
fwd.message.replyTo ! RES_ADD_FAILED(fwd.message, testCase.exc, Some(testCase.update))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(testCase.failure), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(testCase.failure), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -611,7 +611,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
val cmd4 = expectFwdAdd(register, channelUpdates(ShortChannelId(11111)).channelId, r.amountToForward, r.outgoingCltv, 5).message
|
||||
cmd4.replyTo ! RES_ADD_FAILED(cmd4, HtlcValueTooHighInFlight(randomBytes32(), 100000000 msat, 100000000 msat), Some(channelUpdates(ShortChannelId(11111)).channelUpdate))
|
||||
// all the suitable channels have been tried
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(TemporaryChannelFailure(Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true))
|
||||
}
|
||||
{
|
||||
// higher amount payment (have to increased incoming htlc amount for fees to be sufficient)
|
||||
|
@ -646,7 +646,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
val payload = ChannelRelay.Standard(ShortChannelId(12345), 998900 msat, CltvExpiry(61))
|
||||
val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70))
|
||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, Right(IncorrectCltvExpiry(CltvExpiry(61), Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true))
|
||||
expectFwdFail(register, r.add.channelId, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(IncorrectCltvExpiry(CltvExpiry(61), Some(channelUpdates(ShortChannelId(12345)).channelUpdate))), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -662,11 +662,11 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
case class TestCase(result: HtlcResult, cmd: channel.HtlcSettlementCommand)
|
||||
|
||||
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 | FailureMessageCodecs.PERM | 5)), CMD_FAIL_HTLC(r.add.id, Right(InvalidOnionHmac(ByteVector32.One)), commit = true)),
|
||||
TestCase(HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId1, downstream_htlc)), CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure()), commit = true)),
|
||||
TestCase(HtlcResult.DisconnectedBeforeSigned(u_disabled.channelUpdate), CMD_FAIL_HTLC(r.add.id, Right(TemporaryChannelFailure(Some(u_disabled.channelUpdate))), commit = true)),
|
||||
TestCase(HtlcResult.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, Right(PermanentChannelFailure()), commit = true))
|
||||
TestCase(HtlcResult.RemoteFail(UpdateFailHtlc(channelId1, downstream_htlc.id, hex"deadbeef")), CMD_FAIL_HTLC(r.add.id, FailureReason.EncryptedDownstreamFailure(hex"deadbeef"), commit = true)),
|
||||
TestCase(HtlcResult.RemoteFailMalformed(UpdateFailMalformedHtlc(channelId1, downstream_htlc.id, ByteVector32.One, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 5)), CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(InvalidOnionHmac(ByteVector32.One)), commit = true)),
|
||||
TestCase(HtlcResult.OnChainFail(HtlcOverriddenByLocalCommit(channelId1, downstream_htlc)), CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)),
|
||||
TestCase(HtlcResult.DisconnectedBeforeSigned(u_disabled.channelUpdate), CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(TemporaryChannelFailure(Some(u_disabled.channelUpdate))), commit = true)),
|
||||
TestCase(HtlcResult.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true))
|
||||
)
|
||||
|
||||
testCases.foreach { testCase =>
|
||||
|
@ -707,7 +707,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
|||
assert(cmd.message.isInstanceOf[CMD_FAIL_HTLC])
|
||||
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
||||
assert(fail.id == r.add.id)
|
||||
assert(fail.reason == Right(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
assert(fail.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket))))
|
||||
assert(fail.delay_opt.nonEmpty)
|
||||
} else {
|
||||
assert(cmd.message.isInstanceOf[CMD_FAIL_MALFORMED_HTLC])
|
||||
|
|
|
@ -229,7 +229,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 = FailureReason.LocalFailure(PaymentTimeout())
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, failure, commit = true))
|
||||
}
|
||||
|
||||
|
@ -254,8 +254,8 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
// the extra payment will be rejected
|
||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == extra.add.channelId)
|
||||
val failure = IncorrectOrUnknownPaymentDetails(extra.add.amountMsat, nodeParams.currentBlockHeight)
|
||||
assert(fwd.message == CMD_FAIL_HTLC(extra.add.id, Right(failure), commit = true))
|
||||
val failure = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(extra.add.amountMsat, nodeParams.currentBlockHeight))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(extra.add.id, failure, commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -282,8 +282,8 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
|
||||
val fwd1 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd1.channelId == i1.add.channelId)
|
||||
val failure1 = IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)
|
||||
assert(fwd1.message == CMD_FAIL_HTLC(i1.add.id, Right(failure1), commit = true))
|
||||
val failure1 = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight))
|
||||
assert(fwd1.message == CMD_FAIL_HTLC(i1.add.id, failure1, commit = true))
|
||||
|
||||
// Receive new HTLC with different details, but for the same payment hash.
|
||||
val i2 = IncomingPaymentPacket.RelayToTrampolinePacket(
|
||||
|
@ -295,8 +295,8 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
|
||||
val fwd2 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd1.channelId == i1.add.channelId)
|
||||
val failure2 = IncorrectOrUnknownPaymentDetails(1500 msat, nodeParams.currentBlockHeight)
|
||||
assert(fwd2.message == CMD_FAIL_HTLC(i2.add.id, Right(failure2), commit = true))
|
||||
val failure2 = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1500 msat, nodeParams.currentBlockHeight))
|
||||
assert(fwd2.message == CMD_FAIL_HTLC(i2.add.id, failure2, commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -312,7 +312,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, FailureReason.LocalFailure(TrampolineExpiryTooSoon()), commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -328,7 +328,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, FailureReason.LocalFailure(TrampolineExpiryTooSoon()), commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -349,7 +349,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, FailureReason.LocalFailure(TrampolineExpiryTooSoon()), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -398,7 +398,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, FailureReason.LocalFailure(TrampolineFeeInsufficient()), commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -416,7 +416,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, FailureReason.LocalFailure(TrampolineFeeInsufficient()), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -431,7 +431,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(InvalidOnionPayload(UInt64(2), 0)), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(InvalidOnionPayload(UInt64(2), 0)), commit = true))
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
}
|
||||
|
@ -449,7 +449,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(InvalidOnionPayload(UInt64(2), 0)), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(InvalidOnionPayload(UInt64(2), 0)), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -471,7 +471,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, FailureReason.LocalFailure(TrampolineFeeInsufficient()), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -496,7 +496,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, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -519,7 +519,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, FailureReason.LocalFailure(TrampolineFeeInsufficient()), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -535,13 +535,13 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
val payFSM = mockPayFSM.expectMessageType[akka.actor.ActorRef]
|
||||
router.expectMessageType[RouteRequest]
|
||||
|
||||
val failures = RemoteFailure(outgoingAmount, Nil, Sphinx.DecryptedFailurePacket(outgoingNodeId, FinalIncorrectHtlcAmount(42 msat))) :: UnreadableRemoteFailure(outgoingAmount, Nil) :: Nil
|
||||
val failures = RemoteFailure(outgoingAmount, Nil, Sphinx.DecryptedFailurePacket(outgoingNodeId, FinalIncorrectHtlcAmount(42 msat))) :: UnreadableRemoteFailure(outgoingAmount, Nil, ByteVector.empty) :: 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(FinalIncorrectHtlcAmount(42 msat)), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(FinalIncorrectHtlcAmount(42 msat)), commit = true))
|
||||
}
|
||||
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -702,7 +702,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(TemporaryNodeFailure()), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||
}
|
||||
parent.expectMessageType[NodeRelayer.RelayComplete]
|
||||
}
|
||||
|
@ -923,7 +923,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
incomingPayments.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(UnknownNextPeer()), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -993,7 +993,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
incomingPayments.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(UnknownNextPeer()), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1054,7 +1054,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
|||
incomingPayments.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(UnknownNextPeer()), commit = true))
|
||||
assert(fwd.message == CMD_FAIL_HTLC(p.add.id, FailureReason.LocalFailure(UnknownNextPeer()), commit = true))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -230,7 +230,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd1 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd1.channelId == upstream1.add.channelId)
|
||||
assert(fwd1.message.id == upstream1.add.id)
|
||||
assert(fwd1.message.reason == Left(fail1.reason))
|
||||
assert(fwd1.message.reason == FailureReason.EncryptedDownstreamFailure(fail1.reason))
|
||||
register.expectNoMessage(100 millis)
|
||||
|
||||
val fail2 = WillFailHtlc(willAdd2.id, paymentHash, randomBytes(50))
|
||||
|
@ -238,14 +238,14 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd2 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd2.channelId == upstream2.add.channelId)
|
||||
assert(fwd2.message.id == upstream2.add.id)
|
||||
assert(fwd2.message.reason == Right(InvalidOnionBlinding(Sphinx.hash(upstream2.add.onionRoutingPacket))))
|
||||
assert(fwd2.message.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(upstream2.add.onionRoutingPacket))))
|
||||
|
||||
val fail3 = WillFailMalformedHtlc(willAdd3.id, paymentHash, randomBytes32(), InvalidOnionHmac(randomBytes32()).code)
|
||||
peerConnection.send(peer, fail3)
|
||||
val fwd3 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd3.channelId == upstream3.add.channelId)
|
||||
assert(fwd3.message.id == upstream3.add.id)
|
||||
assert(fwd3.message.reason == Right(InvalidOnionHmac(fail3.onionHash)))
|
||||
assert(fwd3.message.reason == FailureReason.LocalFailure(InvalidOnionHmac(fail3.onionHash)))
|
||||
|
||||
val fail4 = WillFailHtlc(willAdd4.id, paymentHash, randomBytes(75))
|
||||
peerConnection.send(peer, fail4)
|
||||
|
@ -253,7 +253,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == add.channelId)
|
||||
assert(fwd.message.id == add.id)
|
||||
assert(fwd.message.reason == Right(TemporaryNodeFailure()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(TemporaryNodeFailure()))
|
||||
})
|
||||
|
||||
val fail5 = WillFailHtlc(willAdd5.id, paymentHash, randomBytes(75))
|
||||
|
@ -262,7 +262,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == add.channelId)
|
||||
assert(fwd.message.id == add.id)
|
||||
assert(fwd.message.reason == Right(TemporaryNodeFailure()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(TemporaryNodeFailure()))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -322,7 +322,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == u.add.channelId)
|
||||
assert(fwd.message.id == u.add.id)
|
||||
assert(fwd.message.reason == Right(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket))))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket))))
|
||||
assert(fwd.message.commit)
|
||||
})
|
||||
peerConnection.expectMsgType[Warning]
|
||||
|
@ -332,7 +332,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == u.add.channelId)
|
||||
assert(fwd.message.id == u.add.id)
|
||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
assert(fwd.message.commit)
|
||||
})
|
||||
peerConnection.expectMsgType[Warning]
|
||||
|
@ -402,7 +402,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwds = (0 until 5).map(_ => register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]])
|
||||
register.expectNoMessage(100 millis)
|
||||
fwds.foreach(fwd => {
|
||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
assert(fwd.message.commit)
|
||||
})
|
||||
assert(fwds.map(_.channelId).toSet == (upstream1 ++ upstream2.slice(0, 1) ++ upstream3.received).map(_.add.channelId).toSet)
|
||||
|
@ -1151,7 +1151,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == add.channelId)
|
||||
assert(fwd.message.id == add.id)
|
||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
assert(fwd.message.commit)
|
||||
})
|
||||
register.expectNoMessage(100 millis)
|
||||
|
@ -1176,7 +1176,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
|||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||
assert(fwd.channelId == upstream1.add.channelId)
|
||||
assert(fwd.message.id == upstream1.add.id)
|
||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
||||
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||
assert(fwd.message.commit)
|
||||
register.expectNoMessage(100 millis)
|
||||
probe.expectNoMessage(100 millis)
|
||||
|
|
|
@ -166,7 +166,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(InvalidOnionBlinding(Sphinx.hash(add_ab.onionRoutingPacket))))
|
||||
assert(fail.reason == FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(add_ab.onionRoutingPacket))))
|
||||
assert(fail.delay_opt.nonEmpty)
|
||||
|
||||
register.expectNoMessage(50 millis)
|
||||
|
@ -202,7 +202,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 == FailureReason.LocalFailure(RequiredNodeFeatureMissing()))
|
||||
|
||||
register.expectNoMessage(50 millis)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ 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.{FailureMessageCodecs, FailureMessageTlv, GenericTlv, TemporaryNodeFailure, TlvStream}
|
||||
import fr.acinq.eclair.wire.protocol._
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||
|
||||
|
@ -32,9 +32,9 @@ class CommandCodecsSpec extends AnyFunSuite {
|
|||
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(Set.empty[FailureMessageTlv], Set(GenericTlv(UInt64(17), hex"deadbeef")))))) -> hex"0003 00000000000000fd ff 0008 2002 1104deadbeef",
|
||||
CMD_FAIL_HTLC(42456, FailureReason.EncryptedDownstreamFailure(hex"d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44")) -> hex"0004 000000000000a5d8 00 0091 d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44",
|
||||
CMD_FAIL_HTLC(253, FailureReason.LocalFailure(TemporaryNodeFailure())) -> hex"0004 00000000000000fd 01 0002 2002",
|
||||
CMD_FAIL_HTLC(253, FailureReason.LocalFailure(TemporaryNodeFailure(TlvStream(Set.empty[FailureMessageTlv], Set(GenericTlv(UInt64(17), hex"deadbeef")))))) -> hex"0004 00000000000000fd 01 0008 2002 1104deadbeef",
|
||||
CMD_FAIL_MALFORMED_HTLC(7984, ByteVector32(hex"17cc093e177c7a7fcaa9e96ab407146c8886546a5690f945c98ac20c4ab3b4f3"), FailureMessageCodecs.BADONION) -> hex"0002 0000000000001f30 17cc093e177c7a7fcaa9e96ab407146c8886546a5690f945c98ac20c4ab3b4f38000",
|
||||
)
|
||||
|
||||
|
@ -51,8 +51,11 @@ class CommandCodecsSpec extends AnyFunSuite {
|
|||
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), None, commit = false, None),
|
||||
hex"0001 000000000000002a900100" -> CMD_FAIL_HTLC(42, Right(TemporaryNodeFailure())),
|
||||
hex"0001 000000000000002a003dff53addc67a29a4f5aa26c6d41957ad798777d338f613e7972433dd656d16df00536728a08b2550a9d645a592e3ae1d78ae25ae5b5149b03ba8d03cde2a36d0bfb2a5bb53a5e2bdb590f6b9e969c84f9b41780dc2a0c5078766edbacf4a40ea2b1d2b9560eee5bbe32570b3ec6fdec44b81e5ae19da5cb1b5d6a3900" -> CMD_FAIL_HTLC(42, FailureReason.EncryptedDownstreamFailure(data123), None, commit = false, None),
|
||||
hex"0001 000000000000002a900100" -> CMD_FAIL_HTLC(42, FailureReason.LocalFailure(TemporaryNodeFailure())),
|
||||
hex"0003 000000000000a5d8 00 0091 d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44" -> CMD_FAIL_HTLC(42456, FailureReason.EncryptedDownstreamFailure(hex"d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44")),
|
||||
hex"0003 00000000000000fd ff 0002 2002" -> CMD_FAIL_HTLC(253, FailureReason.LocalFailure(TemporaryNodeFailure())),
|
||||
hex"0003 00000000000000fd ff 0008 2002 1104deadbeef" -> CMD_FAIL_HTLC(253, FailureReason.LocalFailure(TemporaryNodeFailure(TlvStream(Set.empty[FailureMessageTlv], Set(GenericTlv(UInt64(17), hex"deadbeef")))))),
|
||||
hex"0002 000000000000002ae4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb01c8" -> CMD_FAIL_MALFORMED_HTLC(42, data32, 456, commit = false, None),
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue