@@ -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
@@ -51,14 +46,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
51
46
* @param request payment request containing the total amount to send.
52
47
* @param attemptNumber number of failed previous payment attempts.
53
48
* @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.
55
50
* @param failures previous payment failures.
56
51
*/
57
52
data class PaymentAttempt (
58
53
val request : PayInvoice ,
59
54
val attemptNumber : Int ,
60
55
val pending : LightningOutgoingPayment .Part ,
61
- val sharedSecrets : SharedSecrets ,
56
+ val outgoing : OutgoingPacket ,
62
57
val failures : List <Either <ChannelException , FailureMessage >>
63
58
) {
64
59
val fees: MilliSatoshi = pending.amount - request.amount
@@ -99,8 +94,8 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
99
94
}
100
95
is Either .Right -> {
101
96
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 ())
104
99
db.addOutgoingPayment(LightningOutgoingPayment (request.paymentId, request.amount, request.recipient, request.paymentDetails, listOf (childPayment), LightningOutgoingPayment .Status .Pending ))
105
100
pending[request.paymentId] = payment
106
101
Progress (request, payment.fees, listOf (cmd))
@@ -137,8 +132,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
137
132
return null
138
133
}
139
134
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
140
137
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)) {
142
139
is Try .Failure -> {
143
140
logger.warning { " could not decrypt failure packet: ${decrypted.error.message} " }
144
141
Either .Left (CannotDecryptFailure (channelId, decrypted.error.message ? : " unknown" ))
@@ -168,17 +165,25 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
168
165
val trampolineFees = payment.request.trampolineFeesOverride ? : walletParams.trampolineFees
169
166
val finalError = when {
170
167
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
173
171
else -> null
174
172
}
175
173
return if (finalError != null ) {
176
174
db.completeOutgoingPaymentOffchain(payment.request.paymentId, finalError)
177
175
removeFromState(payment.request.paymentId)
178
176
Failure (payment.request, OutgoingPaymentFailure (finalError, payment.failures + failure))
179
177
} 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
+ }
182
187
logger.info { " retrying payment with higher fees (base=${nextFees.feeBase} , proportional=${nextFees.feeProportional} )..." }
183
188
val trampolineAmount = payment.request.amount + nextFees.calculateFees(payment.request.amount)
184
189
when (val result = selectChannel(trampolineAmount, channels)) {
@@ -190,13 +195,13 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
190
195
}
191
196
is Either .Right -> {
192
197
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)
194
199
db.addOutgoingLightningParts(payment.request.paymentId, listOf (childPayment))
195
200
val payment1 = PaymentAttempt (
196
201
request = payment.request,
197
202
attemptNumber = payment.attemptNumber + 1 ,
198
203
pending = childPayment,
199
- sharedSecrets = sharedSecrets ,
204
+ outgoing = packet ,
200
205
failures = payment.failures + failure
201
206
)
202
207
pending[payment1.request.paymentId] = payment1
@@ -319,7 +324,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
319
324
}
320
325
}
321
326
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 > {
323
328
val logger = MDCLogger (logger, staticMdc = request.mdc())
324
329
val childId = UUID .randomUUID()
325
330
childToParentId[childId] = request.paymentId
@@ -332,10 +337,10 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
332
337
)
333
338
logger.info { " sending $amount to channel ${channel.shortChannelId} " }
334
339
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))
336
341
}
337
342
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 > {
339
344
return when (val paymentRequest = request.paymentDetails.paymentRequest) {
340
345
is Bolt11Invoice -> {
341
346
val minFinalExpiryDelta = paymentRequest.minFinalExpiryDelta ? : Channel .MIN_CLTV_EXPIRY_DELTA
0 commit comments