1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-20 02:27:32 +01:00

Set max payment attempts from configuration (#931)

With a default to `5`.
This commit is contained in:
rorp 2019-04-15 06:19:39 -07:00 committed by Pierre-Marie Padiou
parent 3eceb90fa0
commit 70d7db7f96
11 changed files with 44 additions and 41 deletions

View File

@ -89,6 +89,7 @@ eclair {
payment-request-expiry = 1 hour // default expiry for payment requests generated by this node
max-pending-payment-requests = 10000000
min-funding-satoshis = 100000
max-payment-attempts = 5
autoprobe-count = 0 // number of parallel tasks that send test payments to detect invalid channels

View File

@ -50,7 +50,7 @@ trait Eclair {
def findRoute(targetNodeId: PublicKey, amountMsat: Long, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty): Future[RouteResponse]
def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry: Option[Long] = None): Future[PaymentResult]
def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry: Option[Long] = None, maxAttempts: Option[Int] = None): Future[PaymentResult]
def checkpayment(paymentHash: ByteVector32): Future[Boolean]
@ -135,10 +135,11 @@ class EclairImpl(appKit: Kit) extends Eclair {
(appKit.router ? RouteRequest(appKit.nodeParams.nodeId, targetNodeId, amountMsat, assistedRoutes)).mapTo[RouteResponse]
}
override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry: Option[Long] = None): Future[PaymentResult] = {
val sendPayment = minFinalCltvExpiry match {
case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv)
case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes)
override def send(recipientNodeId: PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]] = Seq.empty, minFinalCltvExpiry_opt: Option[Long] = None, maxAttempts_opt: Option[Int] = None): Future[PaymentResult] = {
val maxAttempts = maxAttempts_opt.getOrElse(appKit.nodeParams.maxPaymentAttempts)
val sendPayment = minFinalCltvExpiry_opt match {
case Some(minCltv) => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, finalCltvExpiry = minCltv, maxAttempts = maxAttempts)
case None => SendPayment(amountMsat, paymentHash, recipientNodeId, assistedRoutes, maxAttempts = maxAttempts)
}
(appKit.paymentInitiator ? sendPayment).mapTo[PaymentResult].map {
case s: PaymentSucceeded => s

View File

@ -76,7 +76,8 @@ case class NodeParams(keyManager: KeyManager,
maxPendingPaymentRequests: Int,
minFundingSatoshis: Long,
routerConf: RouterConf,
socksProxy_opt: Option[Socks5ProxyParams]) {
socksProxy_opt: Option[Socks5ProxyParams],
maxPaymentAttempts: Int) {
val privateKey = keyManager.nodeKey.privateKey
val nodeId = keyManager.nodeId
@ -224,7 +225,8 @@ object NodeParams {
searchRatioChannelAge = config.getDouble("router.path-finding.ratio-channel-age"),
searchRatioChannelCapacity = config.getDouble("router.path-finding.ratio-channel-capacity")
),
socksProxy_opt = socksProxy_opt
socksProxy_opt = socksProxy_opt,
maxPaymentAttempts = config.getInt("max-payment-attempts")
)
}
}

View File

