@@ -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
@@ -51,14 +46,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
5146 * @param request payment request containing the total amount to send.
5247 * @param attemptNumber number of failed previous payment attempts.
5348 * @param pending pending outgoing payment.
54- * @param sharedSecrets payment onion shared secrets, used to decrypt failures.
49+ * @param outgoing payment packet containing the shared secrets used to decrypt failures.
5550 * @param failures previous payment failures.
5651 */
5752 data class PaymentAttempt (
5853 val request : PayInvoice ,
5954 val attemptNumber : Int ,
6055 val pending : LightningOutgoingPayment .Part ,
61- val sharedSecrets : SharedSecrets ,
56+ val outgoing : OutgoingPacket ,
6257 val failures : List <Either <ChannelException , FailureMessage >>
6358 ) {
6459 val fees: MilliSatoshi = pending.amount - request.amount
@@ -99,8 +94,8 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
9994 }
10095 is Either .Right -> {
10196 val hop = NodeHop (walletParams.trampolineNode.id, request.recipient, trampolineFees.cltvExpiryDelta, trampolineFees.calculateFees(request.amount))
102- val (childPayment, sharedSecrets , cmd) = createOutgoingPayment(request, result.value, hop, currentBlockHeight)
103- val payment = PaymentAttempt (request, 0 , childPayment, sharedSecrets , listOf ())
97+ val (childPayment, packet , cmd) = createOutgoingPayment(request, result.value, hop, currentBlockHeight)
98+ val payment = PaymentAttempt (request, 0 , childPayment, packet , listOf ())
10499 db.addOutgoingPayment(LightningOutgoingPayment (request.paymentId, request.amount, request.recipient, request.paymentDetails, listOf (childPayment), LightningOutgoingPayment .Status .Pending ))
105100 pending[request.paymentId] = payment
106101 Progress (request, payment.fees, listOf (cmd))
@@ -137,8 +132,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
137132 return null
138133 }
139134
135+ // We try decrypting with the payment onion hops first, and then iterate over the trampoline hops if necessary.
136+ val sharedSecrets = payment.outgoing.outerSharedSecrets.perHopSecrets + payment.outgoing.innerSharedSecrets.perHopSecrets
140137 val failure = when (event.result) {
141- is ChannelAction .HtlcResult .Fail .RemoteFail -> when (val decrypted = FailurePacket .decrypt(event.result.fail.reason.toByteArray(), payment. sharedSecrets)) {
138+ is ChannelAction .HtlcResult .Fail .RemoteFail -> when (val decrypted = FailurePacket .decrypt(event.result.fail.reason.toByteArray(), sharedSecrets)) {
142139 is Try .Failure -> {
143140 logger.warning { " could not decrypt failure packet: ${decrypted.error.message} " }
144141 Either .Left (CannotDecryptFailure (channelId, decrypted.error.message ? : " unknown" ))
@@ -168,17 +165,25 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
168165 val trampolineFees = payment.request.trampolineFeesOverride ? : walletParams.trampolineFees
169166 val finalError = when {
170167 trampolineFees.size <= payment.attemptNumber + 1 -> FinalFailure .RetryExhausted
171- failure == Either .Right (UnknownNextPeer ) -> FinalFailure .RecipientUnreachable
172- failure != Either .Right (TrampolineExpiryTooSoon ) && failure != Either .Right (TrampolineFeeInsufficient ) -> FinalFailure .UnknownError // non-retriable error
168+ failure == Either .Right (UnknownNextPeer ) || failure == Either .Right (UnknownNextTrampoline ) -> FinalFailure .RecipientUnreachable
169+ failure.right is IncorrectOrUnknownPaymentDetails || failure.right is FinalIncorrectCltvExpiry || failure.right is FinalIncorrectHtlcAmount -> FinalFailure .RecipientRejectedPayment
170+ failure != Either .Right (TemporaryTrampolineFailure ) && failure.right !is TrampolineFeeOrExpiryInsufficient && failure != Either .Right (PaymentTimeout ) -> FinalFailure .UnknownError // non-retriable error
173171 else -> null
174172 }
175173 return if (finalError != null ) {
176174 db.completeOutgoingPaymentOffchain(payment.request.paymentId, finalError)
177175 removeFromState(payment.request.paymentId)
178176 Failure (payment.request, OutgoingPaymentFailure (finalError, payment.failures + failure))
179177 } else {
180- // The trampoline node is asking us to retry the payment with more fees or a larger expiry delta.
181- val nextFees = trampolineFees[payment.attemptNumber + 1 ]
178+ val nextFees = when (val f = failure.right) {
179+ is TrampolineFeeOrExpiryInsufficient -> {
180+ // The trampoline node is asking us to retry the payment with more fees or a larger expiry delta.
181+ val requestedFee = Lightning .nodeFee(f.feeBase, f.feeProportionalMillionths.toLong(), payment.request.amount)
182+ val nextFees = trampolineFees.drop(payment.attemptNumber + 1 ).firstOrNull { it.calculateFees(payment.request.amount) >= requestedFee } ? : trampolineFees[payment.attemptNumber + 1 ]
183+ nextFees.copy(cltvExpiryDelta = maxOf(nextFees.cltvExpiryDelta, f.expiryDelta))
184+ }
185+ else -> trampolineFees[payment.attemptNumber + 1 ]
186+ }
182187 logger.info { " retrying payment with higher fees (base=${nextFees.feeBase} , proportional=${nextFees.feeProportional} )..." }
183188 val trampolineAmount = payment.request.amount + nextFees.calculateFees(payment.request.amount)
184189 when (val result = selectChannel(trampolineAmount, channels)) {
@@ -190,13 +195,13 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
190195 }
191196 is Either .Right -> {
192197 val hop = NodeHop (walletParams.trampolineNode.id, payment.request.recipient, nextFees.cltvExpiryDelta, nextFees.calculateFees(payment.request.amount))
193- val (childPayment, sharedSecrets , cmd) = createOutgoingPayment(payment.request, result.value, hop, currentBlockHeight)
198+ val (childPayment, packet , cmd) = createOutgoingPayment(payment.request, result.value, hop, currentBlockHeight)
194199 db.addOutgoingLightningParts(payment.request.paymentId, listOf (childPayment))
195200 val payment1 = PaymentAttempt (
196201 request = payment.request,
197202 attemptNumber = payment.attemptNumber + 1 ,
198203 pending = childPayment,
199- sharedSecrets = sharedSecrets ,
204+ outgoing = packet ,
200205 failures = payment.failures + failure
201206 )
202207 pending[payment1.request.paymentId] = payment1
@@ -319,7 +324,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
319324 }
320325 }
321326
322- private fun createOutgoingPayment (request : PayInvoice , channel : Normal , hop : NodeHop , currentBlockHeight : Int ): Triple <LightningOutgoingPayment .Part , SharedSecrets , WrappedChannelCommand > {
327+ private fun createOutgoingPayment (request : PayInvoice , channel : Normal , hop : NodeHop , currentBlockHeight : Int ): Triple <LightningOutgoingPayment .Part , OutgoingPacket , WrappedChannelCommand > {
323328 val logger = MDCLogger (logger, staticMdc = request.mdc())
324329 val childId = UUID .randomUUID()
325330 childToParentId[childId] = request.paymentId
@@ -332,10 +337,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
332337 )
333338 logger.info { " sending $amount to channel ${channel.shortChannelId} " }
334339 val add = ChannelCommand .Htlc .Add (amount, request.paymentHash, expiry, onion.packet, paymentId = childId, commit = true )
335- return Triple (outgoingPayment, onion.sharedSecrets , WrappedChannelCommand (channel.channelId, add))
340+ return Triple (outgoingPayment, onion, WrappedChannelCommand (channel.channelId, add))
336341 }
337342
338- private fun createPaymentOnion (request : PayInvoice , hop : NodeHop , currentBlockHeight : Int ): Triple <MilliSatoshi , CltvExpiry , PacketAndSecrets > {
343+ private fun createPaymentOnion (request : PayInvoice , hop : NodeHop , currentBlockHeight : Int ): Triple <MilliSatoshi , CltvExpiry , OutgoingPacket > {
339344 return when (val paymentRequest = request.paymentDetails.paymentRequest) {
340345 is Bolt11Invoice -> {
341346 val minFinalExpiryDelta = paymentRequest.minFinalExpiryDelta ? : Channel .MIN_CLTV_EXPIRY_DELTA
0 commit comments