@@ -7,8 +7,6 @@ import fr.acinq.lightning.*
77import fr.acinq.lightning.channel.*
88import fr.acinq.lightning.channel.states.*
99import fr.acinq.lightning.crypto.sphinx.FailurePacket
10- import fr.acinq.lightning.crypto.sphinx.PacketAndSecrets
11- import fr.acinq.lightning.crypto.sphinx.SharedSecrets
1210import fr.acinq.lightning.db.HopDesc
1311import fr.acinq.lightning.db.LightningOutgoingPayment
1412import fr.acinq.lightning.db.OutgoingPaymentsDb
@@ -20,10 +18,7 @@ import fr.acinq.lightning.logging.mdc
2018import fr.acinq.lightning.router.NodeHop
2119import fr.acinq.lightning.utils.UUID
2220import fr.acinq.lightning.utils.msat
23- import fr.acinq.lightning.wire.FailureMessage
24- import fr.acinq.lightning.wire.TrampolineExpiryTooSoon
25- import fr.acinq.lightning.wire.TrampolineFeeInsufficient
26- import fr.acinq.lightning.wire.UnknownNextPeer
21+ import fr.acinq.lightning.wire.*
2722
2823class OutgoingPaymentHandler (val nodeParams : NodeParams , val walletParams : WalletParams , val db : OutgoingPaymentsDb ) {
2924
@@ -53,14 +48,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
5348 * @param request payment request containing the total amount to send.
5449 * @param attemptNumber number of failed previous payment attempts.
5550 * @param pending pending outgoing payment.
56- * @param sharedSecrets payment onion shared secrets, used to decrypt failures.
51+ * @param outgoing payment packet containing the shared secrets used to decrypt failures.
5752 * @param failures previous payment failures.
5853 */
5954 data class PaymentAttempt (
6055 val request : PayInvoice ,
6156 val attemptNumber : Int ,
6257 val pending : LightningOutgoingPayment .Part ,
63- val sharedSecrets : SharedSecrets ,
58+ val outgoing : OutgoingPacket ,
6459 val failures : List <Either <ChannelException , FailureMessage >>
6560 ) {
6661 val fees: MilliSatoshi = pending.amount - request.amount
@@ -73,9 +68,18 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
7368
7469 private suspend fun sendPaymentInternal (request : PayInvoice , failures : List <Either <ChannelException , FailureMessage >>, channels : Map <ByteVector32 , ChannelState >, currentBlockHeight : Int , logger : MDCLogger ): Either <Failure , Progress > {
7570 val attemptNumber = failures.size
76- val trampolineFees = (request.trampolineFeesOverride ? : walletParams.trampolineFees)[attemptNumber]
77- logger.info { " trying payment with fee_base=${trampolineFees.feeBase} , fee_proportional=${trampolineFees.feeProportional} " }
78- val trampolineAmount = request.amount + trampolineFees.calculateFees(request.amount)
71+ val trampolineFees = (request.trampolineFeesOverride ? : walletParams.trampolineFees)
72+ val nextFees = when (val f = failures.lastOrNull()?.right) {
73+ is TrampolineFeeOrExpiryInsufficient -> {
74+ // The trampoline node is asking us to retry the payment with more fees or a larger expiry delta.
75+ val requestedFee = Lightning .nodeFee(f.feeBase, f.feeProportionalMillionths.toLong(), request.amount)
76+ val nextFees = trampolineFees.drop(attemptNumber).firstOrNull { it.calculateFees(request.amount) >= requestedFee } ? : trampolineFees[attemptNumber]
77+ nextFees.copy(cltvExpiryDelta = maxOf(nextFees.cltvExpiryDelta, f.expiryDelta))
78+ }
79+ else -> trampolineFees[attemptNumber]
80+ }
81+ logger.info { " trying payment with fee_base=${nextFees.feeBase} , fee_proportional=${nextFees.feeProportional} " }
82+ val trampolineAmount = request.amount + nextFees.calculateFees(request.amount)
7983 return when (val result = selectChannel(trampolineAmount, channels)) {
8084 is Either .Left -> {
8185 logger.warning { " payment failed: ${result.value} " }
@@ -87,14 +91,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
8791 Either .Left (Failure (request, OutgoingPaymentFailure (result.value, failures)))
8892 }
8993 is Either .Right -> {
90- val hop = NodeHop (walletParams.trampolineNode.id, request.recipient, trampolineFees .cltvExpiryDelta, trampolineFees .calculateFees(request.amount))
91- val (childPayment, sharedSecrets , cmd) = createOutgoingPayment(request, result.value, hop, currentBlockHeight)
94+ val hop = NodeHop (walletParams.trampolineNode.id, request.recipient, nextFees .cltvExpiryDelta, nextFees .calculateFees(request.amount))
95+ val (childPayment, packet , cmd) = createOutgoingPayment(request, result.value, hop, currentBlockHeight)
9296 if (attemptNumber == 0 ) {
9397 db.addOutgoingPayment(LightningOutgoingPayment (request.paymentId, request.amount, request.recipient, request.paymentDetails, listOf (childPayment), LightningOutgoingPayment .Status .Pending ))
9498 } else {
9599 db.addOutgoingLightningParts(request.paymentId, listOf (childPayment))
96100 }
97- val payment = PaymentAttempt (request, attemptNumber, childPayment, sharedSecrets , failures)
101+ val payment = PaymentAttempt (request, attemptNumber, childPayment, packet , failures)
98102 pending[request.paymentId] = payment
99103 Either .Right (Progress (request, payment.fees, listOf (cmd)))
100104 }
@@ -154,8 +158,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
154158 return null
155159 }
156160
161+ // We try decrypting with the payment onion hops first, and then iterate over the trampoline hops if necessary.
162+ val sharedSecrets = payment.outgoing.outerSharedSecrets.perHopSecrets + payment.outgoing.innerSharedSecrets.perHopSecrets
157163 val failure = when (event.result) {
158- is ChannelAction .HtlcResult .Fail .RemoteFail -> when (val decrypted = FailurePacket .decrypt(event.result.fail.reason.toByteArray(), payment. sharedSecrets)) {
164+ is ChannelAction .HtlcResult .Fail .RemoteFail -> when (val decrypted = FailurePacket .decrypt(event.result.fail.reason.toByteArray(), sharedSecrets)) {
159165 is Try .Failure -> {
160166 logger.warning { " could not decrypt failure packet: ${decrypted.error.message} " }
161167 Either .Left (CannotDecryptFailure (channelId, decrypted.error.message ? : " unknown" ))
@@ -185,8 +191,9 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
185191 val trampolineFees = payment.request.trampolineFeesOverride ? : walletParams.trampolineFees
186192 val finalError = when {
187193 trampolineFees.size <= payment.attemptNumber + 1 -> FinalFailure .RetryExhausted
188- failure == Either .Right (UnknownNextPeer ) -> FinalFailure .RecipientUnreachable
189- failure != Either .Right (TrampolineExpiryTooSoon ) && failure != Either .Right (TrampolineFeeInsufficient ) -> FinalFailure .UnknownError // non-retriable error
194+ failure == Either .Right (UnknownNextPeer ) || failure == Either .Right (UnknownNextTrampoline ) -> FinalFailure .RecipientUnreachable
195+ failure.right is IncorrectOrUnknownPaymentDetails || failure.right is FinalIncorrectCltvExpiry || failure.right is FinalIncorrectHtlcAmount -> FinalFailure .RecipientRejectedPayment
196+ failure != Either .Right (TemporaryTrampolineFailure ) && failure.right !is TrampolineFeeOrExpiryInsufficient && failure != Either .Right (PaymentTimeout ) -> FinalFailure .UnknownError // non-retriable error
190197 else -> null
191198 }
192199 return if (finalError != null ) {
@@ -290,7 +297,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
290297 }
291298 }
292299
293- private fun createOutgoingPayment (request : PayInvoice , channel : Normal , hop : NodeHop , currentBlockHeight : Int ): Triple <LightningOutgoingPayment .Part , SharedSecrets , WrappedChannelCommand > {
300+ private fun createOutgoingPayment (request : PayInvoice , channel : Normal , hop : NodeHop , currentBlockHeight : Int ): Triple <LightningOutgoingPayment .Part , OutgoingPacket , WrappedChannelCommand > {
294301 val logger = MDCLogger (logger, staticMdc = request.mdc())
295302 val childId = UUID .randomUUID()
296303 childToPaymentId[childId] = request.paymentId
@@ -303,10 +310,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
303310 )
304311 logger.info { " sending $amount to channel ${channel.shortChannelId} " }
305312 val add = ChannelCommand .Htlc .Add (amount, request.paymentHash, expiry, onion.packet, paymentId = childId, commit = true )
306- return Triple (outgoingPayment, onion.sharedSecrets , WrappedChannelCommand (channel.channelId, add))
313+ return Triple (outgoingPayment, onion, WrappedChannelCommand (channel.channelId, add))
307314 }
308315
309- private fun createPaymentOnion (request : PayInvoice , hop : NodeHop , currentBlockHeight : Int ): Triple <MilliSatoshi , CltvExpiry , PacketAndSecrets > {
316+ private fun createPaymentOnion (request : PayInvoice , hop : NodeHop , currentBlockHeight : Int ): Triple <MilliSatoshi , CltvExpiry , OutgoingPacket > {
310317 return when (val paymentRequest = request.paymentDetails.paymentRequest) {
311318 is Bolt11Invoice -> {
312319 val minFinalExpiryDelta = paymentRequest.minFinalExpiryDelta ? : Channel .MIN_CLTV_EXPIRY_DELTA
0 commit comments