mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 22:25:26 +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)
|
* [Manage Bitcoin Core's private keys](./ManagingBitcoinCoreKeys.md)
|
||||||
* [Use Tor with Eclair](./Tor.md)
|
* [Use Tor with Eclair](./Tor.md)
|
||||||
* [Multipart Payments](./MultipartPayments.md)
|
* [Multipart Payments](./MultipartPayments.md)
|
||||||
* [Trampoline Payments](./TrampolinePayments.md)
|
|
||||||
* [Monitoring Eclair](./Monitoring.md)
|
* [Monitoring Eclair](./Monitoring.md)
|
||||||
* [PostgreSQL Configuration](./PostgreSQL.md)
|
* [PostgreSQL Configuration](./PostgreSQL.md)
|
||||||
* [Perform Circular Rebalancing](./CircularRebalancing.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.io.Peer
|
||||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
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 fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, TimestampMilli, UInt64}
|
||||||
import scodec.bits.ByteVector
|
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 }
|
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_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_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_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
|
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) =>
|
case PostRevocationAction.RejectHtlc(add) =>
|
||||||
log.debug("rejecting incoming htlc {}", add)
|
log.debug("rejecting incoming htlc {}", add)
|
||||||
// NB: we don't set commit = true, we will sign all updates at once afterwards.
|
// 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) =>
|
case PostRevocationAction.RelayFailure(result) =>
|
||||||
log.debug("forwarding {} to relayer", result)
|
log.debug("forwarding {} to relayer", result)
|
||||||
relayer ! result
|
relayer ! result
|
||||||
|
@ -1544,11 +1544,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with
|
||||||
case PostRevocationAction.RelayHtlc(add) =>
|
case PostRevocationAction.RelayHtlc(add) =>
|
||||||
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
||||||
log.debug("closing in progress: failing {}", add)
|
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) =>
|
case PostRevocationAction.RejectHtlc(add) =>
|
||||||
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
||||||
log.debug("closing in progress: rejecting {}", add)
|
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) =>
|
case PostRevocationAction.RelayFailure(result) =>
|
||||||
log.debug("forwarding {} to relayer", result)
|
log.debug("forwarding {} to relayer", result)
|
||||||
relayer ! result
|
relayer ! result
|
||||||
|
|
|
@ -272,6 +272,13 @@ object Sphinx extends Logging {
|
||||||
*/
|
*/
|
||||||
case class DecryptedFailurePacket(originNode: PublicKey, failureMessage: FailureMessage)
|
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 {
|
object FailurePacket {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -314,18 +321,18 @@ object Sphinx extends Logging {
|
||||||
*
|
*
|
||||||
* @param packet failure packet.
|
* @param packet failure packet.
|
||||||
* @param sharedSecrets nodes shared secrets.
|
* @param sharedSecrets nodes shared secrets.
|
||||||
* @return Success(secret, failure message) if the origin of the packet could be identified and the packet
|
* @return failure message if the origin of the packet could be identified and the packet decrypted, the unwrapped
|
||||||
* decrypted, Failure otherwise.
|
* 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
|
@tailrec
|
||||||
def loop(packet: ByteVector, secrets: Seq[(ByteVector32, PublicKey)]): DecryptedFailurePacket = secrets match {
|
def loop(packet: ByteVector, secrets: Seq[(ByteVector32, PublicKey)]): Either[CannotDecryptFailurePacket, DecryptedFailurePacket] = secrets match {
|
||||||
case Nil => throw new RuntimeException(s"couldn't parse error packet=$packet with sharedSecrets=$sharedSecrets")
|
case Nil => Left(CannotDecryptFailurePacket(packet))
|
||||||
case (secret, pubkey) :: tail =>
|
case (secret, pubkey) :: tail =>
|
||||||
val packet1 = wrap(packet, secret)
|
val packet1 = wrap(packet, secret)
|
||||||
val um = generateKey("um", secret)
|
val um = generateKey("um", secret)
|
||||||
FailureMessageCodecs.failureOnionCodec(Hmac256(um)).decode(packet1.toBitVector) match {
|
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)
|
case _ => loop(packet1, tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,10 +366,10 @@ object Sphinx extends Logging {
|
||||||
case class BlindedHop(blindedPublicKey: PublicKey, encryptedPayload: ByteVector)
|
case class BlindedHop(blindedPublicKey: PublicKey, encryptedPayload: ByteVector)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param firstNodeId the first node, not blinded so that the sender can locate it.
|
* @param firstNodeId the first node, not blinded so that the sender can locate it.
|
||||||
* @param firstPathKey blinding tweak that can be used by the introduction node to derive the private key that
|
* @param firstPathKey blinding tweak that can be used by the introduction node to derive the private key that
|
||||||
* matches the blinded public key.
|
* matches the blinded public key.
|
||||||
* @param blindedHops blinded nodes (including the introduction node).
|
* @param blindedHops blinded nodes (including the introduction node).
|
||||||
*/
|
*/
|
||||||
case class BlindedRoute(firstNodeId: EncodedNodeId, firstPathKey: PublicKey, blindedHops: Seq[BlindedHop]) {
|
case class BlindedRoute(firstNodeId: EncodedNodeId, firstPathKey: PublicKey, blindedHops: Seq[BlindedHop]) {
|
||||||
require(blindedHops.nonEmpty, "blinded route must not be empty")
|
require(blindedHops.nonEmpty, "blinded route must not be empty")
|
||||||
|
|
|
@ -250,7 +250,7 @@ object FailureSummary {
|
||||||
def apply(f: PaymentFailure): FailureSummary = f match {
|
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 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 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.router.Router
|
||||||
import fr.acinq.eclair.wire.protocol
|
import fr.acinq.eclair.wire.protocol
|
||||||
import fr.acinq.eclair.wire.protocol.FailureMessageCodecs.createBadOnionFailure
|
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.
|
* 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 {
|
pending.proposed.find(_.htlc.id == msg.id) match {
|
||||||
case Some(htlc) =>
|
case Some(htlc) =>
|
||||||
val failure = msg match {
|
val failure = msg match {
|
||||||
case msg: WillFailHtlc => Left(msg.reason)
|
case msg: WillFailHtlc => FailureReason.EncryptedDownstreamFailure(msg.reason)
|
||||||
case msg: WillFailMalformedHtlc => Right(createBadOnionFailure(msg.onionHash, msg.failureCode))
|
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) }
|
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)
|
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.bitcoin.scalacompat.Crypto.PublicKey
|
||||||
import fr.acinq.eclair.MilliSatoshi
|
import fr.acinq.eclair.MilliSatoshi
|
||||||
import fr.acinq.eclair.channel.CMD_FAIL_HTLC
|
import fr.acinq.eclair.channel.CMD_FAIL_HTLC
|
||||||
|
import fr.acinq.eclair.wire.protocol.FailureReason
|
||||||
import kamon.Kamon
|
import kamon.Kamon
|
||||||
|
|
||||||
object Monitoring {
|
object Monitoring {
|
||||||
|
@ -127,14 +128,14 @@ object Monitoring {
|
||||||
val Malformed = "MalformedHtlc"
|
val Malformed = "MalformedHtlc"
|
||||||
|
|
||||||
def apply(cmdFail: CMD_FAIL_HTLC): String = cmdFail.reason match {
|
def apply(cmdFail: CMD_FAIL_HTLC): String = cmdFail.reason match {
|
||||||
case Left(_) => Remote
|
case _: FailureReason.EncryptedDownstreamFailure => Remote
|
||||||
case Right(f) => f.getClass.getSimpleName
|
case FailureReason.LocalFailure(f) => f.getClass.getSimpleName
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(pf: PaymentFailure): String = pf match {
|
def apply(pf: PaymentFailure): String = pf match {
|
||||||
case LocalFailure(_, _, t) => t.getClass.getSimpleName
|
case LocalFailure(_, _, t) => t.getClass.getSimpleName
|
||||||
case RemoteFailure(_, _, e) => e.failureMessage.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
|
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). */
|
/** 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 {
|
object PaymentFailure {
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ object PaymentFailure {
|
||||||
}
|
}
|
||||||
case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, _)) =>
|
case RemoteFailure(_, hops, Sphinx.DecryptedFailurePacket(nodeId, _)) =>
|
||||||
ignoreNodeOutgoingEdge(nodeId, hops, ignore)
|
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:
|
// 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 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
|
// - the final recipient: they have no incentive to send garbage since they want that payment
|
||||||
|
|
|
@ -238,7 +238,8 @@ object IncomingPaymentPacket {
|
||||||
case innerPayload =>
|
case innerPayload =>
|
||||||
// We merge contents from the outer and inner payloads.
|
// 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).
|
// 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] = {
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
Sphinx.peel(nodeSecret, Some(add.paymentHash), add.onionRoutingPacket) match {
|
||||||
case Right(Sphinx.DecryptedPacket(_, _, sharedSecret)) =>
|
case Right(Sphinx.DecryptedPacket(_, _, sharedSecret)) => Right(sharedSecret)
|
||||||
val encryptedReason = reason match {
|
|
||||||
case Left(forwarded) => Sphinx.FailurePacket.wrap(forwarded, sharedSecret)
|
|
||||||
case Right(failure) => Sphinx.FailurePacket.create(sharedSecret, failure)
|
|
||||||
}
|
|
||||||
Right(encryptedReason)
|
|
||||||
case Left(_) => Left(CannotExtractSharedSecret(add.channelId, add))
|
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)))
|
ctx.self ! ProcessPacket(add, payload, Some(IncomingStandardPayment(invoice, paymentPreimage, PaymentType.KeySend, TimestampMilli.now(), IncomingPaymentStatus.Pending)))
|
||||||
case _ =>
|
case _ =>
|
||||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, "InvoiceNotFound").increment()
|
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)
|
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) =>
|
case RejectPacket(add, failure) if doHandle(add.paymentHash) =>
|
||||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, failure.getClass.getSimpleName).increment()
|
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)
|
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||||
|
|
||||||
case MultiPartPaymentFSM.MultiPartPaymentFailed(paymentHash, failure, parts) if doHandle(paymentHash) =>
|
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)
|
log.warning("payment with paidAmount={} failed ({})", parts.map(_.amount).sum, failure)
|
||||||
pendingPayments.get(paymentHash).foreach { case (_, handler: ActorRef) => handler ! PoisonPill }
|
pendingPayments.get(paymentHash).foreach { case (_, handler: ActorRef) => handler ! PoisonPill }
|
||||||
parts.collect {
|
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
|
pendingPayments = pendingPayments - paymentHash
|
||||||
}
|
}
|
||||||
|
@ -175,7 +175,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
||||||
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(paymentHash))) {
|
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(paymentHash))) {
|
||||||
failure match {
|
failure match {
|
||||||
case Some(failure) => p 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 {
|
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
|
// 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))
|
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, CMD_FULFILL_HTLC(p.htlc.id, record.paymentPreimage, commit = true))
|
||||||
ctx.system.eventStream.publish(received)
|
ctx.system.eventStream.publish(received)
|
||||||
} else {
|
} 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)
|
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, p.htlc.channelId, cmdFail)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -219,7 +219,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
|
||||||
parts.collect {
|
parts.collect {
|
||||||
case p: MultiPartPaymentFSM.HtlcPart =>
|
case p: MultiPartPaymentFSM.HtlcPart =>
|
||||||
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, "InvoiceNotFound").increment()
|
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)
|
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] = {
|
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.
|
// 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 commonOk = validateCommon(nodeParams, add, payload, record)
|
||||||
val secretOk = validatePaymentSecret(add, payload, record.invoice)
|
val secretOk = validatePaymentSecret(add, payload, record.invoice)
|
||||||
if (commonOk && secretOk) None else Some(cmdFail)
|
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] = {
|
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.
|
// 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 commonOk = validateCommon(nodeParams, add, payload, record)
|
||||||
if (commonOk) None else Some(cmdFail)
|
if (commonOk) None else Some(cmdFail)
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,11 @@ object ChannelRelay {
|
||||||
|
|
||||||
def translateRelayFailure(originHtlcId: Long, fail: HtlcResult.Fail): CMD_FAIL_HTLC = {
|
def translateRelayFailure(originHtlcId: Long, fail: HtlcResult.Fail): CMD_FAIL_HTLC = {
|
||||||
fail match {
|
fail match {
|
||||||
case f: HtlcResult.RemoteFail => CMD_FAIL_HTLC(originHtlcId, Left(f.fail.reason), 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, Right(createBadOnionFailure(f.fail.onionHash, f.fail.failureCode)), 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, Right(PermanentChannelFailure()), commit = true)
|
case _: HtlcResult.OnChainFail => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)
|
||||||
case HtlcResult.ChannelFailureBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(PermanentChannelFailure()), commit = true)
|
case HtlcResult.ChannelFailureBeforeSigned => CMD_FAIL_HTLC(originHtlcId, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true)
|
||||||
case f: HtlcResult.DisconnectedBeforeSigned => CMD_FAIL_HTLC(originHtlcId, Right(TemporaryChannelFailure(Some(f.channelUpdate))), 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) =>
|
case WrappedPeerReadyResult(_: PeerReadyNotifier.PeerUnavailable) =>
|
||||||
Metrics.recordPaymentRelayFailed(Tags.FailureType.WakeUp, Tags.RelayType.Channel)
|
Metrics.recordPaymentRelayFailed(Tags.FailureType.WakeUp, Tags.RelayType.Channel)
|
||||||
context.log.info("rejecting htlc: failed to wake-up remote peer")
|
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) =>
|
case WrappedPeerReadyResult(r: PeerReadyNotifier.PeerReady) =>
|
||||||
context.self ! DoRelay
|
context.self ! DoRelay
|
||||||
relay(Some(r.remoteFeatures), Seq.empty)
|
relay(Some(r.remoteFeatures), Seq.empty)
|
||||||
|
@ -201,7 +201,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
||||||
Behaviors.receiveMessagePartial {
|
Behaviors.receiveMessagePartial {
|
||||||
case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) =>
|
case WrappedForwardFailure(Register.ForwardFailure(Register.Forward(_, channelId, _))) =>
|
||||||
context.log.warn(s"couldn't resolve downstream channel $channelId, failing htlc #${upstream.add.id}")
|
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)
|
Metrics.recordPaymentRelayFailed(Tags.FailureType(cmdFail), Tags.RelayType.Channel)
|
||||||
safeSendAndStop(upstream.add.channelId, cmdFail)
|
safeSendAndStop(upstream.add.channelId, cmdFail)
|
||||||
|
|
||||||
|
@ -260,7 +260,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
// We are the introduction node: we add a delay to make it look like it could come from further downstream.
|
// 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)
|
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 =>
|
case None =>
|
||||||
// We are not the introduction node.
|
// We are not the introduction node.
|
||||||
CMD_FAIL_MALFORMED_HTLC(cmd.id, failure.onionHash, failure.code, commit = true)
|
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.
|
// Otherwise we return the error for the first channel tried.
|
||||||
.getOrElse(previousFailures.head)
|
.getOrElse(previousFailures.head)
|
||||||
.failure
|
.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 {
|
} 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 {
|
walletNodeId_opt match {
|
||||||
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
|
case Some(walletNodeId) if shouldAttemptOnTheFlyFunding(remoteFeatures_opt, previousFailures) => RelayNeedsFunding(walletNodeId, cmdFail)
|
||||||
|
@ -326,7 +326,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
||||||
channel.channelUpdate,
|
channel.channelUpdate,
|
||||||
relayResult match {
|
relayResult match {
|
||||||
case _: RelaySuccess => "success"
|
case _: RelaySuccess => "success"
|
||||||
case RelayFailure(CMD_FAIL_HTLC(_, Right(failureReason), _, _, _)) => failureReason
|
case RelayFailure(CMD_FAIL_HTLC(_, FailureReason.LocalFailure(failureReason), _, _, _)) => failureReason
|
||||||
case other => other
|
case other => other
|
||||||
})
|
})
|
||||||
(channel, relayResult)
|
(channel, relayResult)
|
||||||
|
@ -373,7 +373,7 @@ class ChannelRelay private(nodeParams: NodeParams,
|
||||||
case Some(fail) =>
|
case Some(fail) =>
|
||||||
RelayFailure(fail)
|
RelayFailure(fail)
|
||||||
case None if !update.channelFlags.isEnabled =>
|
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 =>
|
case None =>
|
||||||
val origin = Origin.Hot(addResponseAdapter.toClassic, upstream)
|
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))
|
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 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)
|
val feesOk = nodeFee(update.relayFees, r.amountToForward) <= r.relayFeeMsat || prevUpdate_opt.exists(u => nodeFee(u.relayFees, r.amountToForward) <= r.relayFeeMsat)
|
||||||
if (!htlcMinimumOk) {
|
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) {
|
} 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) {
|
} 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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -476,7 +476,7 @@ class NodeRelay private(nodeParams: NodeParams,
|
||||||
|
|
||||||
private def rejectHtlc(htlcId: Long, channelId: ByteVector32, amount: MilliSatoshi, failure: Option[FailureMessage] = None): Unit = {
|
private def rejectHtlc(htlcId: Long, channelId: ByteVector32, amount: MilliSatoshi, failure: Option[FailureMessage] = None): Unit = {
|
||||||
val failureMessage = failure.getOrElse(IncorrectOrUnknownPaymentDetails(amount, nodeParams.currentBlockHeight))
|
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)
|
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.LiquidityAds.PaymentDetails
|
||||||
import fr.acinq.eclair.wire.protocol._
|
import fr.acinq.eclair.wire.protocol._
|
||||||
import fr.acinq.eclair.{Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, ToMilliSatoshiConversion}
|
import fr.acinq.eclair.{Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, TimestampMilli, ToMilliSatoshiConversion}
|
||||||
import scodec.bits.ByteVector
|
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
|
@ -93,22 +92,25 @@ object OnTheFlyFunding {
|
||||||
def maxFees(htlcMinimum: MilliSatoshi): MilliSatoshi = htlc.amount - htlcMinimum
|
def maxFees(htlcMinimum: MilliSatoshi): MilliSatoshi = htlc.amount - htlcMinimum
|
||||||
|
|
||||||
/** Create commands to fail all upstream HTLCs. */
|
/** 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 _: Upstream.Local => Nil
|
||||||
case u: Upstream.Hot.Channel =>
|
case u: Upstream.Hot.Channel =>
|
||||||
val failure = htlc.pathKey_opt match {
|
val failure = htlc.pathKey_opt match {
|
||||||
case Some(_) => Right(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket)))
|
case Some(_) => FailureReason.LocalFailure(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket)))
|
||||||
case None => failure_opt.getOrElse(Right(UnknownNextPeer()))
|
case None => failure_opt.getOrElse(FailureReason.LocalFailure(UnknownNextPeer()))
|
||||||
}
|
}
|
||||||
Seq(u.add.channelId -> CMD_FAIL_HTLC(u.add.id, failure, commit = true))
|
Seq(u.add.channelId -> CMD_FAIL_HTLC(u.add.id, failure, commit = true))
|
||||||
case u: Upstream.Hot.Trampoline =>
|
case u: Upstream.Hot.Trampoline =>
|
||||||
// In the trampoline case, we currently ignore downstream failures: we should add dedicated failures to the
|
// In the trampoline case, we currently ignore downstream failures: we should add dedicated failures to the
|
||||||
// BOLTs to better handle those cases.
|
// BOLTs to better handle those cases.
|
||||||
val failure = failure_opt match {
|
val failure = failure_opt match {
|
||||||
case Some(f) => f.getOrElse(TemporaryNodeFailure())
|
case Some(f) => f match {
|
||||||
case None => UnknownNextPeer()
|
case _: FailureReason.EncryptedDownstreamFailure => FailureReason.LocalFailure(TemporaryNodeFailure())
|
||||||
|
case _: FailureReason.LocalFailure => f
|
||||||
|
}
|
||||||
|
case None => FailureReason.LocalFailure(UnknownNextPeer())
|
||||||
}
|
}
|
||||||
u.received.map(_.add).map(add => add.channelId -> CMD_FAIL_HTLC(add.id, Right(failure), commit = true))
|
u.received.map(_.add).map(add => add.channelId -> CMD_FAIL_HTLC(add.id, failure, commit = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create commands to fulfill all upstream HTLCs. */
|
/** 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.Monitoring.Tags
|
||||||
import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, PaymentFailed, PaymentSent}
|
import fr.acinq.eclair.payment.{ChannelPaymentRelayed, IncomingPaymentPacket, PaymentFailed, PaymentSent}
|
||||||
import fr.acinq.eclair.transactions.DirectedHtlc.outgoing
|
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 fr.acinq.eclair.{CustomCommitmentsPlugin, Feature, Features, Logs, MilliSatoshiLong, NodeParams, TimestampMilli}
|
||||||
|
|
||||||
import scala.concurrent.Promise
|
import scala.concurrent.Promise
|
||||||
|
@ -136,7 +136,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial
|
||||||
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
|
val failure = InvalidOnionBlinding(ByteVector32.Zeroes)
|
||||||
CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true)
|
CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true)
|
||||||
case None =>
|
case None =>
|
||||||
CMD_FAIL_HTLC(htlc.id, Right(TemporaryNodeFailure()), commit = true)
|
CMD_FAIL_HTLC(htlc.id, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true)
|
||||||
}
|
}
|
||||||
channel ! cmd
|
channel ! cmd
|
||||||
} else {
|
} 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()
|
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
|
// 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.
|
// 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) =>
|
case Right(r: IncomingPaymentPacket.NodeRelayPacket) =>
|
||||||
if (!nodeParams.enableTrampolinePayment) {
|
if (!nodeParams.enableTrampolinePayment) {
|
||||||
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=trampoline disabled")
|
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 {
|
} else {
|
||||||
nodeRelayer ! NodeRelayer.Relay(r, originNode)
|
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
|
// 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.
|
// could come from a downstream node.
|
||||||
val delay = Some(500.millis + Random.nextLong(1500).millis)
|
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 _ =>
|
case _ =>
|
||||||
CMD_FAIL_MALFORMED_HTLC(add.id, badOnion.onionHash, badOnion.code, commit = true)
|
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)
|
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
|
||||||
case Left(failure) =>
|
case Left(failure) =>
|
||||||
log.warning(s"rejecting htlc #${add.id} from channelId=${add.channelId} reason=$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)
|
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 fr.acinq.eclair.wire.protocol._
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import scala.util.{Failure, Success}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 26/08/2016.
|
* 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) = {
|
private def handleRemoteFail(d: WaitingForComplete, fail: UpdateFailHtlc) = {
|
||||||
import d._
|
import d._
|
||||||
((Sphinx.FailurePacket.decrypt(fail.reason, sharedSecrets) match {
|
((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()
|
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(RemoteFailure(request.amount, Nil, e))).increment()
|
||||||
success
|
success
|
||||||
case failure@Failure(_) =>
|
case failure@Left(e) =>
|
||||||
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(UnreadableRemoteFailure(request.amount, Nil))).increment()
|
Metrics.PaymentError.withTag(Tags.Failure, Tags.FailureType(UnreadableRemoteFailure(request.amount, Nil, e.unwrapped))).increment()
|
||||||
failure
|
failure
|
||||||
}) match {
|
}) 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.
|
// We have discovered some liquidity information with this payment: we update the router accordingly.
|
||||||
val stoppedRoute = route.stopAt(nodeId)
|
val stoppedRoute = route.stopAt(nodeId)
|
||||||
if (stoppedRoute.hops.length > 1) {
|
if (stoppedRoute.hops.length > 1) {
|
||||||
|
@ -198,39 +197,39 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
||||||
res
|
res
|
||||||
case res => res
|
case res => res
|
||||||
}) match {
|
}) 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
|
// 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)")
|
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))))
|
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
|
// 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)")
|
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))))
|
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ RemoteFailure(request.amount, route.fullRoute, e))))
|
||||||
case res if failures.size + 1 >= request.maxAttempts =>
|
case res if failures.size + 1 >= request.maxAttempts =>
|
||||||
// otherwise we never try more than maxAttempts, no matter the kind of error returned
|
// otherwise we never try more than maxAttempts, no matter the kind of error returned
|
||||||
val failure = res match {
|
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)")
|
log.info(s"received an error message from nodeId=$nodeId (failure=$failureMessage)")
|
||||||
failureMessage match {
|
failureMessage match {
|
||||||
case failureMessage: Update => handleUpdate(nodeId, failureMessage, d)
|
case failureMessage: Update => handleUpdate(nodeId, failureMessage, d)
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
RemoteFailure(request.amount, route.fullRoute, e)
|
RemoteFailure(request.amount, route.fullRoute, e)
|
||||||
case Failure(t) =>
|
case Left(Sphinx.CannotDecryptFailurePacket(unwrapped)) =>
|
||||||
log.warning(s"cannot parse returned error ${fail.reason.toHex} with sharedSecrets=$sharedSecrets: ${t.getMessage}")
|
log.warning(s"cannot parse returned error ${fail.reason.toHex} with sharedSecrets=$sharedSecrets: unwrapped=$unwrapped")
|
||||||
UnreadableRemoteFailure(request.amount, route.fullRoute)
|
UnreadableRemoteFailure(request.amount, route.fullRoute, unwrapped)
|
||||||
}
|
}
|
||||||
log.warning(s"too many failed attempts, failing the payment")
|
log.warning(s"too many failed attempts, failing the payment")
|
||||||
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ failure)))
|
myStop(request, Left(PaymentFailed(id, paymentHash, failures :+ failure)))
|
||||||
case Failure(t) =>
|
case Left(Sphinx.CannotDecryptFailurePacket(unwrapped)) =>
|
||||||
log.warning(s"cannot parse returned error: ${t.getMessage}, route=${route.printNodes()}")
|
log.warning(s"cannot parse returned error: unwrapped=$unwrapped, route=${route.printNodes()}")
|
||||||
val failure = UnreadableRemoteFailure(request.amount, route.fullRoute)
|
val failure = UnreadableRemoteFailure(request.amount, route.fullRoute, unwrapped)
|
||||||
retry(failure, d)
|
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)")
|
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)
|
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||||
retry(failure, d)
|
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)")
|
log.info(s"received 'Update' type error message from nodeId=$nodeId, retrying payment (failure=$failureMessage)")
|
||||||
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||||
if (failureMessage.update_opt.forall(update => Announcements.checkSig(update, nodeId))) {
|
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)
|
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.
|
// 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")
|
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)
|
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))
|
router ! RouteRequest(nodeParams.nodeId, recipient, request.routeParams, ignore1, paymentContext = Some(cfg.paymentContext))
|
||||||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(request, failures :+ failure, ignore1)
|
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)")
|
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)
|
val failure = RemoteFailure(request.amount, route.fullRoute, e)
|
||||||
retry(failure, d)
|
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))
|
router ! ExcludeChannel(ChannelDesc(ann.channelUpdate.shortChannelId, nodeId, hop.nextNodeId), Some(nodeParams.routerConf.channelExcludeDuration))
|
||||||
}
|
}
|
||||||
data.recipient.extraEdges
|
data.recipient.extraEdges
|
||||||
case hint: HopRelayParams.FromHint =>
|
case _: HopRelayParams.FromHint =>
|
||||||
failure.update_opt match {
|
failure.update_opt match {
|
||||||
case Some(update) =>
|
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)
|
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(_ => {
|
ClearRecipient.validateRoute(nodeId, route).map(_ => {
|
||||||
val finalPayload = nextTrampolineOnion_opt match {
|
val finalPayload = nextTrampolineOnion_opt match {
|
||||||
case Some(trampolinePacket) => NodePayload(nodeId, FinalPayload.Standard.createTrampolinePayload(route.amount, totalAmount, expiry, paymentSecret, trampolinePacket))
|
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)
|
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
|
// 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.
|
// 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) ::
|
(("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])) ::
|
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
||||||
("commit" | provide(false)) ::
|
("commit" | provide(false)) ::
|
||||||
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC]
|
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FAIL_HTLC]
|
||||||
|
@ -43,9 +52,27 @@ object CommandCodecs {
|
||||||
("commit" | provide(false)) ::
|
("commit" | provide(false)) ::
|
||||||
("replyTo_opt" | provide(Option.empty[ActorRef]))).as[CMD_FULFILL_HTLC]
|
("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] =
|
private val cmdFailCodec: Codec[CMD_FAIL_HTLC] =
|
||||||
(("id" | int64) ::
|
(("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.
|
// No need to delay commands after a restart, we've been offline which already created a random delay.
|
||||||
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
("delay_opt" | provide(Option.empty[FiniteDuration])) ::
|
||||||
("commit" | provide(false)) ::
|
("commit" | provide(false)) ::
|
||||||
|
@ -60,9 +87,10 @@ object CommandCodecs {
|
||||||
|
|
||||||
val cmdCodec: Codec[HtlcSettlementCommand] = discriminated[HtlcSettlementCommand].by(uint16)
|
val cmdCodec: Codec[HtlcSettlementCommand] = discriminated[HtlcSettlementCommand].by(uint16)
|
||||||
// NB: order matters!
|
// NB: order matters!
|
||||||
.typecase(3, cmdFailCodec)
|
.typecase(4, cmdFailCodec)
|
||||||
|
.typecase(3, cmdFailEitherCodec)
|
||||||
.typecase(2, cmdFailMalformedCodec)
|
.typecase(2, cmdFailMalformedCodec)
|
||||||
.typecase(1, legacyCmdFailCodec)
|
.typecase(1, cmdFailWithoutLengthCodec)
|
||||||
.typecase(0, cmdFulfillCodec)
|
.typecase(0, cmdFulfillCodec)
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,6 +31,17 @@ import scodec.{Attempt, Codec, Err}
|
||||||
* Created by fabrice on 14/03/17.
|
* 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
|
sealed trait FailureMessageTlv extends Tlv
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
@ -157,6 +168,10 @@ object FailureMessageCodecs {
|
||||||
fallback = unknownFailureMessageCodec.upcast[FailureMessage]
|
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(
|
private def failureOnionPayload(payloadAndPadLength: Int): Codec[FailureMessage] = Codec(
|
||||||
encoder = f => variableSizeBytes(uint16, failureMessageCodec).encode(f).flatMap(bits => {
|
encoder = f => variableSizeBytes(uint16, failureMessageCodec).encode(f).flatMap(bits => {
|
||||||
val payloadLength = bits.bytes.length - 2
|
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 paymentSecret = records.get[PaymentData].map(_.secret).orElse(records.get[KeySend].map(_.paymentPreimage)).get
|
||||||
val paymentPreimage = records.get[KeySend].map(_.paymentPreimage)
|
val paymentPreimage = records.get[KeySend].map(_.paymentPreimage)
|
||||||
val paymentMetadata = records.get[PaymentMetadata].map(_.data)
|
val paymentMetadata = records.get[PaymentMetadata].map(_.data)
|
||||||
|
val isTrampoline = records.get[TrampolineOnion].nonEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
object Standard {
|
object Standard {
|
||||||
|
@ -442,12 +443,13 @@ object PaymentOnion {
|
||||||
Right(Standard(records))
|
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(
|
val tlvs: Set[OnionPaymentPayloadTlv] = Set(
|
||||||
Some(AmountToForward(amount)),
|
Some(AmountToForward(amount)),
|
||||||
Some(OutgoingCltv(expiry)),
|
Some(OutgoingCltv(expiry)),
|
||||||
Some(PaymentData(paymentSecret, totalAmount)),
|
Some(PaymentData(paymentSecret, totalAmount)),
|
||||||
paymentMetadata.map(m => PaymentMetadata(m))
|
paymentMetadata.map(m => PaymentMetadata(m)),
|
||||||
|
trampolineOnion_opt.map(o => TrampolineOnion(o)),
|
||||||
).flatten
|
).flatten
|
||||||
Standard(TlvStream(tlvs, customTlvs))
|
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.fsm.Channel
|
||||||
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
|
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
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 fr.acinq.eclair.{MilliSatoshiLong, NodeParams, TestKitBaseClass}
|
||||||
import org.scalatest.funsuite.AnyFunSuiteLike
|
import org.scalatest.funsuite.AnyFunSuiteLike
|
||||||
import scodec.bits.ByteVector
|
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
|
// at this point the pending incoming htlc is waiting for a preimage
|
||||||
assert(lcp4.htlcTxs(remainingHtlcOutpoint) == None)
|
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]]
|
probe.expectMsgType[CommandSuccess[CMD_FAIL_HTLC]]
|
||||||
val aliceClosing1 = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val aliceClosing1 = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
val lcp5 = aliceClosing1.localCommitPublished.get.copy(irrevocablySpent = lcp4.irrevocablySpent, claimHtlcDelayedTxs = lcp4.claimHtlcDelayedTxs)
|
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)
|
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]]
|
probe.expectMsgType[CommandSuccess[CMD_FAIL_HTLC]]
|
||||||
val bobClosing1 = bob.stateData.asInstanceOf[DATA_CLOSING]
|
val bobClosing1 = bob.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
val rcp4 = bobClosing1.remoteCommitPublished.get.copy(irrevocablySpent = rcp3.irrevocablySpent)
|
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.crypto.keymanager.LocalChannelKeyManager
|
||||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
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.funsuite.FixtureAnyFunSuiteLike
|
||||||
import org.scalatest.{Outcome, Tag}
|
import org.scalatest.{Outcome, Tag}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
assert(bc4.availableBalanceForSend == b)
|
assert(bc4.availableBalanceForSend == b)
|
||||||
assert(bc4.availableBalanceForReceive == a - p - htlcOutputFee)
|
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)
|
val Right((bc5, fail: UpdateFailHtlc)) = bc4.sendFail(cmdFail, bob.underlyingActor.nodeParams.privateKey)
|
||||||
assert(bc5.availableBalanceForSend == b)
|
assert(bc5.availableBalanceForSend == b)
|
||||||
assert(bc5.availableBalanceForReceive == a - p - htlcOutputFee) // a's balance won't return to previous before she acknowledges the fail
|
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.availableBalanceForSend == b + p1 - p3) // as soon as we have the fulfill, the balance increases
|
||||||
assert(bc8.availableBalanceForReceive == a - p1 - htlcOutputFee - p2 - htlcOutputFee - htlcOutputFee)
|
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)
|
val Right((bc9, fail2: UpdateFailHtlc)) = bc8.sendFail(cmdFail2, bob.underlyingActor.nodeParams.privateKey)
|
||||||
assert(bc9.availableBalanceForSend == b + p1 - p3)
|
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
|
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 = {
|
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]
|
val fail = s2r.expectMsgType[UpdateFailHtlc]
|
||||||
s2r.forward(r)
|
s2r.forward(r)
|
||||||
eventually(assert(r.stateData.asInstanceOf[ChannelDataWithCommitments].commitments.changes.remoteChanges.proposed.contains(fail)))
|
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 (preimage, add) = addHtlc(50_000_000 msat, bob, alice, bob2alice, alice2bob)
|
||||||
val cmd = c match {
|
val cmd = c match {
|
||||||
case FulfillHtlc => CMD_FULFILL_HTLC(add.id, preimage)
|
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)
|
crossSign(bob, alice, bob2alice, alice2bob)
|
||||||
val sender = initiateQuiescence(f, sendInitialStfu)
|
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.DirectedHtlc.{incoming, outgoing}
|
||||||
import fr.acinq.eclair.transactions.Transactions
|
import fr.acinq.eclair.transactions.Transactions
|
||||||
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.funsuite.FixtureAnyFunSuiteLike
|
||||||
import org.scalatest.{Outcome, Tag}
|
import org.scalatest.{Outcome, Tag}
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
@ -1527,7 +1527,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
import f._
|
import f._
|
||||||
val (_, htlc) = addHtlc(150000000 msat, alice, bob, alice2bob, bob2alice)
|
val (_, htlc) = addHtlc(150000000 msat, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(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]
|
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
bob ! CMD_SIGN()
|
bob ! CMD_SIGN()
|
||||||
|
@ -1811,7 +1811,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
|
|
||||||
// actual test begins
|
// actual test begins
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
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)
|
val Right(fail) = OutgoingPaymentPacket.buildHtlcFailure(Bob.nodeParams.privateKey, cmd, htlc)
|
||||||
assert(fail.id == htlc.id)
|
assert(fail.id == htlc.id)
|
||||||
bob ! cmd
|
bob ! cmd
|
||||||
|
@ -1841,7 +1841,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
crossSign(alice, bob, alice2bob, bob2alice)
|
crossSign(alice, bob, alice2bob, bob2alice)
|
||||||
|
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
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)
|
val Right(fail) = OutgoingPaymentPacket.buildHtlcFailure(Bob.nodeParams.privateKey, cmd, htlc)
|
||||||
assert(fail.id == htlc.id)
|
assert(fail.id == htlc.id)
|
||||||
bob ! cmd
|
bob ! cmd
|
||||||
|
@ -1854,7 +1854,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
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
|
bob ! c
|
||||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||||
assert(initialState == bob.stateData)
|
assert(initialState == bob.stateData)
|
||||||
|
@ -1874,7 +1874,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
bob2alice.expectMsgType[CommitSig]
|
bob2alice.expectMsgType[CommitSig]
|
||||||
|
|
||||||
// We cannot fail the HTLC, we must wait for the fulfill to be acked.
|
// 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
|
bob ! c
|
||||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), htlc.id)))
|
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 sender = TestProbe()
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
|
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.send(bob, c) // this will fail
|
||||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||||
awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty)
|
awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty)
|
||||||
|
@ -1938,7 +1938,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
import f._
|
import f._
|
||||||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(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]
|
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||||
|
|
||||||
// actual test begins
|
// actual test begins
|
||||||
|
@ -2045,7 +2045,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
||||||
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
val (_, htlc) = addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(alice, bob, alice2bob, bob2alice)
|
crossSign(alice, bob, alice2bob, bob2alice)
|
||||||
// Bob receives a failure with a completely invalid onion error (missing mac)
|
// 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]
|
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||||
assert(fail.id == htlc.id)
|
assert(fail.id == htlc.id)
|
||||||
// We propagate failure upstream (hopefully the sender knows how to unwrap them).
|
// 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.
|
// 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.
|
// 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)
|
bob ! CurrentBlockHeight(htlc.cltvExpiry.blockHeight - bob.underlyingActor.nodeParams.channelConf.fulfillSafetyBeforeTimeout.toInt)
|
||||||
|
|
||||||
bob2blockchain.expectNoMessage(250 millis)
|
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.relay.Relayer._
|
||||||
import fr.acinq.eclair.payment.send.SpontaneousRecipient
|
import fr.acinq.eclair.payment.send.SpontaneousRecipient
|
||||||
import fr.acinq.eclair.transactions.Transactions.ClaimLocalAnchorOutputTx
|
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 fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32}
|
||||||
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
|
||||||
import org.scalatest.{Outcome, Tag}
|
import org.scalatest.{Outcome, Tag}
|
||||||
|
@ -243,7 +243,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||||
test("recv CMD_FAIL_HTLC") { f =>
|
test("recv CMD_FAIL_HTLC") { f =>
|
||||||
import f._
|
import f._
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
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]
|
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||||
awaitCond(bob.stateData == initialState
|
awaitCond(bob.stateData == initialState
|
||||||
.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fail)
|
.modify(_.commitments.changes.localChanges.proposed).using(_ :+ fail)
|
||||||
|
@ -254,7 +254,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||||
import f._
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
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
|
bob ! c
|
||||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||||
assert(initialState == bob.stateData)
|
assert(initialState == bob.stateData)
|
||||||
|
@ -264,7 +264,7 @@ class ShutdownStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike wit
|
||||||
import f._
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
|
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.send(bob, c) // this will fail
|
||||||
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
sender.expectMsg(RES_FAILURE(c, UnknownHtlcId(channelId(bob), 42)))
|
||||||
awaitCond(bob.underlyingActor.nodeParams.db.pendingCommands.listSettlementCommands(initialState.channelId).isEmpty)
|
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 =>
|
test("recv RevokeAndAck (forward UpdateFailHtlc)") { f =>
|
||||||
import f._
|
import f._
|
||||||
bob ! CMD_FAIL_HTLC(1, Right(PermanentChannelFailure()))
|
bob ! CMD_FAIL_HTLC(1, FailureReason.LocalFailure(PermanentChannelFailure()))
|
||||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
bob ! CMD_SIGN()
|
bob ! CMD_SIGN()
|
||||||
|
|
|
@ -229,19 +229,19 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
val packet1 = FailurePacket.create(sharedSecrets.head, expected.failureMessage)
|
val packet1 = FailurePacket.create(sharedSecrets.head, expected.failureMessage)
|
||||||
assert(packet1.length == 292)
|
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)
|
assert(decrypted1 == expected)
|
||||||
|
|
||||||
val packet2 = FailurePacket.wrap(packet1, sharedSecrets(1))
|
val packet2 = FailurePacket.wrap(packet1, sharedSecrets(1))
|
||||||
assert(packet2.length == 292)
|
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)
|
assert(decrypted2 == expected)
|
||||||
|
|
||||||
val packet3 = FailurePacket.wrap(packet2, sharedSecrets(2))
|
val packet3 = FailurePacket.wrap(packet2, sharedSecrets(2))
|
||||||
assert(packet3.length == 292)
|
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)
|
assert(decrypted3 == expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
sharedSecrets(1)),
|
sharedSecrets(1)),
|
||||||
sharedSecrets(2))
|
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)") {
|
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")
|
assert(error4 == hex"9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d")
|
||||||
|
|
||||||
// origin parses error packet and can see that it comes from node #4
|
// 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(pubkey == publicKeys(4))
|
||||||
assert(failure == TemporaryNodeFailure())
|
assert(failure == TemporaryNodeFailure())
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
assert(error4 == hex"2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4")
|
assert(error4 == hex"2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c03b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d783c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04ca86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf979738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c780486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221ea796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4")
|
||||||
|
|
||||||
// origin parses error packet and can see that it comes from node #4
|
// 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(pubkey == publicKeys(4))
|
||||||
assert(parsedFailure == failure)
|
assert(parsedFailure == failure)
|
||||||
}
|
}
|
||||||
|
@ -346,7 +346,7 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
assert(error4 == hex"751c187d145e5498306824f193c6bf9ed4a974fa85b3cc5d32d549ce494c1e7b3a06a19f8a9145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12dc942b5cf1db059d3e73d63967e464b5d5cfd4052de195387de93535e88a2e618e15a7c521d67ce2cc836c49118f205c99f18570504504221e337a29e2716fb28671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4")
|
assert(error4 == hex"751c187d145e5498306824f193c6bf9ed4a974fa85b3cc5d32d549ce494c1e7b3a06a19f8a9145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12dc942b5cf1db059d3e73d63967e464b5d5cfd4052de195387de93535e88a2e618e15a7c521d67ce2cc836c49118f205c99f18570504504221e337a29e2716fb28671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa02afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4ed3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae28227b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1beab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bdd4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26add1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b55c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73af0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b97978ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d16fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b3e546df4")
|
||||||
|
|
||||||
// origin parses error packet and can see that it comes from node #4
|
// 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(pubkey == publicKeys(4))
|
||||||
assert(failure == TemporaryNodeFailure())
|
assert(failure == TemporaryNodeFailure())
|
||||||
}
|
}
|
||||||
|
@ -366,7 +366,7 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
val error2 = FailurePacket.wrap(error1, sharedSecret0)
|
val error2 = FailurePacket.wrap(error1, sharedSecret0)
|
||||||
|
|
||||||
// origin parses error packet and can see that it comes from node #2
|
// 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(pubkey == publicKeys(2))
|
||||||
assert(failure == InvalidRealm())
|
assert(failure == InvalidRealm())
|
||||||
}
|
}
|
||||||
|
@ -387,7 +387,7 @@ class SphinxSpec extends AnyFunSuite {
|
||||||
assert(error2 == hex"c843486107187673b4586f5cdaad43ad84fbac03b39df51bbf9169b2bd682b409a855b2feb0545705f12eba9dbaecee84e328a9c2e4c3086bb1d0909d1f2e4f8a0e9c6be9541e94a849a0887756b984031dcb74d11c20d437a55daf3ee4109dea68ad74f9b742e7571d5e4d1b2ea4f7094787cf361b448a22a547ea85b833aae20f3ba79fb41c6636414c2092d41dd5328e2c1a1c754cb1f0d297628219f91fe946169f593ce7fce79103945d4d24adce46c083ab24757870356af55fcd3d22b9cfd83c45d409eb3081b218448d5dca3a201cf89ac88c9b66049d7c262b32081d3aba2098ea853bfa173ec23aa9253e083dfa881ef487b76780435c1b9f8a1d794557f0ac91d261d280bfb8513ad0c4dab0d7152eb9ee36ae63b8d384613684326d8735dc559f31cecb21b1d55bbcf7a281127adbedd0210b243325fd291cb82d443beec8f4b96aaee4b1a619724d7456b756d391e8fd3256d2b0766e39a435eb4d6d144c7fca1c73105710266e31120565444dfd6e9099e44d73a0f28419809577a267bbbc6671f723669d00c35c8e60fad88d89d4a7477a0c30f9839485197ed76338330f2ca00cf0e31c59da4eeebef977f429ad2c61acac35939866dac5b1df1c3c487ebaf961340c0c1dbc4bedebde7ee0633c3f480b7df265a3d90e78a4bcb9497f4228169fadb647e77afe6f43aa129286bb21767f6e75ac5c092473f99f2cf8b4e191f300c70b210e077a0385d483971bc0c66f5c119c0731a8753793ad12703d9cc5153eb1c8f25b71ee88a8d1d4433aa8f8277366c82111dbebfe0f548411588d54c3606742330d3d84a2f107df98d60995297de11672f6300b11444a04e252d69d8187772798afc6a9cd8b245a5ebd51bf0659f18c57daf1d1f724d2f15d524ab6902fb17a8fa6cee8e01df67735eac34bb0efc183dcb8d2a7cb401bd786c32a17f14c9d9ffc02b4f58c4ebab898a78b4913647d4cb5bafe6f7f27b5a256d1635c10f0ca71796610068c090c270c20bb18ec9d205e640d7655bdf5c9aeae20d7f9426eade0733c19d0aa577caf31f9d5be0a99ed0c509e84ccb555389ca69f09c3e66694a4ea2785f8d839d7dfff08b2c21aff89a023161cb1ebdd1e7a46d6380c0ddbc88eb3526e624fadcd222ecaa09566c2678158f933f03623299fec134a880d39a9d82ba2b29211e7787b3f32d478df856389a02cb68b66fc0dfc0b52353e7360f31e5457a6a9dd34512e912afeb5a92f3cbd3883b62c37e3ba5e4e8b688033150103c810740d130a5597c8a4a16311f50cfb3a919aac1e0a1096f20a14a536c55068ad38f40e62fc6f178b2fee67ca2cbd8afa29ef6c89b217aee02419ca26d59b604521a55e37c0a5a693fbc3ebcba23cd62479ddf62e5521847a2b4ac5e7686ef662c29cf8a8983660530942ee9a6c53b55e08af0b43467989693cefe6267fd524435152c01c9b93aebdec6146366a94162f99ac4c7157c15b988")
|
assert(error2 == hex"c843486107187673b4586f5cdaad43ad84fbac03b39df51bbf9169b2bd682b409a855b2feb0545705f12eba9dbaecee84e328a9c2e4c3086bb1d0909d1f2e4f8a0e9c6be9541e94a849a0887756b984031dcb74d11c20d437a55daf3ee4109dea68ad74f9b742e7571d5e4d1b2ea4f7094787cf361b448a22a547ea85b833aae20f3ba79fb41c6636414c2092d41dd5328e2c1a1c754cb1f0d297628219f91fe946169f593ce7fce79103945d4d24adce46c083ab24757870356af55fcd3d22b9cfd83c45d409eb3081b218448d5dca3a201cf89ac88c9b66049d7c262b32081d3aba2098ea853bfa173ec23aa9253e083dfa881ef487b76780435c1b9f8a1d794557f0ac91d261d280bfb8513ad0c4dab0d7152eb9ee36ae63b8d384613684326d8735dc559f31cecb21b1d55bbcf7a281127adbedd0210b243325fd291cb82d443beec8f4b96aaee4b1a619724d7456b756d391e8fd3256d2b0766e39a435eb4d6d144c7fca1c73105710266e31120565444dfd6e9099e44d73a0f28419809577a267bbbc6671f723669d00c35c8e60fad88d89d4a7477a0c30f9839485197ed76338330f2ca00cf0e31c59da4eeebef977f429ad2c61acac35939866dac5b1df1c3c487ebaf961340c0c1dbc4bedebde7ee0633c3f480b7df265a3d90e78a4bcb9497f4228169fadb647e77afe6f43aa129286bb21767f6e75ac5c092473f99f2cf8b4e191f300c70b210e077a0385d483971bc0c66f5c119c0731a8753793ad12703d9cc5153eb1c8f25b71ee88a8d1d4433aa8f8277366c82111dbebfe0f548411588d54c3606742330d3d84a2f107df98d60995297de11672f6300b11444a04e252d69d8187772798afc6a9cd8b245a5ebd51bf0659f18c57daf1d1f724d2f15d524ab6902fb17a8fa6cee8e01df67735eac34bb0efc183dcb8d2a7cb401bd786c32a17f14c9d9ffc02b4f58c4ebab898a78b4913647d4cb5bafe6f7f27b5a256d1635c10f0ca71796610068c090c270c20bb18ec9d205e640d7655bdf5c9aeae20d7f9426eade0733c19d0aa577caf31f9d5be0a99ed0c509e84ccb555389ca69f09c3e66694a4ea2785f8d839d7dfff08b2c21aff89a023161cb1ebdd1e7a46d6380c0ddbc88eb3526e624fadcd222ecaa09566c2678158f933f03623299fec134a880d39a9d82ba2b29211e7787b3f32d478df856389a02cb68b66fc0dfc0b52353e7360f31e5457a6a9dd34512e912afeb5a92f3cbd3883b62c37e3ba5e4e8b688033150103c810740d130a5597c8a4a16311f50cfb3a919aac1e0a1096f20a14a536c55068ad38f40e62fc6f178b2fee67ca2cbd8afa29ef6c89b217aee02419ca26d59b604521a55e37c0a5a693fbc3ebcba23cd62479ddf62e5521847a2b4ac5e7686ef662c29cf8a8983660530942ee9a6c53b55e08af0b43467989693cefe6267fd524435152c01c9b93aebdec6146366a94162f99ac4c7157c15b988")
|
||||||
|
|
||||||
// origin parses error packet and can see that it comes from node #2
|
// 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(pubkey == publicKeys(2))
|
||||||
assert(failure == InvalidRealm())
|
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.db.sqlite.SqliteUtils.{setVersion, using}
|
||||||
import fr.acinq.eclair.randomBytes32
|
import fr.acinq.eclair.randomBytes32
|
||||||
import fr.acinq.eclair.wire.internal.CommandCodecs.cmdCodec
|
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 org.scalatest.funsuite.AnyFunSuite
|
||||||
|
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
@ -53,8 +53,8 @@ class PendingCommandsDbSpec extends AnyFunSuite {
|
||||||
val channelId2 = randomBytes32()
|
val channelId2 = randomBytes32()
|
||||||
val msg0 = CMD_FULFILL_HTLC(0, randomBytes32())
|
val msg0 = CMD_FULFILL_HTLC(0, randomBytes32())
|
||||||
val msg1 = CMD_FULFILL_HTLC(1, randomBytes32())
|
val msg1 = CMD_FULFILL_HTLC(1, randomBytes32())
|
||||||
val msg2 = CMD_FAIL_HTLC(2, Left(randomBytes32()))
|
val msg2 = CMD_FAIL_HTLC(2, FailureReason.EncryptedDownstreamFailure(randomBytes32()))
|
||||||
val msg3 = CMD_FAIL_HTLC(3, Left(randomBytes32()))
|
val msg3 = CMD_FAIL_HTLC(3, FailureReason.EncryptedDownstreamFailure(randomBytes32()))
|
||||||
val msg4 = CMD_FAIL_MALFORMED_HTLC(4, randomBytes32(), FailureMessageCodecs.BADONION)
|
val msg4 = CMD_FAIL_MALFORMED_HTLC(4, randomBytes32(), FailureMessageCodecs.BADONION)
|
||||||
|
|
||||||
assert(db.listSettlementCommands(channelId1).toSet == Set.empty)
|
assert(db.listSettlementCommands(channelId1).toSet == Set.empty)
|
||||||
|
@ -135,7 +135,7 @@ object PendingCommandsDbSpec {
|
||||||
val cmds = (0 until Random.nextInt(5)).map { _ =>
|
val cmds = (0 until Random.nextInt(5)).map { _ =>
|
||||||
Random.nextInt(2) match {
|
Random.nextInt(2) match {
|
||||||
case 0 => CMD_FULFILL_HTLC(Random.nextLong(100_000), randomBytes32())
|
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))
|
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)
|
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)))
|
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
|
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)
|
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).get.status == IncomingPaymentStatus.Pending)
|
||||||
|
|
||||||
eventListener.expectNoMessage(100 milliseconds)
|
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)
|
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)))
|
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
|
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)
|
val Some(incoming) = nodeParams.db.payments.getIncomingPayment(invoice.paymentHash)
|
||||||
assert(incoming.invoice.isExpired() && incoming.status == IncomingPaymentStatus.Expired)
|
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)
|
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)))
|
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
|
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)
|
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)
|
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)))
|
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
|
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)
|
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)
|
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)))
|
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
|
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)
|
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)
|
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)))
|
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
|
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)
|
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)
|
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)))
|
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
|
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)
|
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)
|
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)))
|
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
|
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)
|
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)
|
assert(receivePayment.paymentHash == invoice.paymentHash)
|
||||||
receivePayment.replyTo ! GetIncomingPaymentActor.RejectPayment("non blinded payment")
|
receivePayment.replyTo ! GetIncomingPaymentActor.RejectPayment("non blinded payment")
|
||||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
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)
|
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)
|
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)))
|
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
|
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)
|
assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +537,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||||
assert(payment.payload.pathId == pathId)
|
assert(payment.payload.pathId == pathId)
|
||||||
payment.replyTo ! GetIncomingPaymentActor.RejectPayment("internal error")
|
payment.replyTo ! GetIncomingPaymentActor.RejectPayment("internal error")
|
||||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
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 =>
|
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)
|
val payment = IncomingBlindedPayment(MinimalBolt12Invoice(invoice.records), preimage, PaymentType.Blinded, TimestampMilli.now(), IncomingPaymentStatus.Pending)
|
||||||
receivePayment.replyTo ! GetIncomingPaymentActor.ProcessPayment(payment)
|
receivePayment.replyTo ! GetIncomingPaymentActor.ProcessPayment(payment)
|
||||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
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)
|
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
|
val commands = f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: f.register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]] :: Nil
|
||||||
assert(commands.toSet == Set(
|
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(0, FailureReason.LocalFailure(PaymentTimeout()), commit = true)),
|
||||||
Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(1, Right(PaymentTimeout()), commit = true))
|
Register.Forward(null, ByteVector32.One, CMD_FAIL_HTLC(1, FailureReason.LocalFailure(PaymentTimeout()), commit = true))
|
||||||
))
|
))
|
||||||
awaitCond({
|
awaitCond({
|
||||||
f.sender.send(handler, GetPendingPayments)
|
f.sender.send(handler, GetPendingPayments)
|
||||||
|
@ -597,7 +597,7 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
|
||||||
|
|
||||||
// Extraneous HTLCs should be failed.
|
// 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.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.
|
// The payment should still be pending in DB.
|
||||||
val Some(incomingPayment) = nodeParams.db.payments.getIncomingPayment(pr1.paymentHash)
|
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.sender.send(handler, IncomingPaymentPacket.FinalPacket(add3, FinalPayload.Standard.createPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, invoice.paymentSecret, invoice.paymentMetadata)))
|
||||||
|
|
||||||
f.register.expectMsgAllOf(
|
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, add1.channelId, CMD_FULFILL_HTLC(add1.id, preimage, commit = true)),
|
||||||
Register.Forward(null, add3.channelId, CMD_FULFILL_HTLC(add3.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)
|
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.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({
|
awaitCond({
|
||||||
f.sender.send(handler, GetPendingPayments)
|
f.sender.send(handler, GetPendingPayments)
|
||||||
f.sender.expectMsgType[PendingPayments].paymentHashes.isEmpty
|
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)
|
val add = UpdateAddHtlc(ByteVector32.One, 0, amountMsat, paymentHash, defaultExpiry, TestConstants.emptyOnionPacket, None, 1.0, None)
|
||||||
sender.send(handlerWithMpp, IncomingPaymentPacket.FinalPacket(add, payload))
|
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)
|
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)))
|
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
|
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(cmd.id == add.id)
|
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 =>
|
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"))))
|
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
|
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(cmd.id == add.id)
|
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 =>
|
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)
|
sender.send(handlerWithoutMpp, fulfill)
|
||||||
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
val cmd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(cmd.id == add.id)
|
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(
|
val failures = Seq(
|
||||||
LocalFailure(finalAmount, Nil, ChannelUnavailable(randomBytes32())),
|
LocalFailure(finalAmount, Nil, ChannelUnavailable(randomBytes32())),
|
||||||
RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48)))))),
|
RemoteFailure(finalAmount, Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, Some(makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48)))))),
|
||||||
UnreadableRemoteFailure(finalAmount, Nil)
|
UnreadableRemoteFailure(finalAmount, Nil, randomBytes(292))
|
||||||
)
|
)
|
||||||
val extraEdges1 = Seq(
|
val extraEdges1 = Seq(
|
||||||
ExtraEdge(a, b, ShortChannelId(1), 10 msat, 0, CltvExpiryDelta(12), 1 msat, None),
|
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]
|
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||||
|
|
||||||
val (failedId1, failedRoute1) = payFsm.stateData.asInstanceOf[PaymentProgress].pending.head
|
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.expectMsgType[RouteRequest]
|
||||||
router.send(payFsm, RouteResponse(Seq(Route(500_000 msat, hop_ad :: hop_de :: Nil, None))))
|
router.send(payFsm, RouteResponse(Seq(Route(500_000 msat, hop_ad :: hop_de :: Nil, None))))
|
||||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||||
|
|
||||||
assert(!payFsm.stateData.asInstanceOf[PaymentProgress].pending.contains(failedId1))
|
assert(!payFsm.stateData.asInstanceOf[PaymentProgress].pending.contains(failedId1))
|
||||||
val (failedId2, failedRoute2) = payFsm.stateData.asInstanceOf[PaymentProgress].pending.head
|
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.length >= 3)
|
||||||
assert(result.failures.contains(LocalFailure(finalAmount, Nil, RetryExhausted)))
|
assert(result.failures.contains(LocalFailure(finalAmount, Nil, RetryExhausted)))
|
||||||
|
|
||||||
|
@ -517,7 +517,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||||
|
|
||||||
val (failedId1, failedRoute1) :: (failedId2, failedRoute2) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq
|
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]
|
router.expectMsgType[RouteRequest]
|
||||||
|
|
||||||
val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout())))))
|
val result = abortAfterFailure(f, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.amount, failedRoute2.hops, Sphinx.DecryptedFailurePacket(e, PaymentTimeout())))))
|
||||||
|
@ -535,7 +535,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||||
|
|
||||||
val (failedId, failedRoute) :: (successId, successRoute) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq
|
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]
|
router.expectMsgType[RouteRequest]
|
||||||
|
|
||||||
val result = fulfillPendingPayments(f, 1, e, finalAmount)
|
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.router._
|
||||||
import fr.acinq.eclair.transactions.Scripts
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.wire.protocol._
|
import fr.acinq.eclair.wire.protocol._
|
||||||
|
import org.scalatest.Inside.inside
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -338,7 +339,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
assert(ignore1.nodes.isEmpty)
|
assert(ignore1.nodes.isEmpty)
|
||||||
|
|
||||||
register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd1))
|
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
|
// 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)))
|
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))
|
assert(ignore2.nodes == Set(c))
|
||||||
// and reply a 2nd time with an unparsable failure
|
// and reply a 2nd time with an unparsable failure
|
||||||
register.expectMsg(ForwardShortId(paymentFSM.toTyped, scid_ab, cmd2))
|
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
|
// 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
|
awaitCond(nodeParams.db.payments.getOutgoingPayment(id).exists(_.status.isInstanceOf[OutgoingPaymentStatus.Failed])) // after last attempt the payment is failed
|
||||||
|
|
||||||
val metrics = metricsListener.expectMsgType[PathFindingExperimentMetrics]
|
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(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))),
|
(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
|
// 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) :: 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), Set(c), 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), Set(c, d), 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), Set(c), 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), 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) {
|
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
|
||||||
import fr.acinq.eclair.transactions.Transactions.InputInfo
|
import fr.acinq.eclair.transactions.Transactions.InputInfo
|
||||||
import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo}
|
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.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload}
|
||||||
import fr.acinq.eclair.wire.protocol._
|
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}
|
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 scodec.bits.{ByteVector, HexStringSyntax}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 31/05/2016.
|
* 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 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)
|
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||||
assert(add_e2 == add_e)
|
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") {
|
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 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)
|
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||||
assert(add_e2 == add_e)
|
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") {
|
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 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)
|
val Right(FinalPacket(add_e2, payload_e)) = decrypt(add_e, priv_e.privateKey, Features.empty)
|
||||||
assert(add_e2 == add_e)
|
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") {
|
test("fail to build outgoing payment with invalid route") {
|
||||||
|
@ -649,15 +662,15 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
||||||
|
|
||||||
// e returns a failure
|
// e returns a failure
|
||||||
val failure = IncorrectOrUnknownPaymentDetails(finalAmount, BlockHeight(currentBlockCount))
|
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)
|
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)
|
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)
|
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)
|
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(failingNode == e)
|
||||||
assert(decryptedFailure == failure)
|
assert(decryptedFailure == failure)
|
||||||
}
|
}
|
||||||
|
@ -679,21 +692,21 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll {
|
||||||
assert(payload_e.isInstanceOf[FinalPayload.Blinded])
|
assert(payload_e.isInstanceOf[FinalPayload.Blinded])
|
||||||
|
|
||||||
// nodes after the introduction node cannot send `update_fail_htlc` messages
|
// 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.id == add_e.id)
|
||||||
assert(fail_e.onionHash == Sphinx.hash(add_e.onionRoutingPacket))
|
assert(fail_e.onionHash == Sphinx.hash(add_e.onionRoutingPacket))
|
||||||
assert(fail_e.failureCode == InvalidOnionBlinding(fail_e.onionHash).code)
|
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.id == add_d.id)
|
||||||
assert(fail_d.onionHash == Sphinx.hash(add_d.onionRoutingPacket))
|
assert(fail_d.onionHash == Sphinx.hash(add_d.onionRoutingPacket))
|
||||||
assert(fail_d.failureCode == InvalidOnionBlinding(fail_d.onionHash).code)
|
assert(fail_d.failureCode == InvalidOnionBlinding(fail_d.onionHash).code)
|
||||||
// only the introduction node is allowed to send an `update_fail_htlc` message
|
// only the introduction node is allowed to send an `update_fail_htlc` message
|
||||||
val failure = InvalidOnionBlinding(Sphinx.hash(add_c.onionRoutingPacket))
|
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)
|
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)
|
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(failingNode == c)
|
||||||
assert(decryptedFailure == failure)
|
assert(decryptedFailure == failure)
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
// channel 1 goes to NORMAL state:
|
// 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)))
|
system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments)))
|
||||||
channel.expectMsgAllOf(
|
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)
|
CMD_FAIL_MALFORMED_HTLC(4, ByteVector32.Zeroes, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 24, commit = true)
|
||||||
)
|
)
|
||||||
channel.expectNoMessage(100 millis)
|
channel.expectNoMessage(100 millis)
|
||||||
|
@ -130,15 +130,15 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
// channel 2 goes to NORMAL state:
|
// 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)))
|
system.eventStream.publish(ChannelStateChanged(channel.ref, channels(1).commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels(1).commitments)))
|
||||||
channel.expectMsgAllOf(
|
channel.expectMsgAllOf(
|
||||||
CMD_FAIL_HTLC(0, Right(TemporaryNodeFailure()), commit = true),
|
CMD_FAIL_HTLC(0, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||||
CMD_FAIL_HTLC(4, Right(TemporaryNodeFailure()), commit = true)
|
CMD_FAIL_HTLC(4, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true)
|
||||||
)
|
)
|
||||||
channel.expectNoMessage(100 millis)
|
channel.expectNoMessage(100 millis)
|
||||||
|
|
||||||
// let's assume that channel 1 was disconnected before having signed the fails, and gets connected again:
|
// 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)))
|
system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments)))
|
||||||
channel.expectMsgAllOf(
|
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)
|
CMD_FAIL_MALFORMED_HTLC(4, ByteVector32.Zeroes, FailureMessageCodecs.BADONION | FailureMessageCodecs.PERM | 24, commit = true)
|
||||||
)
|
)
|
||||||
channel.expectNoMessage(100 millis)
|
channel.expectNoMessage(100 millis)
|
||||||
|
@ -225,10 +225,10 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
// channel 1 goes to NORMAL state:
|
// channel 1 goes to NORMAL state:
|
||||||
system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments)))
|
system.eventStream.publish(ChannelStateChanged(channel.ref, channels.head.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels.head.commitments)))
|
||||||
val expected1 = Set(
|
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(3, preimage, commit = true),
|
||||||
CMD_FULFILL_HTLC(5, 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])
|
val received1 = expected1.map(_ => channel.expectMsgType[Command])
|
||||||
assert(received1 == expected1)
|
assert(received1 == expected1)
|
||||||
|
@ -237,10 +237,10 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
// channel 2 goes to NORMAL state:
|
// channel 2 goes to NORMAL state:
|
||||||
system.eventStream.publish(ChannelStateChanged(channel.ref, channels(1).channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels(1).commitments)))
|
system.eventStream.publish(ChannelStateChanged(channel.ref, channels(1).channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(channels(1).commitments)))
|
||||||
val expected2 = Set(
|
val expected2 = Set(
|
||||||
CMD_FAIL_HTLC(1, Right(TemporaryNodeFailure()), commit = true),
|
CMD_FAIL_HTLC(1, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||||
CMD_FAIL_HTLC(3, Right(TemporaryNodeFailure()), commit = true),
|
CMD_FAIL_HTLC(3, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true),
|
||||||
CMD_FULFILL_HTLC(4, preimage, 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])
|
val received2 = expected2.map(_ => channel.expectMsgType[Command])
|
||||||
assert(received2 == expected2)
|
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)))
|
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.
|
// Payment 1 should fail instantly.
|
||||||
channel_upstream_1.expectMsg(CMD_FAIL_HTLC(0, 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, Right(TemporaryNodeFailure()), commit = true))
|
channel_upstream_2.expectMsg(CMD_FAIL_HTLC(7, FailureReason.LocalFailure(TemporaryNodeFailure()), commit = true))
|
||||||
channel_upstream_1.expectNoMessage(100 millis)
|
channel_upstream_1.expectNoMessage(100 millis)
|
||||||
channel_upstream_2.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)
|
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_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)
|
val (_, postRestart) = f.createRelayer(nodeParams)
|
||||||
postRestart ! PostRestartHtlcCleaner.Init(List(channelData))
|
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))
|
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
|
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 {
|
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)
|
}.toSet)
|
||||||
|
|
||||||
sender.send(relayer, buildForwardFail(testCase.downstream_1_1, testCase.upstream_1))
|
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))
|
sender.send(relayer, buildForwardFail(testCase.downstream_2_3, testCase.upstream_2))
|
||||||
register.expectMsg(testCase.upstream_2.originHtlcs.map {
|
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)
|
}.head)
|
||||||
|
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -736,7 +736,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
|
|
||||||
// Standard channel goes to NORMAL state:
|
// Standard channel goes to NORMAL state:
|
||||||
system.eventStream.publish(ChannelStateChanged(channel.ref, c.commitments.channelId, system.deadLetters, a, OFFLINE, NORMAL, Some(c.commitments)))
|
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)
|
channel.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,8 +754,8 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit
|
||||||
}
|
}
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
val cmd1 = CMD_FAIL_HTLC(id = 0L, 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 = Left(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))
|
val nodeParams1 = nodeParams.copy(pluginParams = List(pluginParams))
|
||||||
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd1)
|
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd1)
|
||||||
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd2)
|
nodeParams1.db.pendingCommands.addSettlementCommand(channelId_ab_1, cmd2)
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||||
if (success) {
|
if (success) {
|
||||||
expectFwdAdd(register, lcu.channelId, outgoingAmount, outgoingExpiry, 7)
|
expectFwdAdd(register, lcu.channelId, outgoingAmount, outgoingExpiry, 7)
|
||||||
} else {
|
} 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))
|
fwd1.message.replyTo ! RES_ADD_FAILED(fwd2.message, HtlcValueTooHighInFlight(channelIds(realScid1), 1000000000 msat, 1516977616 msat), Some(u1.channelUpdate))
|
||||||
|
|
||||||
// the relayer should give up
|
// 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 =>
|
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)
|
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 =>
|
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)
|
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
|
||||||
fwd.replyTo ! Register.ForwardFailure(fwd)
|
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 =>
|
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 ! WrappedLocalChannelDown(d)
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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 =>
|
test("fail to relay when channel is disabled") { f =>
|
||||||
|
@ -403,7 +403,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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 =>
|
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 ! WrappedLocalChannelUpdate(u)
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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 =>
|
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])
|
assert(cmd.message.isInstanceOf[CMD_FAIL_HTLC])
|
||||||
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
||||||
assert(fail.id == r.add.id)
|
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)
|
assert(fail.delay_opt.nonEmpty)
|
||||||
} else {
|
} else {
|
||||||
assert(cmd.message.isInstanceOf[CMD_FAIL_MALFORMED_HTLC])
|
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)
|
peerReadyManager.expectMessageType[PeerReadyManager.Register].replyTo ! PeerReadyManager.Registered(outgoingNodeId, otherAttempts = 0)
|
||||||
assert(switchboard.expectMessageType[Switchboard.GetPeerInfo].remoteNodeId == outgoingNodeId)
|
assert(switchboard.expectMessageType[Switchboard.GetPeerInfo].remoteNodeId == outgoingNodeId)
|
||||||
val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
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)
|
cleanUpWakeUpActors(peerReadyManager, switchboard)
|
||||||
}
|
}
|
||||||
|
@ -492,7 +492,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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 =>
|
test("fail to relay when fee is insufficient") { f =>
|
||||||
|
@ -505,7 +505,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||||
channelRelayer ! WrappedLocalChannelUpdate(u)
|
channelRelayer ! WrappedLocalChannelUpdate(u)
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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 =>
|
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)
|
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||||
|
|
||||||
// relay fails because the current update (u3) with higher fees occurred more than 10 minutes ago
|
// 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 =>
|
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)
|
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
||||||
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
|
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
|
||||||
fwd.message.replyTo ! RES_ADD_FAILED(fwd.message, testCase.exc, Some(testCase.update))
|
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
|
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))
|
cmd4.replyTo ! RES_ADD_FAILED(cmd4, HtlcValueTooHighInFlight(randomBytes32(), 100000000 msat, 100000000 msat), Some(channelUpdates(ShortChannelId(11111)).channelUpdate))
|
||||||
// all the suitable channels have been tried
|
// 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)
|
// 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 payload = ChannelRelay.Standard(ShortChannelId(12345), 998900 msat, CltvExpiry(61))
|
||||||
val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70))
|
val r = createValidIncomingPacket(payload, 1000000 msat, CltvExpiry(70))
|
||||||
channelRelayer ! Relay(r, TestConstants.Alice.nodeParams.nodeId)
|
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)
|
case class TestCase(result: HtlcResult, cmd: channel.HtlcSettlementCommand)
|
||||||
|
|
||||||
val testCases = Seq(
|
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.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, Right(InvalidOnionHmac(ByteVector32.One)), 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, Right(PermanentChannelFailure()), 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, Right(TemporaryChannelFailure(Some(u_disabled.channelUpdate))), 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, Right(PermanentChannelFailure()), commit = true))
|
TestCase(HtlcResult.ChannelFailureBeforeSigned, CMD_FAIL_HTLC(r.add.id, FailureReason.LocalFailure(PermanentChannelFailure()), commit = true))
|
||||||
)
|
)
|
||||||
|
|
||||||
testCases.foreach { testCase =>
|
testCases.foreach { testCase =>
|
||||||
|
@ -707,7 +707,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
|
||||||
assert(cmd.message.isInstanceOf[CMD_FAIL_HTLC])
|
assert(cmd.message.isInstanceOf[CMD_FAIL_HTLC])
|
||||||
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
val fail = cmd.message.asInstanceOf[CMD_FAIL_HTLC]
|
||||||
assert(fail.id == r.add.id)
|
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)
|
assert(fail.delay_opt.nonEmpty)
|
||||||
} else {
|
} else {
|
||||||
assert(cmd.message.isInstanceOf[CMD_FAIL_MALFORMED_HTLC])
|
assert(cmd.message.isInstanceOf[CMD_FAIL_MALFORMED_HTLC])
|
||||||
|
|
|
@ -229,7 +229,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incomingMultiPart.dropRight(1).foreach { p =>
|
incomingMultiPart.dropRight(1).foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]](30 seconds)
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]](30 seconds)
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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))
|
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
|
// the extra payment will be rejected
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == extra.add.channelId)
|
assert(fwd.channelId == extra.add.channelId)
|
||||||
val failure = IncorrectOrUnknownPaymentDetails(extra.add.amountMsat, nodeParams.currentBlockHeight)
|
val failure = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(extra.add.amountMsat, nodeParams.currentBlockHeight))
|
||||||
assert(fwd.message == CMD_FAIL_HTLC(extra.add.id, Right(failure), commit = true))
|
assert(fwd.message == CMD_FAIL_HTLC(extra.add.id, failure, commit = true))
|
||||||
|
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -282,8 +282,8 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd1 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd1 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd1.channelId == i1.add.channelId)
|
assert(fwd1.channelId == i1.add.channelId)
|
||||||
val failure1 = IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight)
|
val failure1 = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1000 msat, nodeParams.currentBlockHeight))
|
||||||
assert(fwd1.message == CMD_FAIL_HTLC(i1.add.id, Right(failure1), commit = true))
|
assert(fwd1.message == CMD_FAIL_HTLC(i1.add.id, failure1, commit = true))
|
||||||
|
|
||||||
// Receive new HTLC with different details, but for the same payment hash.
|
// Receive new HTLC with different details, but for the same payment hash.
|
||||||
val i2 = IncomingPaymentPacket.RelayToTrampolinePacket(
|
val i2 = IncomingPaymentPacket.RelayToTrampolinePacket(
|
||||||
|
@ -295,8 +295,8 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd2 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd2 = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd1.channelId == i1.add.channelId)
|
assert(fwd1.channelId == i1.add.channelId)
|
||||||
val failure2 = IncorrectOrUnknownPaymentDetails(1500 msat, nodeParams.currentBlockHeight)
|
val failure2 = FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(1500 msat, nodeParams.currentBlockHeight))
|
||||||
assert(fwd2.message == CMD_FAIL_HTLC(i2.add.id, Right(failure2), commit = true))
|
assert(fwd2.message == CMD_FAIL_HTLC(i2.add.id, failure2, commit = true))
|
||||||
|
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -312,7 +312,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -328,7 +328,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
p.foreach { p =>
|
p.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -398,7 +398,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -416,7 +416,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
p.foreach { p =>
|
p.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -431,7 +431,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
|
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
}
|
}
|
||||||
|
@ -449,7 +449,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
p.foreach { p =>
|
p.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -471,7 +471,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incomingMultiPart.foreach { p =>
|
incomingMultiPart.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -496,7 +496,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incoming.foreach { p =>
|
incoming.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -519,7 +519,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incomingMultiPart.foreach { p =>
|
incomingMultiPart.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -535,13 +535,13 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
val payFSM = mockPayFSM.expectMessageType[akka.actor.ActorRef]
|
val payFSM = mockPayFSM.expectMessageType[akka.actor.ActorRef]
|
||||||
router.expectMessageType[RouteRequest]
|
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)
|
payFSM ! PaymentFailed(relayId, incomingMultiPart.head.add.paymentHash, failures)
|
||||||
|
|
||||||
incomingMultiPart.foreach { p =>
|
incomingMultiPart.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -702,7 +702,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incomingMultiPart.foreach { p =>
|
incomingMultiPart.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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]
|
parent.expectMessageType[NodeRelayer.RelayComplete]
|
||||||
}
|
}
|
||||||
|
@ -923,7 +923,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl
|
||||||
incomingPayments.foreach { p =>
|
incomingPayments.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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 =>
|
incomingPayments.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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 =>
|
incomingPayments.foreach { p =>
|
||||||
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == p.add.channelId)
|
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]]
|
val fwd1 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd1.channelId == upstream1.add.channelId)
|
assert(fwd1.channelId == upstream1.add.channelId)
|
||||||
assert(fwd1.message.id == upstream1.add.id)
|
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)
|
register.expectNoMessage(100 millis)
|
||||||
|
|
||||||
val fail2 = WillFailHtlc(willAdd2.id, paymentHash, randomBytes(50))
|
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]]
|
val fwd2 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd2.channelId == upstream2.add.channelId)
|
assert(fwd2.channelId == upstream2.add.channelId)
|
||||||
assert(fwd2.message.id == upstream2.add.id)
|
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)
|
val fail3 = WillFailMalformedHtlc(willAdd3.id, paymentHash, randomBytes32(), InvalidOnionHmac(randomBytes32()).code)
|
||||||
peerConnection.send(peer, fail3)
|
peerConnection.send(peer, fail3)
|
||||||
val fwd3 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd3 = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd3.channelId == upstream3.add.channelId)
|
assert(fwd3.channelId == upstream3.add.channelId)
|
||||||
assert(fwd3.message.id == upstream3.add.id)
|
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))
|
val fail4 = WillFailHtlc(willAdd4.id, paymentHash, randomBytes(75))
|
||||||
peerConnection.send(peer, fail4)
|
peerConnection.send(peer, fail4)
|
||||||
|
@ -253,7 +253,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
||||||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == add.channelId)
|
assert(fwd.channelId == add.channelId)
|
||||||
assert(fwd.message.id == add.id)
|
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))
|
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]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == add.channelId)
|
assert(fwd.channelId == add.channelId)
|
||||||
assert(fwd.message.id == add.id)
|
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]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == u.add.channelId)
|
assert(fwd.channelId == u.add.channelId)
|
||||||
assert(fwd.message.id == u.add.id)
|
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)
|
assert(fwd.message.commit)
|
||||||
})
|
})
|
||||||
peerConnection.expectMsgType[Warning]
|
peerConnection.expectMsgType[Warning]
|
||||||
|
@ -332,7 +332,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
||||||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == u.add.channelId)
|
assert(fwd.channelId == u.add.channelId)
|
||||||
assert(fwd.message.id == u.add.id)
|
assert(fwd.message.id == u.add.id)
|
||||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||||
assert(fwd.message.commit)
|
assert(fwd.message.commit)
|
||||||
})
|
})
|
||||||
peerConnection.expectMsgType[Warning]
|
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]])
|
val fwds = (0 until 5).map(_ => register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]])
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
fwds.foreach(fwd => {
|
fwds.foreach(fwd => {
|
||||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||||
assert(fwd.message.commit)
|
assert(fwd.message.commit)
|
||||||
})
|
})
|
||||||
assert(fwds.map(_.channelId).toSet == (upstream1 ++ upstream2.slice(0, 1) ++ upstream3.received).map(_.add.channelId).toSet)
|
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]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == add.channelId)
|
assert(fwd.channelId == add.channelId)
|
||||||
assert(fwd.message.id == add.id)
|
assert(fwd.message.id == add.id)
|
||||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||||
assert(fwd.message.commit)
|
assert(fwd.message.commit)
|
||||||
})
|
})
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
|
@ -1176,7 +1176,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike {
|
||||||
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
val fwd = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]]
|
||||||
assert(fwd.channelId == upstream1.add.channelId)
|
assert(fwd.channelId == upstream1.add.channelId)
|
||||||
assert(fwd.message.id == upstream1.add.id)
|
assert(fwd.message.id == upstream1.add.id)
|
||||||
assert(fwd.message.reason == Right(UnknownNextPeer()))
|
assert(fwd.message.reason == FailureReason.LocalFailure(UnknownNextPeer()))
|
||||||
assert(fwd.message.commit)
|
assert(fwd.message.commit)
|
||||||
register.expectNoMessage(100 millis)
|
register.expectNoMessage(100 millis)
|
||||||
probe.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
|
val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(fail.id == add_ab.id)
|
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)
|
assert(fail.delay_opt.nonEmpty)
|
||||||
|
|
||||||
register.expectNoMessage(50 millis)
|
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
|
val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(fail.id == add_ab.id)
|
assert(fail.id == add_ab.id)
|
||||||
assert(fail.reason == Right(RequiredNodeFeatureMissing()))
|
assert(fail.reason == FailureReason.LocalFailure(RequiredNodeFeatureMissing()))
|
||||||
|
|
||||||
register.expectNoMessage(50 millis)
|
register.expectNoMessage(50 millis)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package fr.acinq.eclair.wire.internal
|
||||||
import fr.acinq.bitcoin.scalacompat.ByteVector32
|
import fr.acinq.bitcoin.scalacompat.ByteVector32
|
||||||
import fr.acinq.eclair.UInt64
|
import fr.acinq.eclair.UInt64
|
||||||
import fr.acinq.eclair.channel._
|
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 org.scalatest.funsuite.AnyFunSuite
|
||||||
import scodec.bits.{ByteVector, HexStringSyntax}
|
import scodec.bits.{ByteVector, HexStringSyntax}
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ class CommandCodecsSpec extends AnyFunSuite {
|
||||||
test("encode/decode all settlement commands") {
|
test("encode/decode all settlement commands") {
|
||||||
val testCases: Map[HtlcSettlementCommand, ByteVector] = Map(
|
val testCases: Map[HtlcSettlementCommand, ByteVector] = Map(
|
||||||
CMD_FULFILL_HTLC(1573, ByteVector32(hex"e64e7c07667366e517886af99a25a5dd547014c95ba392ea4623fbf47fe00927")) -> hex"0000 0000000000000625 e64e7c07667366e517886af99a25a5dd547014c95ba392ea4623fbf47fe00927",
|
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(42456, FailureReason.EncryptedDownstreamFailure(hex"d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44")) -> hex"0004 000000000000a5d8 00 0091 d21a88a158067efecbee41da24e1d7407747f135e585e7417843729d3bff5c160817d14ce569761d93749a23d227edc0ade99c1a8d59541e45e1f623af2602d568a9a3c3bca71f1b4860ae0b599ba016c58224eab7721ed930eb2bdfd83ff940cc9e8106b0bd6b2027821f8d102b8c680664d90ce9e69d8bb96453a7b495710b83c13e4b3085bb0156b7091ed927305c44",
|
||||||
CMD_FAIL_HTLC(253, Right(TemporaryNodeFailure())) -> hex"0003 00000000000000fd ff 0002 2002",
|
CMD_FAIL_HTLC(253, FailureReason.LocalFailure(TemporaryNodeFailure())) -> hex"0004 00000000000000fd 01 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(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",
|
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 data123 = hex"fea75bb8cf45349eb544d8da832af5af30eefa671ec27cf2e4867bacada2dbe00a6ce5141164aa153ac8b4b25c75c3af15c4b5cb6a293607751a079bc546da17f654b76a74bc57b6b21ed73d2d3909f3682f01b85418a0f0ecddb759e9481d4563a572ac1ddcb77c64ae167d8dfbd889703cb5c33b4b9636bad472"
|
||||||
val testCases = Map(
|
val testCases = Map(
|
||||||
hex"0000 000000000000002ae4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb" -> CMD_FULFILL_HTLC(42, data32, commit = false, None),
|
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 000000000000002a003dff53addc67a29a4f5aa26c6d41957ad798777d338f613e7972433dd656d16df00536728a08b2550a9d645a592e3ae1d78ae25ae5b5149b03ba8d03cde2a36d0bfb2a5bb53a5e2bdb590f6b9e969c84f9b41780dc2a0c5078766edbacf4a40ea2b1d2b9560eee5bbe32570b3ec6fdec44b81e5ae19da5cb1b5d6a3900" -> CMD_FAIL_HTLC(42, FailureReason.EncryptedDownstreamFailure(data123), None, commit = false, None),
|
||||||
hex"0001 000000000000002a900100" -> CMD_FAIL_HTLC(42, Right(TemporaryNodeFailure())),
|
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),
|
hex"0002 000000000000002ae4927c04913251b44d0a3a8e57ded746fee80ff3b424e70dad2a1428eeba86cb01c8" -> CMD_FAIL_MALFORMED_HTLC(42, data32, 456, commit = false, None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue