Skip to content

Commit be42d28

Browse files
committed
Update trampoline to match spec proposal
We update the trampoline feature to match the official specification from lightning/bolts#836. We remove support for the previous version of trampoline, which means that when paying nodes that use the experimental version, we will use the trampoline-to-non-trampoline flow instead. Similarly, when older nodes pay updated nodes, they won't understand the new trampoline feature bit and will use the trampoline-to-non-trampoline flow. We update the trampoline-to-non-trampoline flow to remove the unused trampoline payload in the onion, which saves some space. Note that we don't want to officially specify this scenario, as it leaks some data about the recipient to the trampoline node. We rather wait for nodes to either support trampoline or blinded paths, which fixes this issue.
1 parent 0f7796a commit be42d28

File tree

19 files changed

+313
-243
lines changed

19 files changed

+313
-243
lines changed

src/commonMain/kotlin/fr/acinq/lightning/Features.kt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ sealed class Feature {
147147
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Invoice)
148148
}
149149

150+
@Serializable
151+
object TrampolinePayment : Feature() {
152+
override val rfcName get() = "trampoline_routing"
153+
override val mandatory get() = 56
154+
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
155+
}
156+
150157
// The following features have not been standardised, hence the high feature bits to avoid conflicts.
151158

152159
/** This feature bit should be activated when a node accepts having their channel reserve set to 0. */
@@ -189,15 +196,6 @@ sealed class Feature {
189196
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node)
190197
}
191198

192-
// The version of trampoline enabled by this feature bit does not match the latest spec PR: once the spec is accepted,
193-
// we will introduce a new version of trampoline that will work in parallel to this one, until we can safely deprecate it.
194-
@Serializable
195-
object ExperimentalTrampolinePayment : Feature() {
196-
override val rfcName get() = "trampoline_payment_experimental"
197-
override val mandatory get() = 148
198-
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
199-
}
200-
201199
@Serializable
202200
object ExperimentalSplice : Feature() {
203201
override val rfcName get() = "splice_experimental"
@@ -288,7 +286,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
288286
Feature.Quiescence,
289287
Feature.ChannelType,
290288
Feature.PaymentMetadata,
291-
Feature.ExperimentalTrampolinePayment,
289+
Feature.TrampolinePayment,
292290
Feature.ZeroReserveChannels,
293291
Feature.WakeUpNotificationClient,
294292
Feature.WakeUpNotificationProvider,
@@ -327,7 +325,7 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se
327325
Feature.PaymentSecret to listOf(Feature.VariableLengthOnion),
328326
Feature.BasicMultiPartPayment to listOf(Feature.PaymentSecret),
329327
Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey),
330-
Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret),
328+
Feature.TrampolinePayment to listOf(Feature.BasicMultiPartPayment),
331329
Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice),
332330
Feature.FundingFeeCredit to listOf(Feature.OnTheFlyFunding)
333331
)

