@@ -7,8 +7,6 @@ import fr.acinq.lightning.*
7
7
import fr.acinq.lightning.channel.*
8
8
import fr.acinq.lightning.channel.states.*
9
9
import fr.acinq.lightning.crypto.sphinx.FailurePacket
10
- import fr.acinq.lightning.crypto.sphinx.PacketAndSecrets
11
- import fr.acinq.lightning.crypto.sphinx.SharedSecrets
12
10
import fr.acinq.lightning.db.HopDesc
13
11
import fr.acinq.lightning.db.LightningOutgoingPayment
14
12
import fr.acinq.lightning.db.OutgoingPaymentsDb
@@ -20,10 +18,7 @@ import fr.acinq.lightning.logging.mdc
20
18
import fr.acinq.lightning.router.NodeHop
21
19
import fr.acinq.lightning.utils.UUID
22
20
import 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.*
27
22
28
23
class OutgoingPaymentHandler (val nodeParams : NodeParams , val walletParams : WalletParams , val db : OutgoingPaymentsDb ) {
29
24
@@ -53,14 +48,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
53
48
* @param request payment request containing the total amount to send.
54
49
* @param attemptNumber number of failed previous payment attempts.
55
50
* @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.
57
52
* @param failures previous payment failures.
58
53
*/
59
54
data class PaymentAttempt (
60
55
val request : PayInvoice ,
61
56
val attemptNumber : Int ,
62
57
val pending : LightningOutgoingPayment .Part ,
63
- val sharedSecrets : SharedSecrets ,
58
+ val outgoing : OutgoingPacket ,
64
59
val failures : List <Either <ChannelException , FailureMessage >>
65
60
) {
66
61
val fees: MilliSatoshi = pending.amount - request.amount
@@ -73,9 +68,18 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
73
68
74
69
private suspend fun sendPaymentInternal (request : PayInvoice , failures : List <Either <ChannelException , FailureMessage >>, channels : Map <ByteVector32 , ChannelState >, currentBlockHeight : Int , logger : MDCLogger ): Either <Failure , Progress > {
75
70
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)
79
83
return when (val result = selectChannel(trampolineAmount, channels)) {
80
84
is Either .Left -> {
81
85
logger.warning { " payment failed: ${result.value} " }
@@ -87,14 +91,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
87
91
Either .Left (Failure (request, OutgoingPaymentFailure (result.value, failures)))
88
92
}
89
93
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)
92
96
if (attemptNumber == 0 ) {
93
97
db.addOutgoingPayment(LightningOutgoingPayment (request.paymentId, request.amount, request.recipient, request.paymentDetails, listOf (childPayment), LightningOutgoingPayment .Status .Pending ))
94
98
} else {
95
99
db.addOutgoingLightningParts(request.paymentId, listOf (childPayment))
96
100
}
97
- val payment = PaymentAttempt (request, attemptNumber, childPayment, sharedSecrets , failures)
101
+ val payment = PaymentAttempt (request, attemptNumber, childPayment, packet , failures)
98
102
pending[request.paymentId] = payment
99
103
Either .Right (Progress (request, payment.fees, listOf (cmd)))
100
104
}
@@ -154,8 +158,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
154
158
return null
155
159
}
156
160
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
157
163
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)) {
159
165
is Try .Failure -> {
160
166
logger.warning { " could not decrypt failure packet: ${decrypted.error.message} " }
161
167
Either .Left (CannotDecryptFailure (channelId, decrypted.error.message ? : " unknown" ))
@@ -185,8 +191,9 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
185
191
val trampolineFees = payment.request.trampolineFeesOverride ? : walletParams.trampolineFees
186
192
val finalError = when {
187
193
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
190
197
else -> null
191
198
}
192
199
return if (finalError != null ) {
@@ -290,7 +297,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
290
297
}
291
298
}
292
299
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 > {
294
301
val logger = MDCLogger (logger, staticMdc = request.mdc())
295
302
val childId = UUID .randomUUID()
296
303
childToPaymentId[childId] = request.paymentId
@@ -303,10 +310,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
303
310
)
304
311
logger.info { " sending $amount to channel ${channel.shortChannelId} " }
305
312
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))
307
314
}
308
315
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 > {
310
317
return when (val paymentRequest = request.paymentDetails.paymentRequest) {
311
318
is Bolt11Invoice -> {
312
319
val minFinalExpiryDelta = paymentRequest.minFinalExpiryDelta ? : Channel .MIN_CLTV_EXPIRY_DELTA
0 commit comments