mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 02:27:32 +01:00
Add context logging to route request (#1469)
This commit is contained in:
parent
6d9dbb8612
commit
85163cb356
@ -71,7 +71,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
|
||||
val maxFee = routeParams.getMaxFee(r.totalAmount)
|
||||
log.debug("sending {} with maximum fee {}", r.totalAmount, maxFee)
|
||||
val d = PaymentProgress(sender, r, r.maxAttempts, Map.empty, Ignore.empty, Nil)
|
||||
router ! createRouteRequest(nodeParams, r.totalAmount, maxFee, routeParams, d)
|
||||
router ! createRouteRequest(nodeParams, r.totalAmount, maxFee, routeParams, d, cfg)
|
||||
goto(WAIT_FOR_ROUTES) using d
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
|
||||
// remaining amount. In that case we discard these routes and send a new request to the router.
|
||||
log.info("discarding routes, another child payment failed so we need to recompute them (amount = {}, maximum fee = {})", toSend, maxFee)
|
||||
val routeParams = d.request.getRouteParams(nodeParams, randomize = true) // we randomize route selection when we retry
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d)
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d, cfg)
|
||||
stay
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
|
||||
val (toSend, maxFee) = remainingToSend(nodeParams, d.request, d.pending.values)
|
||||
log.debug("retry sending {} with maximum fee {} without ignoring channels ({})", toSend, maxFee, d.ignore.channels.map(_.shortChannelId).mkString(","))
|
||||
val routeParams = d.request.getRouteParams(nodeParams, randomize = true) // we randomize route selection when we retry
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d).copy(ignore = d.ignore.emptyChannels())
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d, cfg).copy(ignore = d.ignore.emptyChannels())
|
||||
retriedFailedChannels = true
|
||||
stay using d.copy(remainingAttempts = (d.remainingAttempts - 1).max(0), ignore = d.ignore.emptyChannels())
|
||||
} else {
|
||||
@ -147,7 +147,7 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
|
||||
log.debug("child payment failed, retry sending {} with maximum fee {}", toSend, maxFee)
|
||||
val routeParams = d.request.getRouteParams(nodeParams, randomize = true) // we randomize route selection when we retry
|
||||
val d1 = d.copy(pending = stillPending, ignore = ignore1, failures = d.failures ++ pf.failures)
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d1)
|
||||
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d1, cfg)
|
||||
goto(WAIT_FOR_ROUTES) using d1
|
||||
}
|
||||
|
||||
@ -369,7 +369,7 @@ object MultiPartPaymentLifecycle {
|
||||
*/
|
||||
case class PaymentSucceeded(sender: ActorRef, request: SendMultiPartPayment, preimage: ByteVector32, parts: Seq[PartialPayment], pending: Set[UUID]) extends Data
|
||||
|
||||
private def createRouteRequest(nodeParams: NodeParams, toSend: MilliSatoshi, maxFee: MilliSatoshi, routeParams: RouteParams, d: PaymentProgress): RouteRequest =
|
||||
private def createRouteRequest(nodeParams: NodeParams, toSend: MilliSatoshi, maxFee: MilliSatoshi, routeParams: RouteParams, d: PaymentProgress, cfg: SendPaymentConfig): RouteRequest =
|
||||
RouteRequest(
|
||||
nodeParams.nodeId,
|
||||
d.request.targetNodeId,
|
||||
@ -379,7 +379,8 @@ object MultiPartPaymentLifecycle {
|
||||
d.ignore,
|
||||
Some(routeParams),
|
||||
allowMultiPart = true,
|
||||
d.pending.values.toSeq)
|
||||
d.pending.values.toSeq,
|
||||
Some(cfg.paymentContext))
|
||||
|
||||
private def createChildPayment(route: Route, request: SendMultiPartPayment): SendPaymentToRoute = {
|
||||
val finalPayload = Onion.createMultiPartPayload(route.amount, request.totalAmount, request.targetExpiry, request.paymentSecret, request.additionalTlvs, request.userCustomTlvs)
|
||||
|
@ -30,7 +30,7 @@ import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle.{PreimageReceived,
|
||||
import fr.acinq.eclair.payment.send.PaymentError._
|
||||
import fr.acinq.eclair.payment.send.PaymentLifecycle.{SendPayment, SendPaymentToRoute}
|
||||
import fr.acinq.eclair.router.RouteNotFound
|
||||
import fr.acinq.eclair.router.Router.{Hop, NodeHop, Route, RouteParams}
|
||||
import fr.acinq.eclair.router.Router._
|
||||
import fr.acinq.eclair.wire.Onion.FinalLegacyPayload
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, LongToBtcAmount, MilliSatoshi, NodeParams, randomBytes32}
|
||||
@ -329,6 +329,8 @@ object PaymentInitiator {
|
||||
def fullRoute(route: Route): Seq[Hop] = route.hops ++ additionalHops
|
||||
|
||||
def createPaymentSent(preimage: ByteVector32, parts: Seq[PaymentSent.PartialPayment]) = PaymentSent(parentId, paymentHash, preimage, recipientAmount, recipientNodeId, parts)
|
||||
|
||||
def paymentContext: PaymentContext = PaymentContext(id, parentId, paymentHash)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
||||
log.debug("sending {} to route {}", c.finalPayload.amount, c.printRoute())
|
||||
val send = SendPayment(c.targetNodeId, c.finalPayload, maxAttempts = 1, assistedRoutes = c.assistedRoutes)
|
||||
c.route.fold(
|
||||
hops => router ! FinalizeRoute(c.finalPayload.amount, hops, c.assistedRoutes),
|
||||
hops => router ! FinalizeRoute(c.finalPayload.amount, hops, c.assistedRoutes, paymentContext = Some(cfg.paymentContext)),
|
||||
route => self ! RouteResponse(route :: Nil)
|
||||
)
|
||||
if (cfg.storeInDb) {
|
||||
@ -92,7 +92,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
||||
span.tag(Tags.TotalAmount, c.finalPayload.totalAmount.toLong)
|
||||
span.tag(Tags.Expiry, c.finalPayload.expiry.toLong)
|
||||
log.debug("sending {} to {}", c.finalPayload.amount, c.targetNodeId)
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), c.assistedRoutes, routeParams = c.routeParams)
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), c.assistedRoutes, routeParams = c.routeParams, paymentContext = Some(cfg.paymentContext))
|
||||
if (cfg.storeInDb) {
|
||||
paymentsDb.addOutgoingPayment(OutgoingPayment(id, cfg.parentId, cfg.externalId, paymentHash, PaymentType.Standard, c.finalPayload.amount, cfg.recipientAmount, cfg.recipientNodeId, System.currentTimeMillis, cfg.paymentRequest, OutgoingPaymentStatus.Pending))
|
||||
}
|
||||
@ -174,12 +174,12 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
||||
val ignore1 = if (Announcements.checkSig(failureMessage.update, nodeId)) {
|
||||
val assistedRoutes1 = handleUpdate(nodeId, failureMessage, data)
|
||||
// let's try again, router will have updated its state
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), assistedRoutes1, ignore, c.routeParams)
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), assistedRoutes1, ignore, c.routeParams, paymentContext = Some(cfg.paymentContext))
|
||||
ignore
|
||||
} else {
|
||||
// this node is fishy, it gave us a bad sig!! let's filter it out
|
||||
log.warning(s"got bad signature from node=$nodeId update=${failureMessage.update}")
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), c.assistedRoutes, ignore + nodeId, c.routeParams)
|
||||
router ! RouteRequest(nodeParams.nodeId, c.targetNodeId, c.finalPayload.amount, c.getMaxFee(nodeParams), c.assistedRoutes, ignore + nodeId, c.routeParams, paymentContext = Some(cfg.paymentContext))
|
||||
ignore + nodeId
|
||||
}
|
||||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(s, c, failures :+ RemoteFailure(cfg.fullRoute(route), e), ignore1)
|
||||
@ -236,7 +236,7 @@ class PaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig, router: A
|
||||
|
||||
private def retry(failure: PaymentFailure, data: WaitingForComplete): FSM.State[PaymentLifecycle.State, PaymentLifecycle.Data] = {
|
||||
val ignore1 = PaymentFailure.updateIgnored(failure, data.ignore)
|
||||
router ! RouteRequest(nodeParams.nodeId, data.c.targetNodeId, data.c.finalPayload.amount, data.c.getMaxFee(nodeParams), data.c.assistedRoutes, ignore1, data.c.routeParams)
|
||||
router ! RouteRequest(nodeParams.nodeId, data.c.targetNodeId, data.c.finalPayload.amount, data.c.getMaxFee(nodeParams), data.c.assistedRoutes, ignore1, data.c.routeParams, paymentContext = Some(cfg.paymentContext))
|
||||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(data.sender, data.c, data.failures :+ failure, ignore1)
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,10 @@
|
||||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, Status}
|
||||
import akka.event.LoggingAdapter
|
||||
import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Satoshi}
|
||||
import fr.acinq.eclair.Logs.LogCategory
|
||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||
import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph.graphEdgeToHop
|
||||
import fr.acinq.eclair.router.Graph.GraphStructure.{DirectedGraph, GraphEdge}
|
||||
@ -37,64 +38,75 @@ import scala.util.{Failure, Random, Success, Try}
|
||||
|
||||
object RouteCalculation {
|
||||
|
||||
def finalizeRoute(d: Data, fr: FinalizeRoute)(implicit ctx: ActorContext, log: LoggingAdapter): Data = {
|
||||
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
|
||||
def finalizeRoute(d: Data, fr: FinalizeRoute)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = {
|
||||
Logs.withMdc(log)(Logs.mdc(
|
||||
category_opt = Some(LogCategory.PAYMENT),
|
||||
parentPaymentId_opt = fr.paymentContext.map(_.parentId),
|
||||
paymentId_opt = fr.paymentContext.map(_.id),
|
||||
paymentHash_opt = fr.paymentContext.map(_.paymentHash))) {
|
||||
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
|
||||
|
||||
val assistedChannels: Map[ShortChannelId, AssistedChannel] = fr.assistedRoutes.flatMap(toAssistedChannels(_, fr.hops.last, fr.amount)).toMap
|
||||
val extraEdges = assistedChannels.values.map(ac =>
|
||||
GraphEdge(ChannelDesc(ac.extraHop.shortChannelId, ac.extraHop.nodeId, ac.nextNodeId), toFakeUpdate(ac.extraHop, ac.htlcMaximum), htlcMaxToCapacity(ac.htlcMaximum), Some(ac.htlcMaximum))
|
||||
).toSet
|
||||
val g = extraEdges.foldLeft(d.graph) { case (g: DirectedGraph, e: GraphEdge) => g.addEdge(e) }
|
||||
// split into sublists [(a,b),(b,c), ...] then get the edges between each of those pairs
|
||||
fr.hops.sliding(2).map { case List(v1, v2) => g.getEdgesBetween(v1, v2) }.toList match {
|
||||
case edges if edges.nonEmpty && edges.forall(_.nonEmpty) =>
|
||||
// select the largest edge (using balance when available, otherwise capacity).
|
||||
val selectedEdges = edges.map(es => es.maxBy(e => e.balance_opt.getOrElse(e.capacity.toMilliSatoshi)))
|
||||
val hops = selectedEdges.map(d => ChannelHop(d.desc.a, d.desc.b, d.update))
|
||||
ctx.sender ! RouteResponse(Route(fr.amount, hops) :: Nil)
|
||||
case _ => // some nodes in the supplied route aren't connected in our graph
|
||||
ctx.sender ! Status.Failure(new IllegalArgumentException("Not all the nodes in the supplied route are connected with public channels"))
|
||||
val assistedChannels: Map[ShortChannelId, AssistedChannel] = fr.assistedRoutes.flatMap(toAssistedChannels(_, fr.hops.last, fr.amount)).toMap
|
||||
val extraEdges = assistedChannels.values.map(ac =>
|
||||
GraphEdge(ChannelDesc(ac.extraHop.shortChannelId, ac.extraHop.nodeId, ac.nextNodeId), toFakeUpdate(ac.extraHop, ac.htlcMaximum), htlcMaxToCapacity(ac.htlcMaximum), Some(ac.htlcMaximum))
|
||||
).toSet
|
||||
val g = extraEdges.foldLeft(d.graph) { case (g: DirectedGraph, e: GraphEdge) => g.addEdge(e) }
|
||||
// split into sublists [(a,b),(b,c), ...] then get the edges between each of those pairs
|
||||
fr.hops.sliding(2).map { case List(v1, v2) => g.getEdgesBetween(v1, v2) }.toList match {
|
||||
case edges if edges.nonEmpty && edges.forall(_.nonEmpty) =>
|
||||
// select the largest edge (using balance when available, otherwise capacity).
|
||||
val selectedEdges = edges.map(es => es.maxBy(e => e.balance_opt.getOrElse(e.capacity.toMilliSatoshi)))
|
||||
val hops = selectedEdges.map(d => ChannelHop(d.desc.a, d.desc.b, d.update))
|
||||
ctx.sender ! RouteResponse(Route(fr.amount, hops) :: Nil)
|
||||
case _ => // some nodes in the supplied route aren't connected in our graph
|
||||
ctx.sender ! Status.Failure(new IllegalArgumentException("Not all the nodes in the supplied route are connected with public channels"))
|
||||
}
|
||||
d
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
def handleRouteRequest(d: Data, routerConf: RouterConf, currentBlockHeight: Long, r: RouteRequest)(implicit ctx: ActorContext, log: LoggingAdapter): Data = {
|
||||
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
|
||||
def handleRouteRequest(d: Data, routerConf: RouterConf, currentBlockHeight: Long, r: RouteRequest)(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Data = {
|
||||
Logs.withMdc(log)(Logs.mdc(
|
||||
category_opt = Some(LogCategory.PAYMENT),
|
||||
parentPaymentId_opt = r.paymentContext.map(_.parentId),
|
||||
paymentId_opt = r.paymentContext.map(_.id),
|
||||
paymentHash_opt = r.paymentContext.map(_.paymentHash))) {
|
||||
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
|
||||
|
||||
// we convert extra routing info provided in the payment request to fake channel_update
|
||||
// it takes precedence over all other channel_updates we know
|
||||
val assistedChannels: Map[ShortChannelId, AssistedChannel] = r.assistedRoutes.flatMap(toAssistedChannels(_, r.target, r.amount))
|
||||
.filterNot { case (_, ac) => ac.extraHop.nodeId == r.source } // we ignore routing hints for our own channels, we have more accurate information
|
||||
.toMap
|
||||
val extraEdges = assistedChannels.values.map(ac =>
|
||||
GraphEdge(ChannelDesc(ac.extraHop.shortChannelId, ac.extraHop.nodeId, ac.nextNodeId), toFakeUpdate(ac.extraHop, ac.htlcMaximum), htlcMaxToCapacity(ac.htlcMaximum), Some(ac.htlcMaximum))
|
||||
).toSet
|
||||
val ignoredEdges = r.ignore.channels ++ d.excludedChannels
|
||||
val params = r.routeParams.getOrElse(getDefaultRouteParams(routerConf))
|
||||
val routesToFind = if (params.randomize) DEFAULT_ROUTES_COUNT else 1
|
||||
// we convert extra routing info provided in the payment request to fake channel_update
|
||||
// it takes precedence over all other channel_updates we know
|
||||
val assistedChannels: Map[ShortChannelId, AssistedChannel] = r.assistedRoutes.flatMap(toAssistedChannels(_, r.target, r.amount))
|
||||
.filterNot { case (_, ac) => ac.extraHop.nodeId == r.source } // we ignore routing hints for our own channels, we have more accurate information
|
||||
.toMap
|
||||
val extraEdges = assistedChannels.values.map(ac =>
|
||||
GraphEdge(ChannelDesc(ac.extraHop.shortChannelId, ac.extraHop.nodeId, ac.nextNodeId), toFakeUpdate(ac.extraHop, ac.htlcMaximum), htlcMaxToCapacity(ac.htlcMaximum), Some(ac.htlcMaximum))
|
||||
).toSet
|
||||
val ignoredEdges = r.ignore.channels ++ d.excludedChannels
|
||||
val params = r.routeParams.getOrElse(getDefaultRouteParams(routerConf))
|
||||
val routesToFind = if (params.randomize) DEFAULT_ROUTES_COUNT else 1
|
||||
|
||||
log.info(s"finding routes ${r.source}->${r.target} with assistedChannels={} ignoreNodes={} ignoreChannels={} excludedChannels={}", assistedChannels.keys.mkString(","), r.ignore.nodes.map(_.value).mkString(","), r.ignore.channels.mkString(","), d.excludedChannels.mkString(","))
|
||||
log.info("finding routes with randomize={} params={}", params.randomize, params)
|
||||
val tags = TagSet.Empty.withTag(Tags.MultiPart, r.allowMultiPart).withTag(Tags.Amount, Tags.amountBucket(r.amount))
|
||||
KamonExt.time(Metrics.FindRouteDuration.withTags(tags.withTag(Tags.NumberOfRoutes, routesToFind.toLong))) {
|
||||
val result = if (r.allowMultiPart) {
|
||||
findMultiPartRoute(d.graph, r.source, r.target, r.amount, r.maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, params, currentBlockHeight)
|
||||
} else {
|
||||
findRoute(d.graph, r.source, r.target, r.amount, r.maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, params, currentBlockHeight)
|
||||
}
|
||||
result match {
|
||||
case Success(routes) =>
|
||||
Metrics.RouteResults.withTags(tags).record(routes.length)
|
||||
routes.foreach(route => Metrics.RouteLength.withTags(tags).record(route.length))
|
||||
ctx.sender ! RouteResponse(routes)
|
||||
case Failure(t) =>
|
||||
val failure = if (isNeighborBalanceTooLow(d.graph, r)) BalanceTooLow else t
|
||||
Metrics.FindRouteErrors.withTags(tags.withTag(Tags.Error, failure.getClass.getSimpleName)).increment()
|
||||
ctx.sender ! Status.Failure(failure)
|
||||
log.info(s"finding routes ${r.source}->${r.target} with assistedChannels={} ignoreNodes={} ignoreChannels={} excludedChannels={}", assistedChannels.keys.mkString(","), r.ignore.nodes.map(_.value).mkString(","), r.ignore.channels.mkString(","), d.excludedChannels.mkString(","))
|
||||
log.info("finding routes with randomize={} params={}", params.randomize, params)
|
||||
val tags = TagSet.Empty.withTag(Tags.MultiPart, r.allowMultiPart).withTag(Tags.Amount, Tags.amountBucket(r.amount))
|
||||
KamonExt.time(Metrics.FindRouteDuration.withTags(tags.withTag(Tags.NumberOfRoutes, routesToFind.toLong))) {
|
||||
val result = if (r.allowMultiPart) {
|
||||
findMultiPartRoute(d.graph, r.source, r.target, r.amount, r.maxFee, extraEdges, ignoredEdges, r.ignore.nodes, r.pendingPayments, params, currentBlockHeight)
|
||||
} else {
|
||||
findRoute(d.graph, r.source, r.target, r.amount, r.maxFee, routesToFind, extraEdges, ignoredEdges, r.ignore.nodes, params, currentBlockHeight)
|
||||
}
|
||||
result match {
|
||||
case Success(routes) =>
|
||||
Metrics.RouteResults.withTags(tags).record(routes.length)
|
||||
routes.foreach(route => Metrics.RouteLength.withTags(tags).record(route.length))
|
||||
ctx.sender ! RouteResponse(routes)
|
||||
case Failure(t) =>
|
||||
val failure = if (isNeighborBalanceTooLow(d.graph, r)) BalanceTooLow else t
|
||||
Metrics.FindRouteErrors.withTags(tags.withTag(Tags.Error, failure.getClass.getSimpleName)).increment()
|
||||
ctx.sender ! Status.Failure(failure)
|
||||
}
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
d
|
||||
}
|
||||
|
||||
private def toFakeUpdate(extraHop: ExtraHop, htlcMaximum: MilliSatoshi): ChannelUpdate = {
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package fr.acinq.eclair.router
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import akka.Done
|
||||
import akka.actor.{ActorRef, Props}
|
||||
import akka.event.DiagnosticLoggingAdapter
|
||||
@ -31,6 +33,7 @@ import fr.acinq.eclair.crypto.TransportHandler
|
||||
import fr.acinq.eclair.db.NetworkDb
|
||||
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
|
||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentConfig
|
||||
import fr.acinq.eclair.router.Graph.GraphStructure.DirectedGraph
|
||||
import fr.acinq.eclair.router.Graph.WeightRatios
|
||||
import fr.acinq.eclair.router.Monitoring.{Metrics, Tags}
|
||||
@ -396,9 +399,18 @@ object Router {
|
||||
ignore: Ignore = Ignore.empty,
|
||||
routeParams: Option[RouteParams] = None,
|
||||
allowMultiPart: Boolean = false,
|
||||
pendingPayments: Seq[Route] = Nil)
|
||||
pendingPayments: Seq[Route] = Nil,
|
||||
paymentContext: Option[PaymentContext] = None)
|
||||
|
||||
case class FinalizeRoute(amount: MilliSatoshi, hops: Seq[PublicKey], assistedRoutes: Seq[Seq[ExtraHop]] = Nil)
|
||||
case class FinalizeRoute(amount: MilliSatoshi,
|
||||
hops: Seq[PublicKey],
|
||||
assistedRoutes: Seq[Seq[ExtraHop]] = Nil,
|
||||
paymentContext: Option[PaymentContext] = None)
|
||||
|
||||
/**
|
||||
* Useful for having appropriate logging context at hand when finding routes
|
||||
*/
|
||||
case class PaymentContext(id: UUID, parentId: UUID, paymentHash: ByteVector32)
|
||||
|
||||
case class Route(amount: MilliSatoshi, hops: Seq[ChannelHop]) {
|
||||
require(hops.nonEmpty, "route cannot be empty")
|
||||
|
@ -46,7 +46,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
|
||||
import MultiPartPaymentLifecycleSpec._
|
||||
|
||||
case class FixtureParam(paymentId: UUID,
|
||||
case class FixtureParam(cfg: SendPaymentConfig,
|
||||
nodeParams: NodeParams,
|
||||
payFsm: TestFSMRef[MultiPartPaymentLifecycle.State, MultiPartPaymentLifecycle.Data, MultiPartPaymentLifecycle],
|
||||
router: TestProbe,
|
||||
@ -64,7 +64,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
}
|
||||
val paymentHandler = TestFSMRef(new TestMultiPartPaymentLifecycle().asInstanceOf[MultiPartPaymentLifecycle])
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[PaymentEvent])
|
||||
withFixture(test.toNoArgTest(FixtureParam(id, nodeParams, paymentHandler, router, sender, childPayFsm, eventListener)))
|
||||
withFixture(test.toNoArgTest(FixtureParam(cfg, nodeParams, paymentHandler, router, sender, childPayFsm, eventListener)))
|
||||
}
|
||||
|
||||
test("successful first attempt (single part)") { f =>
|
||||
@ -74,7 +74,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
val payment = SendMultiPartPayment(randomBytes32, e, finalAmount, expiry, 1, routeParams = Some(routeParams.copy(randomize = true)))
|
||||
sender.send(payFsm, payment)
|
||||
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, routeParams = Some(routeParams.copy(randomize = false)), allowMultiPart = true))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, routeParams = Some(routeParams.copy(randomize = false)), allowMultiPart = true, paymentContext = Some(cfg.paymentContext)))
|
||||
assert(payFsm.stateName === WAIT_FOR_ROUTES)
|
||||
|
||||
val singleRoute = Route(finalAmount, hop_ab_1 :: hop_be :: Nil)
|
||||
@ -100,7 +100,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
val payment = SendMultiPartPayment(randomBytes32, e, 1200000 msat, expiry, 1, routeParams = Some(routeParams.copy(randomize = false)))
|
||||
sender.send(payFsm, payment)
|
||||
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, 1200000 msat, maxFee, routeParams = Some(routeParams.copy(randomize = false)), allowMultiPart = true))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, 1200000 msat, maxFee, routeParams = Some(routeParams.copy(randomize = false)), allowMultiPart = true, paymentContext = Some(cfg.paymentContext)))
|
||||
assert(payFsm.stateName === WAIT_FOR_ROUTES)
|
||||
|
||||
val routes = Seq(
|
||||
@ -156,7 +156,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
val childId = payFsm.stateData.asInstanceOf[PaymentProgress].pending.keys.head
|
||||
childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(failingRoute.hops, Sphinx.DecryptedFailurePacket(b, PermanentChannelFailure)))))
|
||||
// We retry ignoring the failing channel.
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, routeParams = Some(routeParams.copy(randomize = true)), allowMultiPart = true, ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_be, b, e)))))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, routeParams = Some(routeParams.copy(randomize = true)), allowMultiPart = true, ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_be, b, e))), paymentContext = Some(cfg.paymentContext)))
|
||||
router.send(payFsm, RouteResponse(Seq(Route(400000 msat, hop_ac_1 :: hop_ce :: Nil), Route(600000 msat, hop_ad :: hop_de :: Nil))))
|
||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
@ -183,13 +183,13 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId1, paymentHash, Seq(RemoteFailure(failedRoute1.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure)))))
|
||||
|
||||
// When we retry, we ignore the failing node and we let the router know about the remaining pending route.
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, failedRoute1.amount, maxFee - failedRoute1.fee, ignore = Ignore(Set(b), Set.empty), pendingPayments = Seq(failedRoute2), allowMultiPart = true, routeParams = Some(routeParams.copy(randomize = true))))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, failedRoute1.amount, maxFee - failedRoute1.fee, ignore = Ignore(Set(b), Set.empty), pendingPayments = Seq(failedRoute2), allowMultiPart = true, routeParams = Some(routeParams.copy(randomize = true)), paymentContext = Some(cfg.paymentContext)))
|
||||
// The second part fails while we're still waiting for new routes.
|
||||
childPayFsm.send(payFsm, PaymentFailed(failedId2, paymentHash, Seq(RemoteFailure(failedRoute2.hops, Sphinx.DecryptedFailurePacket(b, TemporaryNodeFailure)))))
|
||||
// We receive a response to our first request, but it's now obsolete: we re-sent a new route request that takes into
|
||||
// account the latest failures.
|
||||
router.send(payFsm, RouteResponse(Seq(Route(failedRoute1.amount, hop_ac_1 :: hop_ce :: Nil))))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, ignore = Ignore(Set(b), Set.empty), allowMultiPart = true, routeParams = Some(routeParams.copy(randomize = true))))
|
||||
router.expectMsg(RouteRequest(nodeParams.nodeId, e, finalAmount, maxFee, ignore = Ignore(Set(b), Set.empty), allowMultiPart = true, routeParams = Some(routeParams.copy(randomize = true)), paymentContext = Some(cfg.paymentContext)))
|
||||
awaitCond(payFsm.stateData.asInstanceOf[PaymentProgress].pending.isEmpty)
|
||||
childPayFsm.expectNoMsg(100 millis)
|
||||
|
||||
@ -225,7 +225,8 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_ab_1, a, b))),
|
||||
pendingPayments = Seq(pendingRoute),
|
||||
allowMultiPart = true,
|
||||
routeParams = Some(routeParams.copy(randomize = true)))
|
||||
routeParams = Some(routeParams.copy(randomize = true)),
|
||||
paymentContext = Some(cfg.paymentContext))
|
||||
router.expectMsg(expectedRouteRequest)
|
||||
router.send(payFsm, Status.Failure(RouteNotFound))
|
||||
router.expectMsg(expectedRouteRequest.copy(ignore = Ignore.empty))
|
||||
@ -270,7 +271,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
router.send(payFsm, Status.Failure(RouteNotFound))
|
||||
|
||||
val result = sender.expectMsgType[PaymentFailed]
|
||||
assert(result.id === paymentId)
|
||||
assert(result.id === cfg.id)
|
||||
assert(result.paymentHash === paymentHash)
|
||||
assert(result.failures === Seq(LocalFailure(Nil, RouteNotFound)))
|
||||
|
||||
@ -347,10 +348,10 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
awaitCond(payFsm.stateName === PAYMENT_ABORTED)
|
||||
|
||||
sender.watch(payFsm)
|
||||
childPayFsm.send(payFsm, PaymentSent(paymentId, paymentHash, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(successId, successRoute.amount, successRoute.fee, randomBytes32, Some(successRoute.hops)))))
|
||||
childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentHash, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(successId, successRoute.amount, successRoute.fee, randomBytes32, Some(successRoute.hops)))))
|
||||
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage))
|
||||
val result = sender.expectMsgType[PaymentSent]
|
||||
assert(result.id === paymentId)
|
||||
assert(result.id === cfg.id)
|
||||
assert(result.paymentHash === paymentHash)
|
||||
assert(result.paymentPreimage === paymentPreimage)
|
||||
assert(result.parts.length === 1 && result.parts.head.id === successId)
|
||||
@ -376,7 +377,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
childPayFsm.expectMsgType[SendPaymentToRoute]
|
||||
|
||||
val (childId, route) :: (failedId, failedRoute) :: Nil = payFsm.stateData.asInstanceOf[PaymentProgress].pending.toSeq
|
||||
childPayFsm.send(payFsm, PaymentSent(paymentId, paymentHash, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(childId, route.amount, route.fee, randomBytes32, Some(route.hops)))))
|
||||
childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentHash, paymentPreimage, finalAmount, e, Seq(PaymentSent.PartialPayment(childId, route.amount, route.fee, randomBytes32, Some(route.hops)))))
|
||||
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage))
|
||||
awaitCond(payFsm.stateName === PAYMENT_SUCCEEDED)
|
||||
|
||||
@ -403,10 +404,10 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
val partialPayments = pending.map {
|
||||
case (childId, route) => PaymentSent.PartialPayment(childId, route.amount, route.fee, randomBytes32, Some(route.hops))
|
||||
}
|
||||
partialPayments.foreach(pp => childPayFsm.send(payFsm, PaymentSent(paymentId, paymentHash, paymentPreimage, finalAmount, e, Seq(pp))))
|
||||
partialPayments.foreach(pp => childPayFsm.send(payFsm, PaymentSent(cfg.id, paymentHash, paymentPreimage, finalAmount, e, Seq(pp))))
|
||||
sender.expectMsg(PreimageReceived(paymentHash, paymentPreimage))
|
||||
val result = sender.expectMsgType[PaymentSent]
|
||||
assert(result.id === paymentId)
|
||||
assert(result.id === cfg.id)
|
||||
assert(result.paymentHash === paymentHash)
|
||||
assert(result.paymentPreimage === paymentPreimage)
|
||||
assert(result.parts.toSet === partialPayments.toSet)
|
||||
@ -437,7 +438,7 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
|
||||
}
|
||||
|
||||
val result = sender.expectMsgType[PaymentFailed]
|
||||
assert(result.id === paymentId)
|
||||
assert(result.id === cfg.id)
|
||||
assert(result.paymentHash === paymentHash)
|
||||
assert(result.failures.nonEmpty)
|
||||
|
||||
|
@ -62,10 +62,9 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
val defaultExternalId = UUID.randomUUID().toString
|
||||
val defaultPaymentRequest = SendPaymentRequest(defaultAmountMsat, defaultPaymentHash, d, 1, externalId = Some(defaultExternalId))
|
||||
|
||||
def defaultRouteRequest(source: PublicKey, target: PublicKey): RouteRequest = RouteRequest(source, target, defaultAmountMsat, defaultMaxFee)
|
||||
def defaultRouteRequest(source: PublicKey, target: PublicKey, cfg: SendPaymentConfig): RouteRequest = RouteRequest(source, target, defaultAmountMsat, defaultMaxFee, paymentContext = Some(cfg.paymentContext))
|
||||
|
||||
case class PaymentFixture(id: UUID,
|
||||
parentId: UUID,
|
||||
case class PaymentFixture(cfg: SendPaymentConfig,
|
||||
nodeParams: NodeParams,
|
||||
paymentFSM: TestFSMRef[PaymentLifecycle.State, PaymentLifecycle.Data, PaymentLifecycle],
|
||||
routerForwarder: TestProbe,
|
||||
@ -83,12 +82,13 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[PaymentEvent])
|
||||
PaymentFixture(id, parentId, nodeParams, paymentFSM, routerForwarder, register, sender, monitor, eventListener)
|
||||
PaymentFixture(cfg, nodeParams, paymentFSM, routerForwarder, register, sender, monitor, eventListener)
|
||||
}
|
||||
|
||||
test("send to route") { _ =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
// pre-computed route going from A to D
|
||||
val route = Route(defaultAmountMsat, ChannelHop(a, b, update_ab) :: ChannelHop(b, c, update_bc) :: ChannelHop(c, d, update_cd) :: Nil)
|
||||
@ -112,12 +112,13 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("send to route (node_id only)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
// pre-computed route going from A to D
|
||||
val request = SendPaymentToRoute(Left(Seq(a, b, c, d)), FinalLegacyPayload(defaultAmountMsat, defaultExpiry))
|
||||
|
||||
sender.send(paymentFSM, request)
|
||||
routerForwarder.expectMsg(FinalizeRoute(defaultAmountMsat, Seq(a, b, c, d)))
|
||||
routerForwarder.expectMsg(FinalizeRoute(defaultAmountMsat, Seq(a, b, c, d), paymentContext = Some(cfg.paymentContext)))
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
@ -148,13 +149,14 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("send to route (routing hints)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val recipient = randomKey.publicKey
|
||||
val routingHint = Seq(Seq(ExtraHop(c, ShortChannelId(561), 1 msat, 100, CltvExpiryDelta(144))))
|
||||
val request = SendPaymentToRoute(Left(Seq(a, b, c, recipient)), FinalLegacyPayload(defaultAmountMsat, defaultExpiry), routingHint)
|
||||
|
||||
sender.send(paymentFSM, request)
|
||||
routerForwarder.expectMsg(FinalizeRoute(defaultAmountMsat, Seq(a, b, c, recipient), routingHint))
|
||||
routerForwarder.expectMsg(FinalizeRoute(defaultAmountMsat, Seq(a, b, c, recipient), routingHint, paymentContext = Some(cfg.paymentContext)))
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
@ -171,6 +173,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (route not found)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(f, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 5)
|
||||
sender.send(paymentFSM, request)
|
||||
@ -186,6 +189,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (route too expensive)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 5, routeParams = Some(RouteParams(randomize = false, 100 msat, 0.0, 20, CltvExpiryDelta(2016), None, MultiPartParams(10000 msat, 5))))
|
||||
sender.send(paymentFSM, request)
|
||||
@ -200,10 +204,11 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (unparsable failure)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 2)
|
||||
sender.send(paymentFSM, request)
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d, cfg))
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
@ -216,7 +221,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
sender.send(paymentFSM, Relayer.ForwardRemoteFail(UpdateFailHtlc(ByteVector32.Zeroes, 0, randomBytes32), defaultOrigin, UpdateAddHtlc(ByteVector32.Zeroes, 0, defaultAmountMsat, defaultPaymentHash, defaultExpiry, TestConstants.emptyOnionPacket))) // unparsable message
|
||||
|
||||
// then the payment lifecycle will ask for a new route excluding all intermediate nodes
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(ignore = Ignore(Set(c), Set.empty)))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(ignore = Ignore(Set(c), Set.empty)))
|
||||
|
||||
// let's simulate a response by the router with another route
|
||||
sender.send(paymentFSM, RouteResponse(route :: Nil))
|
||||
@ -235,13 +240,14 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (local error)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 2)
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, _, _, _) = paymentFSM.stateData
|
||||
@ -250,20 +256,21 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
sender.send(paymentFSM, Status.Failure(AddHtlcFailed(ByteVector32.Zeroes, defaultPaymentHash, ChannelUnavailable(ByteVector32.Zeroes), Local(id, Some(paymentFSM.underlying.self)), None, None)))
|
||||
|
||||
// then the payment lifecycle will ask for a new route excluding the channel
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_ab, a, b)))))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_ab, a, b)))))
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending)) // payment is still pending because the error is recoverable
|
||||
}
|
||||
|
||||
test("payment failed (first hop returns an UpdateFailMalformedHtlc)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 2)
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, _, _, _) = paymentFSM.stateData
|
||||
@ -272,19 +279,20 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
sender.send(paymentFSM, UpdateFailMalformedHtlc(ByteVector32.Zeroes, 0, randomBytes32, FailureMessageCodecs.BADONION))
|
||||
|
||||
// then the payment lifecycle will ask for a new route excluding the channel
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_ab, a, b)))))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d, cfg).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_ab, a, b)))))
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
}
|
||||
|
||||
test("payment failed (TemporaryChannelFailure)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 2)
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, route) = paymentFSM.stateData
|
||||
@ -299,7 +307,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
// payment lifecycle forwards the embedded channelUpdate to the router
|
||||
routerForwarder.expectMsg(update_bc)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(a, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
// we allow 2 tries, so we send a 2nd request to the router
|
||||
assert(sender.expectMsgType[PaymentFailed].failures === RemoteFailure(route.hops, Sphinx.DecryptedFailurePacket(b, failure)) :: LocalFailure(Nil, RouteNotFound) :: Nil)
|
||||
@ -308,13 +316,14 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (Update)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 5)
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, route1) = paymentFSM.stateData
|
||||
@ -329,7 +338,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
// payment lifecycle forwards the embedded channelUpdate to the router
|
||||
routerForwarder.expectMsg(channelUpdate_bc_modified)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending)) // 1 failure but not final, the payment is still PENDING
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
|
||||
// router answers with a new route, taking into account the new update
|
||||
@ -349,7 +358,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
// but it will still forward the embedded channelUpdate to the router
|
||||
routerForwarder.expectMsg(channelUpdate_bc_modified_2)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
|
||||
// this time the router can't find a route: game over
|
||||
@ -360,10 +369,11 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (Update in last attempt)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 1)
|
||||
sender.send(paymentFSM, request)
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, _) = paymentFSM.stateData
|
||||
@ -383,6 +393,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (Update in assisted route)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
// we build an assisted route for channel bc and cd
|
||||
val assistedRoutes = Seq(Seq(
|
||||
@ -395,7 +406,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(assistedRoutes = assistedRoutes))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(assistedRoutes = assistedRoutes))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, _) = paymentFSM.stateData
|
||||
@ -414,7 +425,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
ExtraHop(b, channelId_bc, update_bc.feeBaseMsat, update_bc.feeProportionalMillionths, channelUpdate_bc_modified.cltvExpiryDelta),
|
||||
ExtraHop(c, channelId_cd, update_cd.feeBaseMsat, update_cd.feeProportionalMillionths, update_cd.cltvExpiryDelta)
|
||||
))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(assistedRoutes = assistedRoutes1))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(assistedRoutes = assistedRoutes1))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
|
||||
// router answers with a new route, taking into account the new update
|
||||
@ -427,6 +438,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment failed (Update disabled in assisted route)") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
// we build an assisted route for channel cd
|
||||
val assistedRoutes = Seq(Seq(ExtraHop(c, channelId_cd, update_cd.feeBaseMsat, update_cd.feeProportionalMillionths, update_cd.cltvExpiryDelta)))
|
||||
@ -434,7 +446,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(assistedRoutes = assistedRoutes))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(assistedRoutes = assistedRoutes))
|
||||
routerForwarder.forward(routerFixture.router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, _) = paymentFSM.stateData
|
||||
@ -453,13 +465,14 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
def testPermanentFailure(router: ActorRef, failure: FailureMessage): Unit = {
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 2)
|
||||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE && nodeParams.db.payments.getOutgoingPayment(id).exists(_.status === OutgoingPaymentStatus.Pending))
|
||||
|
||||
val WaitingForRoute(_, _, Nil, _) = paymentFSM.stateData
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg))
|
||||
routerForwarder.forward(router)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, _, cmd1, Nil, sharedSecrets1, _, route1) = paymentFSM.stateData
|
||||
@ -469,7 +482,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
|
||||
// payment lifecycle forwards the embedded channelUpdate to the router
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_bc, b, c)))))
|
||||
routerForwarder.expectMsg(defaultRouteRequest(nodeParams.nodeId, d, cfg).copy(ignore = Ignore(Set.empty, Set(ChannelDesc(channelId_bc, b, c)))))
|
||||
routerForwarder.forward(router)
|
||||
// we allow 2 tries, so we send a 2nd request to the router, which won't find another route
|
||||
|
||||
@ -490,6 +503,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("payment succeeded") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle()
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 5)
|
||||
sender.send(paymentFSM, request)
|
||||
@ -613,6 +627,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
test("disable database and events") { routerFixture =>
|
||||
val payFixture = createPaymentLifecycle(storeInDb = false, publishEvent = false)
|
||||
import payFixture._
|
||||
import cfg._
|
||||
|
||||
val request = SendPayment(d, FinalLegacyPayload(defaultAmountMsat, defaultExpiry), 3)
|
||||
sender.send(paymentFSM, request)
|
||||
|
Loading…
Reference in New Issue
Block a user