@ -265,7 +265,7 @@ trait OldService extends Logging {
case JInt(amountMsat) :: JString(paymentHash) :: JString(nodeId) :: Nil =>
(Try(ByteVector32.fromValidHex(paymentHash)), Try(PublicKey(ByteVector.fromValidHex(nodeId)))) match {
case (Success(ph), Success(pk)) => completeRpcFuture(req.id, (paymentInitiator ?
SendPayment(amountMsat.toLong, ph, pk)).mapTo[PaymentResult].map {
SendPayment(amountMsat.toLong, ph, pk, maxAttempts = appKit.nodeParams.maxPaymentAttempts)).mapTo[PaymentResult].map {
case s: PaymentSucceeded => s
case f: PaymentFailed => f.copy(failures = PaymentLifecycle.transformForUser(f.failures))
})
@ -285,8 +285,8 @@ trait OldService extends Logging {
logger.debug(s"api call for sending payment with amount_msat=$amount_msat")
// optional cltv expiry
val sendPayment = pr.minFinalCltvExpiry match {
case None => SendPayment(amount_msat, pr.paymentHash, pr.nodeId)
case Some(minFinalCltvExpiry) => SendPayment(amount_msat, pr.paymentHash, pr.nodeId, assistedRoutes = Nil, minFinalCltvExpiry)
case None => SendPayment(amount_msat, pr.paymentHash, pr.nodeId, maxAttempts = appKit.nodeParams.maxPaymentAttempts)
case Some(minFinalCltvExpiry) => SendPayment(amount_msat, pr.paymentHash, pr.nodeId, assistedRoutes = Nil, minFinalCltvExpiry, maxAttempts = appKit.nodeParams.maxPaymentAttempts)
}
completeRpcFuture(req.id, (paymentInitiator ? sendPayment).mapTo[PaymentResult].map {
case s: PaymentSucceeded => s

View File

@ -19,7 +19,7 @@ package fr.acinq.eclair.api
import akka.http.scaladsl.server._
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, MilliSatoshi, Satoshi}
import fr.acinq.eclair.{Eclair, Kit, ShortChannelId}
import fr.acinq.eclair.{Eclair, Kit, NodeParams, ShortChannelId}
import FormParamExtractors._
import akka.NotUsed
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
@ -213,17 +213,17 @@ trait Service extends Directives with Logging {
}
} ~
path("send") {
formFields("invoice".as[PaymentRequest], "amountMsat".as[Long].?) {
case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None) =>
complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry))
case (invoice, Some(overrideAmount)) =>
complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry))
formFields("invoice".as[PaymentRequest], "amountMsat".as[Long].?, "maxAttempts".as[Int].?) {
case (invoice@PaymentRequest(_, Some(amount), _, nodeId, _, _), None, maxAttempts) =>
complete(eclairApi.send(nodeId, amount.toLong, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts))
case (invoice, Some(overrideAmount), maxAttempts) =>
complete(eclairApi.send(invoice.nodeId, overrideAmount, invoice.paymentHash, invoice.routingInfo, invoice.minFinalCltvExpiry, maxAttempts))
case _ => reject(MalformedFormFieldRejection("invoice", "The invoice must have an amount or you need to specify one using the field 'amountMsat'"))
}
} ~
path("sendtonode") {
formFields("amountMsat".as[Long], "paymentHash".as[ByteVector32](sha256HashUnmarshaller), "nodeId".as[PublicKey]) { (amountMsat, paymentHash, nodeId) =>
complete(eclairApi.send(nodeId, amountMsat, paymentHash))
formFields("amountMsat".as[Long], "paymentHash".as[ByteVector32](sha256HashUnmarshaller), "nodeId".as[PublicKey], "maxAttempts".as[Int].?) { (amountMsat, paymentHash, nodeId, maxAttempts) =>
complete(eclairApi.send(nodeId, amountMsat, paymentHash, maxAttempts = maxAttempts))
}
} ~
path("checkpayment") {

View File

@ -181,15 +181,12 @@ object PaymentLifecycle {
// @formatter:off
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String, expirySeconds_opt: Option[Long] = None, extraHops: List[List[ExtraHop]] = Nil, fallbackAddress: Option[String] = None)
/**
* @param maxFeePct set by default to 3% as a safety measure (even if a route is found, if fee is higher than that payment won't be attempted)
*/
case class SendPayment(amountMsat: Long,
paymentHash: ByteVector32,
targetNodeId: PublicKey,
assistedRoutes: Seq[Seq[ExtraHop]] = Nil,
finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY,
maxAttempts: Int = 5,
maxAttempts: Int,
routeParams: Option[RouteParams] = None) {
require(amountMsat > 0, s"amountMsat must be > 0")
}

View File

@ -96,7 +96,8 @@ object TestConstants {
searchRatioChannelAge = 0.0,
searchRatioChannelCapacity = 0.0
),
socksProxy_opt = None
socksProxy_opt = None,
maxPaymentAttempts = 5
)
def channelParams = Peer.makeChannelParams(
@ -160,7 +161,8 @@ object TestConstants {
searchRatioChannelAge = 0.0,
searchRatioChannelCapacity = 0.0
),
socksProxy_opt = None
socksProxy_opt = None,
maxPaymentAttempts = 5
)
def channelParams = Peer.makeChannelParams(

View File

@ -71,7 +71,7 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest {
override def findRoute(targetNodeId: Crypto.PublicKey, amountMsat: Long, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]]): Future[RouteResponse] = ???
override def send(recipientNodeId: Crypto.PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry: Option[Long]): Future[PaymentLifecycle.PaymentResult] = ???
override def send(recipientNodeId: Crypto.PublicKey, amountMsat: Long, paymentHash: ByteVector32, assistedRoutes: Seq[Seq[PaymentRequest.ExtraHop]], minFinalCltvExpiry: Option[Long], maxAttempts: Option[Int] = None): Future[PaymentLifecycle.PaymentResult] = ???
override def checkpayment(paymentHash: ByteVector32): Future[Boolean] = ???

View File

@ -261,7 +261,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
val pr = sender.expectMsgType[PaymentRequest]
// then we make the actual payment
sender.send(nodes("A").paymentInitiator,
SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams))
SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 1))
sender.expectMsgType[PaymentSucceeded]
}
@ -283,7 +283,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
// then we make the actual payment, do not randomize the route to make sure we route through node B
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will receive an error from B that include the updated channel update, then will retry the payment
sender.expectMsgType[PaymentSucceeded](5 seconds)
@ -319,7 +319,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
// then we make the payment (B-C has a smaller capacity than A-B and C-D)
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an error from C, then retry and route around C: A->B->E->C->D
sender.expectMsgType[PaymentSucceeded](5 seconds)
@ -327,7 +327,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
test("send an HTLC A->D with an unknown payment hash") {
val sender = TestProbe()
val pr = SendPayment(100000000L, randomBytes32, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val pr = SendPayment(100000000L, randomBytes32, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, pr)
// A will receive an error from D and won't retry
@ -345,7 +345,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of only 1 mBTC
val sendReq = SendPayment(100000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(100000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an IncorrectPaymentAmount error from D
@ -363,7 +363,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of 6 mBTC
val sendReq = SendPayment(600000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(600000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
// A will first receive an IncorrectPaymentAmount error from D
@ -381,7 +381,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
val pr = sender.expectMsgType[PaymentRequest]
// A send payment of 3 mBTC, more than asked but it should still be accepted
val sendReq = SendPayment(300000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(300000000L, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
sender.expectMsgType[PaymentSucceeded]
}
@ -394,7 +394,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
sender.send(nodes("D").paymentHandler, ReceivePayment(Some(amountMsat), "1 payment"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(amountMsat.amount, pr.paymentHash, nodes("D").nodeParams.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 5)
sender.send(nodes("A").paymentInitiator, sendReq)
sender.expectMsgType[PaymentSucceeded]
}
@ -737,7 +737,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
val amountMsat = MilliSatoshi(300000000L)
sender.send(paymentHandlerF, ReceivePayment(Some(amountMsat), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPayment(300000000L, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(300000000L, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 1)
sender.send(nodes("A").paymentInitiator, sendReq)
// we forward the htlc to the payment handler
forwardHandlerF.expectMsgType[UpdateAddHtlc]
@ -750,7 +750,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
def send(amountMsat: Long, paymentHandler: ActorRef, paymentInitiator: ActorRef) = {
sender.send(paymentHandler, ReceivePayment(Some(MilliSatoshi(amountMsat)), "1 coffee"))
val pr = sender.expectMsgType[PaymentRequest]
val sendReq = SendPayment(amountMsat, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams)
val sendReq = SendPayment(amountMsat, pr.paymentHash, pr.nodeId, routeParams = integrationTestRouteParams, maxAttempts = 1)
sender.send(paymentInitiator, sendReq)
sender.expectNoMsg()
}

View File

@ -55,7 +55,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, f)
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, f, maxAttempts = 5)
sender.send(paymentFSM, request)
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
@ -71,7 +71,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, routeParams = Some(RouteParams(randomize = false, maxFeeBaseMsat = 100, maxFeePct = 0.0, routeMaxLength = 20, routeMaxCltv = 2016, ratios = None)))
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, routeParams = Some(RouteParams(randomize = false, maxFeeBaseMsat = 100, maxFeePct = 0.0, routeMaxLength = 20, routeMaxCltv = 2016, ratios = None)), maxAttempts = 5)
sender.send(paymentFSM, request)
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
@ -311,7 +311,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d)
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, d, maxAttempts = 5)
sender.send(paymentFSM, request)
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]])
@ -359,7 +359,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
// we send a payment to G which is just after the
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, g)
val request = SendPayment(defaultAmountMsat, defaultPaymentHash, g, maxAttempts = 5)
sender.send(paymentFSM, request)
// the route will be A -> B -> G where B -> G has a channel_update with fees=0

View File

@ -86,8 +86,8 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
(for {
kit <- fKit
sendPayment = req.minFinalCltvExpiry match {
case None => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo)
case Some(minFinalCltvExpiry) => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo, finalCltvExpiry = minFinalCltvExpiry)
case None => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo, maxAttempts = kit.nodeParams.maxPaymentAttempts)
case Some(minFinalCltvExpiry) => SendPayment(amountMsat, req.paymentHash, req.nodeId, req.routingInfo, finalCltvExpiry = minFinalCltvExpiry, maxAttempts = kit.nodeParams.maxPaymentAttempts)
}
res <- (kit.paymentInitiator ? sendPayment).mapTo[PaymentResult]
} yield res).recover {