src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ data class NodeParams(
197197
Feature.Quiescence to FeatureSupport.Mandatory,
198198
Feature.ChannelType to FeatureSupport.Mandatory,
199199
Feature.PaymentMetadata to FeatureSupport.Optional,
200-
Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional,
200+
Feature.TrampolinePayment to FeatureSupport.Optional,
201201
Feature.ZeroReserveChannels to FeatureSupport.Optional,
202202
Feature.WakeUpNotificationClient to FeatureSupport.Optional,
203203
Feature.ChannelBackupClient to FeatureSupport.Optional,

src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
344344
if (request.recipient == walletParams.trampolineNode.id) {
345345
// We are directly paying our trampoline node.
346346
OutgoingPaymentPacket.buildPacketToTrampolinePeer(paymentRequest, request.amount, expiry)
347-
} else if (invoiceFeatures.hasFeature(Feature.ExperimentalTrampolinePayment)) {
347+
} else if (invoiceFeatures.hasFeature(Feature.TrampolinePayment)) {
348348
OutgoingPaymentPacket.buildPacketToTrampolineRecipient(paymentRequest, request.amount, expiry, hop)
349349
} else {
350350
OutgoingPaymentPacket.buildPacketToLegacyRecipient(paymentRequest, request.amount, expiry, hop)

src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentPacket.kt

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ object OutgoingPaymentPacket {
2626
return buildOnion(sessionKey, nodes, payloads, associatedData, payloadLength)
2727
}
2828

29-
private fun buildOnion(sessionKey: PrivateKey, nodes: List<PublicKey>, payloads: List<PaymentOnion.PerHopPayload>, associatedData: ByteVector32, payloadLength: Int? = null): PacketAndSecrets {
29+
fun buildOnion(sessionKey: PrivateKey, nodes: List<PublicKey>, payloads: List<PaymentOnion.PerHopPayload>, associatedData: ByteVector32, payloadLength: Int? = null): PacketAndSecrets {
3030
require(nodes.size == payloads.size)
3131
val payloadsBin = payloads.map { it.write() }
3232
val totalPayloadLength = payloadLength ?: payloadsBin.sumOf { it.size + Sphinx.MacLength }
@@ -43,12 +43,11 @@ object OutgoingPaymentPacket {
4343
* @param hop the trampoline hop from the trampoline node to the recipient.
4444
*/
4545
fun buildPacketToTrampolineRecipient(invoice: Bolt11Invoice, amount: MilliSatoshi, expiry: CltvExpiry, hop: NodeHop): Triple<MilliSatoshi, CltvExpiry, PacketAndSecrets> {
46-
require(invoice.features.hasFeature(Feature.ExperimentalTrampolinePayment)) { "invoice must support trampoline" }
46+
require(invoice.features.hasFeature(Feature.TrampolinePayment)) { "invoice must support trampoline" }
4747
val trampolineOnion = run {
4848
val finalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(amount, expiry, invoice.paymentSecret, invoice.paymentMetadata)
4949
val trampolinePayload = PaymentOnion.NodeRelayPayload.create(amount, expiry, hop.nextNodeId)
50-
// We may be paying an older version of lightning-kmp that only supports trampoline packets of size 400.
51-
buildOnion(listOf(hop.nodeId, hop.nextNodeId), listOf(trampolinePayload, finalPayload), invoice.paymentHash, payloadLength = 400)
50+
buildOnion(listOf(hop.nodeId, hop.nextNodeId), listOf(trampolinePayload, finalPayload), invoice.paymentHash)
5251
}
5352
val trampolineAmount = amount + hop.fee(amount)
5453
val trampolineExpiry = expiry + hop.cltvExpiryDelta
@@ -67,7 +66,7 @@ object OutgoingPaymentPacket {
6766
* @param expiry cltv expiry that should be received by the final recipient.
6867
*/
6968
fun buildPacketToTrampolinePeer(invoice: Bolt11Invoice, amount: MilliSatoshi, expiry: CltvExpiry): Triple<MilliSatoshi, CltvExpiry, PacketAndSecrets> {
70-
require(invoice.features.hasFeature(Feature.ExperimentalTrampolinePayment)) { "invoice must support trampoline" }
69+
require(invoice.features.hasFeature(Feature.TrampolinePayment)) { "invoice must support trampoline" }
7170
val trampolineOnion = run {
7271
val finalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(amount, expiry, invoice.paymentSecret, invoice.paymentMetadata)
7372
buildOnion(listOf(invoice.nodeId), listOf(finalPayload), invoice.paymentHash)
@@ -90,23 +89,20 @@ object OutgoingPaymentPacket {
9089
*/
9190
fun buildPacketToLegacyRecipient(invoice: Bolt11Invoice, amount: MilliSatoshi, expiry: CltvExpiry, hop: NodeHop): Triple<MilliSatoshi, CltvExpiry, PacketAndSecrets> {
9291
val trampolineOnion = run {
93-
// NB: the final payload will never reach the recipient, since the trampoline node will convert that to a legacy payment.
94-
// We use the smallest final payload possible, otherwise we may overflow the trampoline onion size.
95-
val dummyFinalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(amount, expiry, invoice.paymentSecret, null)
96-
var routingInfo = invoice.routingInfo
97-
var trampolinePayload = PaymentOnion.RelayToNonTrampolinePayload.create(amount, amount, expiry, hop.nextNodeId, invoice, routingInfo)
98-
var trampolineOnion = buildOnion(listOf(hop.nodeId, hop.nextNodeId), listOf(trampolinePayload, dummyFinalPayload), invoice.paymentHash)
99-
// Ensure that this onion can fit inside the outer 1300 bytes onion. The outer onion fields need ~150 bytes and we add some safety margin.
100-
while (trampolineOnion.packet.payload.size() > 1000) {
101-
routingInfo = routingInfo.dropLast(1)
102-
trampolinePayload = PaymentOnion.RelayToNonTrampolinePayload.create(amount, amount, expiry, hop.nextNodeId, invoice, routingInfo)
103-
trampolineOnion = buildOnion(listOf(hop.nodeId, hop.nextNodeId), listOf(trampolinePayload, dummyFinalPayload), invoice.paymentHash)
104-
}
105-
trampolineOnion
92+
// We create a trampoline onion detailing what to forward to the recipient.
93+
val trampolinePayload = PaymentOnion.NodeRelayPayload.create(amount, expiry, hop.nextNodeId)
94+
buildOnion(listOf(hop.nodeId), listOf(trampolinePayload), invoice.paymentHash)
10695
}
96+
// We wrap that trampoline onion in a payment onion that contains the invoice's routing hints.
97+
// We remove routing hints until they fit inside the payment onion.
10798
val trampolineAmount = amount + hop.fee(amount)
10899
val trampolineExpiry = expiry + hop.cltvExpiryDelta
109-
val payload = PaymentOnion.FinalPayload.Standard.createTrampolinePayload(trampolineAmount, trampolineAmount, trampolineExpiry, invoice.paymentSecret, trampolineOnion.packet)
100+
var routingInfo = invoice.routingInfo
101+
var payload = PaymentOnion.FinalPayload.Standard.createTrampolineToLegacyPayload(trampolineAmount, trampolineExpiry, invoice, routingInfo, trampolineOnion.packet)
102+
while (payload.write().size + Sphinx.MacLength > OnionRoutingPacket.PaymentPacketLength) {
103+
routingInfo = routingInfo.dropLast(1)
104+
payload = PaymentOnion.FinalPayload.Standard.createTrampolineToLegacyPayload(trampolineAmount, trampolineExpiry, invoice, routingInfo, trampolineOnion.packet)
105+
}
110106
val paymentOnion = buildOnion(listOf(hop.nodeId), listOf(payload), invoice.paymentHash, OnionRoutingPacket.PaymentPacketLength)
111107
return Triple(trampolineAmount, trampolineExpiry, paymentOnion)
112108
}

0 commit comments

Comments
 (0)