diff --git a/src/commonMain/kotlin/fr/acinq/lightning/Features.kt b/src/commonMain/kotlin/fr/acinq/lightning/Features.kt index bec099840..c747191ed 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/Features.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/Features.kt @@ -147,6 +147,13 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Invoice) } + @Serializable + object TrampolinePayment : Feature() { + override val rfcName get() = "trampoline_routing" + override val mandatory get() = 56 + override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice) + } + // The following features have not been standardised, hence the high feature bits to avoid conflicts. /** This feature bit should be activated when a node accepts having their channel reserve set to 0. */ @@ -189,15 +196,6 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node) } - // The version of trampoline enabled by this feature bit does not match the latest spec PR: once the spec is accepted, - // we will introduce a new version of trampoline that will work in parallel to this one, until we can safely deprecate it. - @Serializable - object ExperimentalTrampolinePayment : Feature() { - override val rfcName get() = "trampoline_payment_experimental" - override val mandatory get() = 148 - override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice) - } - @Serializable object ExperimentalSplice : Feature() { override val rfcName get() = "splice_experimental" @@ -288,7 +286,7 @@ data class Features(val activated: Map, val unknown: Se Feature.Quiescence, Feature.ChannelType, Feature.PaymentMetadata, - Feature.ExperimentalTrampolinePayment, + Feature.TrampolinePayment, Feature.ZeroReserveChannels, Feature.WakeUpNotificationClient, Feature.WakeUpNotificationProvider, @@ -327,7 +325,7 @@ data class Features(val activated: Map, val unknown: Se Feature.PaymentSecret to listOf(Feature.VariableLengthOnion), Feature.BasicMultiPartPayment to listOf(Feature.PaymentSecret), Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey), - Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret), + Feature.TrampolinePayment to listOf(Feature.BasicMultiPartPayment), Feature.OnTheFlyFunding to listOf(Feature.ExperimentalSplice), Feature.FundingFeeCredit to listOf(Feature.OnTheFlyFunding) ) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index e555b599f..02a49561f 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -197,7 +197,7 @@ data class NodeParams( Feature.Quiescence to FeatureSupport.Mandatory, Feature.ChannelType to FeatureSupport.Mandatory, Feature.PaymentMetadata to FeatureSupport.Optional, - Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional, + Feature.TrampolinePayment to FeatureSupport.Optional, Feature.ZeroReserveChannels to FeatureSupport.Optional, Feature.WakeUpNotificationClient to FeatureSupport.Optional, Feature.ChannelBackupClient to FeatureSupport.Optional, diff --git a/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt b/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt index b97eaa1d2..1aa9a474c 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandler.kt @@ -352,13 +352,14 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle is Bolt11Invoice -> { val minFinalExpiryDelta = paymentRequest.minFinalExpiryDelta ?: Channel.MIN_CLTV_EXPIRY_DELTA val finalExpiry = nodeParams.paymentRecipientExpiryParams.computeFinalExpiry(currentBlockHeight, minFinalExpiryDelta) - val finalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(request.amount, finalExpiry, paymentRequest.paymentSecret, paymentRequest.paymentMetadata) - val invoiceFeatures = paymentRequest.features - val (trampolineAmount, trampolineExpiry, trampolineOnion) = if (invoiceFeatures.hasFeature(Feature.ExperimentalTrampolinePayment)) { - // We may be paying an older version of lightning-kmp that only supports trampoline packets of size 400. - OutgoingPaymentPacket.buildPacket(request.paymentHash, trampolineRoute, finalPayload, 400) - } else { - OutgoingPaymentPacket.buildTrampolineToNonTrampolinePacket(paymentRequest, trampolineRoute, finalPayload) + val (trampolineAmount, trampolineExpiry, trampolineOnion) = when { + paymentRequest.features.hasFeature(Feature.TrampolinePayment) -> { + val finalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(request.amount, finalExpiry, paymentRequest.paymentSecret, paymentRequest.paymentMetadata) + OutgoingPaymentPacket.buildPacket(request.paymentHash, trampolineRoute, finalPayload, null) + } + else -> { + OutgoingPaymentPacket.buildTrampolineToNonTrampolinePacket(paymentRequest, trampolineRoute.last(), request.amount, finalExpiry) + } } return Triple(trampolineAmount, trampolineExpiry, trampolineOnion.packet) } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentPacket.kt b/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentPacket.kt index 21fd07d3f..564967404 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentPacket.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/payment/OutgoingPaymentPacket.kt @@ -56,32 +56,19 @@ object OutgoingPaymentPacket { /** * Build an encrypted trampoline onion packet when the final recipient doesn't support trampoline. - * The next-to-last trampoline node payload will contain instructions to convert to a legacy payment. + * The trampoline payload will contain instructions to convert to a legacy payment. + * This reveals to the trampoline node who the recipient is and details from the invoice. + * This must be deprecated once recipients support either trampoline or blinded paths. * - * @param invoice a Bolt11 invoice (features and routing hints will be provided to the next-to-last node). - * @param hops the trampoline hops (including ourselves in the first hop, and the non-trampoline final recipient in the last hop). - * @param finalPayload payload data for the final node (amount, expiry, etc) - * @return a (firstAmount, firstExpiry, onion) triple where: - * - firstAmount is the amount for the trampoline node in the route - * - firstExpiry is the cltv expiry for the first trampoline node in the route - * - the trampoline onion to include in final payload of a normal onion + * @param invoice a Bolt11 invoice (features and routing hints will be provided to the trampoline node). + * @param hop the trampoline hop from the trampoline node to the recipient. + * @param finalAmount amount that should be received by the final recipient. + * @param finalExpiry cltv expiry that should be received by the final recipient. */ - fun buildTrampolineToNonTrampolinePacket(invoice: Bolt11Invoice, hops: List, finalPayload: PaymentOnion.FinalPayload.Standard): Triple { - // NB: the final payload will never reach the recipient, since the next-to-last trampoline hop will convert that to a legacy payment - // We use the smallest final payload possible, otherwise we may overflow the trampoline onion size. - val dummyFinalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(finalPayload.amount, finalPayload.expiry, finalPayload.paymentSecret, null) - val (firstAmount, firstExpiry, payloads) = hops.drop(1).reversed().fold(Triple(finalPayload.amount, finalPayload.expiry, listOf(dummyFinalPayload))) { triple, hop -> - val (amount, expiry, payloads) = triple - val payload = when (payloads.size) { - // The next-to-last trampoline hop must include invoice data to indicate the conversion to a legacy payment. - 1 -> PaymentOnion.RelayToNonTrampolinePayload.create(finalPayload.amount, finalPayload.totalAmount, finalPayload.expiry, hop.nextNodeId, invoice) - else -> PaymentOnion.NodeRelayPayload.create(amount, expiry, hop.nextNodeId) - } - Triple(amount + hop.fee(amount), expiry + hop.cltvExpiryDelta, listOf(payload) + payloads) - } - val nodes = hops.map { it.nextNodeId } - val onion = buildOnion(nodes, payloads, invoice.paymentHash, payloadLength = null) - return Triple(firstAmount, firstExpiry, onion) + fun buildTrampolineToNonTrampolinePacket(invoice: Bolt11Invoice, hop: NodeHop, finalAmount: MilliSatoshi, finalExpiry: CltvExpiry): Triple { + val payload = PaymentOnion.RelayToNonTrampolinePayload.create(finalAmount, finalAmount, finalExpiry, hop.nextNodeId, invoice) + val onion = buildOnion(listOf(hop.nodeId), listOf(payload), invoice.paymentHash, payloadLength = null) + return Triple(finalAmount + hop.fee(finalAmount), finalExpiry + hop.cltvExpiryDelta, onion) } /** diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt index bde0d2bc6..1d8427fc0 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt @@ -101,6 +101,17 @@ sealed class OnionPaymentPayloadTlv : Tlv { } } + /** Id of the next node. */ + data class OutgoingNodeId(val nodeId: PublicKey) : OnionPaymentPayloadTlv() { + override val tag: Long get() = OutgoingNodeId.tag + override fun write(out: Output) = LightningCodecs.writeBytes(nodeId.value, out) + + companion object : TlvValueReader { + const val tag: Long = 14 + override fun read(input: Input): OutgoingNodeId = OutgoingNodeId(PublicKey(LightningCodecs.bytes(input, 33))) + } + } + /** * When payment metadata is included in a Bolt 9 invoice, we should send it as-is to the recipient. * This lets recipients generate invoices without having to store anything on their side until the invoice is paid. @@ -128,6 +139,20 @@ sealed class OnionPaymentPayloadTlv : Tlv { } } + /** An encrypted trampoline onion packet. */ + data class TrampolineOnion(val packet: OnionRoutingPacket) : OnionPaymentPayloadTlv() { + override val tag: Long get() = TrampolineOnion.tag + override fun write(out: Output) = OnionRoutingPacketSerializer(packet.payload.size()).write(packet, out) + + companion object : TlvValueReader { + const val tag: Long = 20 + override fun read(input: Input): TrampolineOnion { + val payloadLength = input.availableBytes - 66 // 1 byte version + 33 bytes public key + 32 bytes HMAC + return TrampolineOnion(OnionRoutingPacketSerializer(payloadLength).read(input)) + } + } + } + /** * Invoice feature bits. Only included for intermediate trampoline nodes when they should convert to a legacy payment * because the final recipient doesn't support trampoline. @@ -142,17 +167,6 @@ sealed class OnionPaymentPayloadTlv : Tlv { } } - /** Id of the next node. */ - data class OutgoingNodeId(val nodeId: PublicKey) : OnionPaymentPayloadTlv() { - override val tag: Long get() = OutgoingNodeId.tag - override fun write(out: Output) = LightningCodecs.writeBytes(nodeId.value, out) - - companion object : TlvValueReader { - const val tag: Long = 66098 - override fun read(input: Input): OutgoingNodeId = OutgoingNodeId(PublicKey(LightningCodecs.bytes(input, 33))) - } - } - /** * Invoice routing hints. Only included for intermediate trampoline nodes when they should convert to a legacy payment * because the final recipient doesn't support trampoline. @@ -194,20 +208,6 @@ sealed class OnionPaymentPayloadTlv : Tlv { } } - /** An encrypted trampoline onion packet. */ - data class TrampolineOnion(val packet: OnionRoutingPacket) : OnionPaymentPayloadTlv() { - override val tag: Long get() = TrampolineOnion.tag - override fun write(out: Output) = OnionRoutingPacketSerializer(packet.payload.size()).write(packet, out) - - companion object : TlvValueReader { - const val tag: Long = 66100 - override fun read(input: Input): TrampolineOnion { - val payloadLength = input.availableBytes - 66 // 1 byte version + 33 bytes public key + 32 bytes HMAC - return TrampolineOnion(OnionRoutingPacketSerializer(payloadLength).read(input)) - } - } - } - /** Blinded paths to relay the payment to */ data class OutgoingBlindedPaths(val paths: List) : OnionPaymentPayloadTlv() { override val tag: Long get() = OutgoingBlindedPaths.tag @@ -252,15 +252,15 @@ object PaymentOnion { OnionPaymentPayloadTlv.AmountToForward.tag to OnionPaymentPayloadTlv.AmountToForward.Companion as TlvValueReader, OnionPaymentPayloadTlv.OutgoingCltv.tag to OnionPaymentPayloadTlv.OutgoingCltv.Companion as TlvValueReader, OnionPaymentPayloadTlv.OutgoingChannelId.tag to OnionPaymentPayloadTlv.OutgoingChannelId.Companion as TlvValueReader, + OnionPaymentPayloadTlv.OutgoingNodeId.tag to OnionPaymentPayloadTlv.OutgoingNodeId.Companion as TlvValueReader, OnionPaymentPayloadTlv.PaymentData.tag to OnionPaymentPayloadTlv.PaymentData.Companion as TlvValueReader, OnionPaymentPayloadTlv.EncryptedRecipientData.tag to OnionPaymentPayloadTlv.EncryptedRecipientData.Companion as TlvValueReader, OnionPaymentPayloadTlv.BlindingPoint.tag to OnionPaymentPayloadTlv.BlindingPoint.Companion as TlvValueReader, OnionPaymentPayloadTlv.PaymentMetadata.tag to OnionPaymentPayloadTlv.PaymentMetadata.Companion as TlvValueReader, OnionPaymentPayloadTlv.TotalAmount.tag to OnionPaymentPayloadTlv.TotalAmount.Companion as TlvValueReader, + OnionPaymentPayloadTlv.TrampolineOnion.tag to OnionPaymentPayloadTlv.TrampolineOnion.Companion as TlvValueReader, OnionPaymentPayloadTlv.InvoiceFeatures.tag to OnionPaymentPayloadTlv.InvoiceFeatures.Companion as TlvValueReader, - OnionPaymentPayloadTlv.OutgoingNodeId.tag to OnionPaymentPayloadTlv.OutgoingNodeId.Companion as TlvValueReader, OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader, - OnionPaymentPayloadTlv.TrampolineOnion.tag to OnionPaymentPayloadTlv.TrampolineOnion.Companion as TlvValueReader, OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag to OnionPaymentPayloadTlv.OutgoingBlindedPaths.Companion as TlvValueReader, ) ) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/crypto/sphinx/SphinxTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/crypto/sphinx/SphinxTestsCommon.kt index 188196f57..f57f54146 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/crypto/sphinx/SphinxTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/crypto/sphinx/SphinxTestsCommon.kt @@ -14,10 +14,7 @@ import fr.acinq.lightning.tests.utils.LightningTestSuite import fr.acinq.lightning.utils.msat import fr.acinq.lightning.wire.* import fr.acinq.secp256k1.Hex -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.assertTrue +import kotlin.test.* class SphinxTestsCommon : LightningTestSuite() { private val privKeys = listOf( @@ -208,6 +205,74 @@ class SphinxTestsCommon : LightningTestSuite() { assertEquals(packets[4].hmac, ByteVector32("0000000000000000000000000000000000000000000000000000000000000000")) } + // See bolt04/trampoline-payment-onion-test.json + @Test + fun `create payment packet -- trampoline -- reference test vector`() { + // Alice generates a trampoline onion to Eve via Carol. + val carol = privKeys[2] + val eve = privKeys[4] + val paymentHash = ByteVector32.fromValidHex("4242424242424242424242424242424242424242424242424242424242424242") + val trampolinePayloads = listOf( + Hex.decode("2e 020405f5e100 04030c3500 0e2102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"), + Hex.decode("31 020405f5e100 04030c3500 08242a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a05f5e100"), + ) + val trampolineSessionKey = PrivateKey.fromHex("0303030303030303030303030303030303030303030303030303030303030303") + val trampolineOnionLength = trampolinePayloads.sumOf { it.size + Sphinx.MacLength } + val trampolineOnion = Sphinx.create(trampolineSessionKey, listOf(carol.publicKey(), eve.publicKey()), trampolinePayloads, paymentHash, trampolineOnionLength).packet + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(trampolineOnionLength).write(trampolineOnion)), + "0002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe3371860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623", + ) + + // Alice generates a payment onion to Carol via Bob. + val bob = privKeys[1] + val paymentPayloads = listOf( + Hex.decode("15 020405f5f488 04030c35fa 060808bbaa0000070451"), + Hex.decode("fd0116 020405f5f488 04030c35fa 08242b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b05f5f488 14e30002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe3371860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623"), + ) + val paymentSessionKey = PrivateKey.fromHex("0404040404040404040404040404040404040404040404040404040404040404") + val paymentOnion = Sphinx.create(paymentSessionKey, listOf(bob.publicKey(), carol.publicKey()), paymentPayloads, paymentHash, OnionRoutingPacket.PaymentPacketLength).packet + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(OnionRoutingPacket.PaymentPacketLength).write(paymentOnion)), + "0003462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b9149ce01cce1709194109ab594037113e897ab6120025c770527dd8537997e2528082b984fe078a5667978a573abeaf7977d9b8b6ee4f124d3352f7eea52cc66c0e76b8f6d7a25d4501a04ae190b17baff8e6378b36f165815f714559dfef275278eba897f5f229be70fc8a1980cf859d1c25fe90c77f006419770e19d29ba80be8f613d039dd05600734e0d1e218af441fe30877e717a26b7b37c2c071d62bf6d61dd17f7abfb81546d2c722c9a6dc581aa97fb6f3b513e5fbaf0d669fbf0714b2b016a0a8e356d55f267fa144f7501792f2a59269c5a22e555a914e2eb71eba5af519564f246cf58983ea3fa2674e3ab7d9969d8dffbb2bda2b2752657417937d46601eb8ebf1837221d4bdf55a4d6a97ecffde5a09bd409717fa19e440e55d775890aed89f72e65af515757e94a9b501e6bad048af55e1583adb2960a84f60fb5efd0352e77a34045fc6b221498f62810bd8294d995d9f513696f8633f29faaa9668d0c6fa0d0dd7fa13e2c185572485762bd2810dc12187f521fbefa9c320762ac1e107f7988d81c6ee201ab68a95d45d578027e271b6526154317877037dca17134ccd955a22a8481b8e1996d896fc4bf006154ed18ef279d4f255e3f1233d037aea2560011069a0ae56d6bfdd8327054ded12d85d120b8982bff970986db333baae7c95f85677726a8f74cc8bd1e5aca3d433c113048305ecce8e35caf0485a53df00284b52b42291a9ffe686b96442135b3107d8856bc652d674ee9a148e79b02c9972d3ca8c2b02609f3b358c4a67c540ba6769c4d83169bceda640b1d18b74d12b6df605b417dacf6f82d79d43bb40920898f818dc8344c036ae9c8bbf9ef52ea1ccf225c8825a4d8503df190b999e15a4be34c9d7bbf60d3b93bb7d6559f4a5916f5e40c3defeeca9337ccd1280e46d6727c5c91c2d898b685543d4ca7cfee23981323c43260b6387e7febb0fffb200a8c974ef36b3253d0fb9fe0c1c6017f2dbbdc169f3f061d9781521e8118164aeec31c3e59c199016f1025c295d8f7bdeb627d357105a2708c4c3a856b9e83ff37ed69f59f2d2e464ed1db5882925ebe2493a7ddb707e1a308fa445172a24b3ea60732f75f5c69b41fc11467ee93f37c9a6f7285ba42f716e2a0e30909056ea3e4f7985d14ca9ab280cc184ce98e2a0722d0447aa1a2eedc5e53ddfa53731df7eced406b10627b0bebd768a30bde0d470c0f1d10adc070f8d3029cacceec74e4833f4dc8c52c3f41733f5f896fceb425d0737e717a63bfb033df46286d99594dd01e2bd0a942ab792874177b32842f4833bc0340ddb74852e9cd6f29f1d997a4a4bf05dd5d12011f95e6ce18928e3a9b83b24d15f989bdf43370bcc657c3ac6601eaf5e951efdbd7ee69b1623dc5039b2dfc640692378ef032f17bc36cc00293ad90b7e18f5feb8f287a7061ed9713929aed9b14b8d566199fc7822b1c38daa16b6d83077b10af0e2b6e531ccc34ea248ea593128c9ff17defcee6618c29cd2d93cfed99b90319104b1fdcfea91e98b41d792782840fb7b25280d8565b0bcd874e79b1b323139e7fc88eb5f80f690ce30fcd81111076adb31de6aeced879b538c0b5f2b74c027cc582a540133952cb021424510312f13e15d403f700f3e15b41d677c5a1e7c4e692c5880cb4522c48e993381996a29615d2956781509cd74aec6a3c73b8536d1817e473dad4cbb1787e046606b692a44e5d21ef6b5219658b002f674367e90a2b610924e9ac543362257d4567728f2e61f61231cb5d7816e100bb6f6bd9a42329b728b18d7a696711650c16fd476e2f471f38af0f6b00d45c6e1fa492cc7962814953ab6ad1ce3d3f3dc950e64d18a8fdce6aabc14321576f06" + ) + val decryptedBob = Sphinx.peel(bob, paymentHash, paymentOnion).right!! + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(OnionRoutingPacket.PaymentPacketLength).write(decryptedBob.nextPacket)), + "00036d7fa1507cac3a1fda3308b465b71d817a2ee8dfde598c6bdb1dec73c7acf0165136fdd0fd9f3f7eac074f42b015825614214ac3b7ec95234538c9cfd04fc1a5128fa47c8d56e21e51bb843da8252c0abafb72395cf6ca8a186bd1de72341cb0f988e79988c39e4d444a4495120ccf3577576177a45c2a0fdc88776291d3af9e62d700c06206c769260859715ba5e1e7c0dc5f97dbf80decb564c885d0d6f0e10bddb225ee3d82a1e02b6a3735ea81ab91dada382a5752a940814e38c709e62d3427d69bfd09a19955c507aea300bf10578e3bda3d632a5de159f3fc0ff9311b2fc5d4a6c03582c4cd85c92d29bc285971f1019cb468942a7d3706e096f6ab105e7d8d525586a4f7987135af70d166317dc2b5b6c58345c54e87615d277e7ade5f0b9f8baed5f16e1b340492c4fa6b443f94544a4f083e4dfb778badf1084c0c39e998cd67ff5f1a6526fb163cfd48e04ff34d928a91f061781463b9f668a0d084e6c5bb80413968ee3185abd545b38f63f496d9fa16e67d84c08414df8c03d0efb1925edcdd14a4134424f65372166be4a8e66906a428eb726ae43ea6cf81256f082382e18b765e78cd21819045b5bcc5f4464b812215f8838acf73c5a4748a09ee10b6bcec9c201dc38ef009b23b9072d653c81316a59b36533732f4c4eeb29863bcf420155aa90378a111f0393599fb9dd42f69808c3552654b7352a6a1e2a71db0a0214c8d9021ef52d667da4d351a9a44a0cdbff34894d1994e7cced665061b6979f9e508d98ac9b2193f01694597e8189122daf0bd3c82743f5994678b4efb309028be23987bc18720388bc78be39b02276a0f3577390e36a5cd0dbab97b08a5c7f45a5a952681a2669e653004977b2a1e098a5bfee2ee927c2f51fc9dc66af120b5a40b01738c5db1e091f7141096e3c4d5905a695f02c852fd40412c7288c15befb522eec41232899863c17f66cbfefb3597c346fc7483a03d0f3f2dcaa6ae56d508d0df9298d80b2bcfcb91b30b298ca2415a3cbc8284bb2f4a5cfc244efe2d78a446d36d350bebd7ff30d70a2015679f1a3a63d841e2333fa30ebabf9d84576616f3c93a78a42948d991e1c628c3dbb3ad856fe97f9a3ce3d2b7e8e3ff2e1c3b2eb494dd9c947878120a8912afda70ca7d7829b9011f13c848d10e69274f4bd918c4c5531c8382e5f1c0b72ecabbd34d14190cde1a3247e4473c8016a122077f4a9cddf21c11680c2c25c342dacc7676304dd2466b47a172641e33de3cf9c2f476f57e0a90cdb7f8398dc012fd65df9a685a73b8f6f02a2ba3045e0cb308a72645370c827ac43da67f614e2d68b7811805b8144e6f21e94b679003486aa79bad22db09735d72e5a32c5831c3e44c9100322ae70c74df52ba98653624361b62d9500b704450e6e23d3373aae9697fed5e6133d1d1677608be513344590fd72569c6e19c070d303e8aa6f7196e7ac0f4039912cf1e9f050c6927340f9a96de229adbe7906072bc87c2214dc476d8dc7d81f2cb56d5a7407fe9fb378703f04fe19f1bb4b8f938c84072a4ac0b18de581b4b8b5971ce411cc82a0484764a6df49f8ffc3c858a299b4ffc9f96b933bd12f4fc876b6ce34f7c022ded91d51a03c5f14c29a9f7b28e45395782d74a3d795ac596b44ba36f805d62e3ba7976f10904784af7f5994cc57817979a0adf87e3b3e32047f0a4d68c2609c9405612b264094c49dd27836f4bdab4d68256b2b4d8e10411ff166065265fdd0c04c6c3ad989530f258b9549128765f0cc6af5e50cf15d3fd856e91580bf66a7ebce267726aee798b580df6deaee59fa90c5a35e06f36d4960c326d0418adcbe3ff4248bf04dc24a3758de2c58f97fd9e4333beae43428d184e3872ad52d2b4dd4d770da0dca339bf70a6b22dd05cf8547ec0a7a8d49543" + ) + val decryptedCarol = Sphinx.peel(carol, paymentHash, decryptedBob.nextPacket).right!! + assertTrue(decryptedCarol.isLastPacket) + val decryptedTrampolineCarol = Sphinx.peel(carol, paymentHash, trampolineOnion).right!! + assertFalse(decryptedTrampolineCarol.isLastPacket) + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(trampolineOnionLength).write(decryptedTrampolineCarol.nextPacket)), + "00035e5c85814fdb522b4efeef99b44fe8a5d3d3412057bc213b98d6f605edb022c2ae4a9141f6ac403790afeed975061f024e2723d485f9cb35a3eaf881732f468dc19009bf195b561590798fb895b7b7065b5537018dec330e509e8618700c9c6e1df5d15b900ac3c34104b6abb1099fd2eca3b640d7d5fda9370e20c09035168fc64d954baa80361b965314c400da2d7a64d0536bf9e494aebb80aec358327a4a1a667fcff1daf241c99dd8c4fa907de5b931fb9daed083c157f5ea1dd960d142952f8ebe4e1ccaee4d565a093e2b91f94b04a884ce2e8c60aced3565e8d2d10de5" + ) + + // Carol relays the payment to Eve via Dave. + val dave = privKeys[3] + val relayPayloads = listOf( + Hex.decode("15 020405f5e100 04030c3500 060808bbaa00002a06c1"), + Hex.decode("fd0116 020405f5e100 04030c3500 08242c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c05f5e100 14e300035e5c85814fdb522b4efeef99b44fe8a5d3d3412057bc213b98d6f605edb022c2ae4a9141f6ac403790afeed975061f024e2723d485f9cb35a3eaf881732f468dc19009bf195b561590798fb895b7b7065b5537018dec330e509e8618700c9c6e1df5d15b900ac3c34104b6abb1099fd2eca3b640d7d5fda9370e20c09035168fc64d954baa80361b965314c400da2d7a64d0536bf9e494aebb80aec358327a4a1a667fcff1daf241c99dd8c4fa907de5b931fb9daed083c157f5ea1dd960d142952f8ebe4e1ccaee4d565a093e2b91f94b04a884ce2e8c60aced3565e8d2d10de5"), + ) + val relaySessionKey = PrivateKey.fromHex("0505050505050505050505050505050505050505050505050505050505050505") + val relayOnion = Sphinx.create(relaySessionKey, listOf(dave.publicKey(), eve.publicKey()), relayPayloads, paymentHash, OnionRoutingPacket.PaymentPacketLength).packet + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(OnionRoutingPacket.PaymentPacketLength).write(relayOnion)), + "000362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f74125c590a7877d17303ddcdce79b0b33e006eaf4eff557631c70c7ab9a61105ffd738239016e84c2639ff5246f3d2167ea7ea7932138435a9cc427f7f9d838daa2b7f4c3bfb8c44e8e48d2fd744c1c5a7626d188b5690d36900eb0a498cd0b4139424bc1b65d74409a72fca8e36f239f4c80644963e80391ca1c707f727e3dc9656de66bfdf77823b0b5746c55c31978faffd65937b2c526478e4f30d08cc371fb9d045f65316af2d416c9a82ac412db84e4386901877670c8a2fcdd1b2f3276c5384f2feb23d4c62788cce78edc1194bf4fbd2af5670d2917cc940c41897fea944ebf908a1a90a1bd208b42209ccf2d480d2590bfce320ce185f12e77703f906e98b1a9ff701490b792a60faba11d75d691c2cecf867bb63062ec8c3bd1c2665dbd380e59cdffbfd028e5c86a1371fd3d5141e50986247a9f21143df0d1099e6df7e2044f1f4a87bc759cb7c2354616e39ace2d06165a580206ae9c5bc5005a6654215e7ab1bb619eb2df5bc11e3a8cfbc0a9c7e515c0f6d9d02512ef856d4782e54192ea63a173b4fcf02a11e85d2da6de47a6f8dd9bbfb30dccecd5e2195d5c9b0bf0bfc8b571b4962deacba4669afa017294b45e2668ad87168b9589f00f56275022f049f0cdece8c9e1f0f35035aa1af4a70103a7e8aa2b7a6579accf554c6a4f305981f5732036894765e086c167f5f342f313e4617da53b79303c72e0a6f03c3f592cb9c035c509c02dc09e5ea20b158a3f47b1722db86d354f7dfccbdaf6be21c7f473e143b459b2b06a21984f29ba80dfcd52696c76fb2a11f66383e33d88226f451317125fcfa02671015c359db52ee1462b1b820588d5c874765de4e7cc83b84dde8630b2a21325116cf53fd1eb369bed1330dfcbe0633698c518a376312624d78011922621e32e9b316a9329c3d1f967069d35844e60caf53e7a2bbbe695808de2e91dc16a9dd408ab2a8c363f2a5c34124f9c79010db4706e1315e1ff230741a9ab7e069318db587004bd0ccb71aad37c616b276bc0fe883865ba730b4d86ce2ae710185747d0860e00bb37b97fe71d79492a2e9a3bc07e5693f92de886fab3802ac62a8a4adcbf041eec05152cd28fd77154799e99674c8ea571519186ad9eb84a26edcef86473621e05515f3278810f931c662d037d9da73612c2f9d7d64e573595c402e9166299cbe356119ca38a3c6da77d6f864d61062b4300e388b631f60c25cb364b76561b4064c13e9e25d1ecb491472047157ea04fbbf6ccfe36cb2c030250b0335ae00255cf3670a61a5f207d72fccaac0b36a74d041f62341bc3759cd17d6e1c81aafcbbdc0f29906e54bc66dc1217031f881c9782eabe09de6835cdf4426113fb28e3bc0a73b007521c9a5abdc4a602c3c3358f0d3d81c8d84da5cc8acf1d15c9dd038ca64229097c666099a701b47bcf3a35e2541d4554a7bc1e3d4693b031c35f33b063d339558911870dd8bc3a52895612bee20ea8a7b0110da64362a357a4f9dbd7ff01155278c1173c57dd3d1b0947e58b571673544dbeff1c19cdb0ab9901671b3d043c4173fbcdf8e9cb03585bb9987414080046b6f283fc7c3aa245152941138636cd1b201e59080b8a7257bc2d7046c18d738c64804b088ac0983fbaeb92624f3ddc175afa3afc85cc8d83815bea41a195e883a4044a6406dbcb67682fc0522d2c920bc1d2372e95ea31408fcbe53e91c787e6da85255c40d0c9dbb0d4a5ded5886c90664bec4396f94782851fcd8565562a9df646025ad224078add8a05b8614ad0ce33141213a4650590ebaef22ef10b9cca5c4025eeaf58796baf052824d239586d7c706431fa1240f36a4f882d36ca608ece021b803386356f13a22bf3f42ef39d" + ) + val decryptedDave = Sphinx.peel(dave, paymentHash, relayOnion).right!! + assertEquals( + Hex.encode(OnionRoutingPacketSerializer(OnionRoutingPacket.PaymentPacketLength).write(decryptedDave.nextPacket)), + "00037d517980f2321ce95c8ecea4aebceb2f62ebbbac598973439c79e9f66d28ce5c728d3226c796b85df07009baec8b4e46d73bf6bbf6f8dfa8bcf610bda5de6ebaf395b5a8572e30e91e402688834a13db55d04c28dc1bfdcc07c602532330ee6ce1bd6acce875c81fd53b8f7f4243ed940bb5e4897252763968a00c2b59d6cbfdf73bd86e4b9135a63dcf99612da557962da6b525c68b3159e4f56cd49cf654ca6240ec5a0c2365731266eb4263e16c90aed2fbd662de9aa22ce7bf8af18687d99550e48477c6c46f8a84957d24ac323381e69b57342d82e06082c645fcd96eb77ed4f563f04e7e7913e4bac16a78e56d223baead194b4cd80c97fa7d892d0288780ac90f1020e0cb43e267721bbbdd6fb759da9df2744882f4259a4e5bab60aaca2847311122c1d60a483c978d7b3042ae189892f85e1e7e3ad89d48769404b5dea1ddf1794b3c6b002286995e976b1de9c2457895a00952a06983986a619863e4c60f17e40a210e89273fa7f55ebd83887d451b5e658b9092e81540de49a4e4a05a757aa103ca5dc63194094869f067d5cc36e2d59de9d038f3b5a4b6fa4fd5b276db7b77182ddc96eaf53bfbfa2f988b785643047a5639965dde3baafeac2db4efbdf04da3520766c012c988d64c9e3aa2f723baa4926e413b18b93bdeec4e0761ef55bedea1de8751b49cb8a67a15ddeae511e06f03d36c2158aba897997c53a2f12e2db98214b093c0ee3c5fe6763dc7a3418db28a571a88da50a851eac78f78c29c489d6a09751976f4e456ffa23b71b3894e9263476d490e842de6a41fd085bf218691b1de3b4cf077f560fc86dd7f8d24c06912e5b9d53fc7b36d3f5bcde6cb9f22d5db09c0ec4e870466d0549f5fcd0e6849aa925f3f238b1a613c022ea22dc308899330113b60576b7fc8904233a77cf24ad2f9482cdc1265f6e74353d92d4fbff4a0a42dfebb92ac71c7fc2c79ccd1b187bd4542ed2d1808735179bebaba664f49a75d2823f7e7041e1cc0f717899b7eb2c2b9550be185f1a0b2245a48fdc205c5339742ad14e370193158997f4d4edff05297a4668705f667b2a858a0b8af56aa4b93fb41b30e16a50a75fdc0ce33dd94da254d8b1e55c40aa49444aacf4796a6979f0feca13924ff3a886d3e859e51d2d585ee919abcc82c396da1df7a4e97f415a01c25c1a5ae2fe65f4cc385e16d91e54836e12e7588d89ee41dfef277b97eb7d6c6ebfc6ed4f89b13d776904fad6e405123bca86068dc558dbbc284c65947f295b8828e7e35e80fd0981ba46229d47e646afa73f55070ae4a202b8c46719e6449632a4377eedbe83a69d1f422e73bc159172e631165dc5fe63e09dcace38218de2598204127255535d6c2197003383195af636cfd8615bd8a5db96057fe4ee67156685351d90c3db5bf61d3e573877572f58f982d9fbb35bd678143ccc1f2cccf1fd34c20e0a59b4c837540fac3964068eec3ffb8981a2ab774c542f74168ccd7fa9b08141cd0bda0d99ecee10a3857818370456c3c00d3f7b514f30ff6c31f11147851c8438411de3fc71719fbf79df3cab963231732c95850d59df90144161c2ef84a8b1c76a9494b8dd7234782bc61a6fc23222599a14163f78e117c99f33b3d2a4b11339903b41e7cfc253f1319d4c3ab1dc3d31a503c0bf9c233cb9216201d71abf915b8e50c0612b1fdba8ea8f248767256597151ba2f58dd67d470f8cfdfdc0bffceba618587f652a2155c58717a85e1eff38149b521f99449b35ed2a5ecb474fe60257d261017386ae08ea61cf907ebb7d2d5b1a55e50088449563d1d788d8b4f18ee57e24c6cab40dcd569495c6ea13fa1ca68dbeb6fed7462444ca94b6561471b4e1a75945d7327e5e56348bbd5cae106bf74976cc9288394a731b3555401e59c2718001171b6d6" + ) + val decryptedEve = Sphinx.peel(eve, paymentHash, decryptedDave.nextPacket).right!! + assertTrue(decryptedEve.isLastPacket) + val decryptedTrampolineEve = Sphinx.peel(eve, paymentHash, decryptedTrampolineCarol.nextPacket).right!! + assertTrue(decryptedTrampolineEve.isLastPacket) + } + @Test fun `create packet with payloads filling the onion`() { val packetAndSecrets = Sphinx.create(sessionKey, publicKeys, paymentPayloadsFull.map { it.toByteArray() }, associatedData, OnionRoutingPacket.PaymentPacketLength) diff --git a/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt index 890ca60cc..84181dd05 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/payment/OutgoingPaymentHandlerTestsCommon.kt @@ -240,31 +240,6 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { testSinglePartTrampolinePayment(payment, invoice, recipientKey) } - @Test - fun `successful first attempt -- single part + backwards-compatibility trampoline bit`() = runSuspendTest { - val recipientKey = randomKey() - val invoice = run { - // Invoices generated by older versions of wallets based on lightning-kmp will generate invoices with the following feature bits. - val invoiceFeatures = mapOf( - Feature.VariableLengthOnion to FeatureSupport.Optional, - Feature.PaymentSecret to FeatureSupport.Mandatory, - Feature.BasicMultiPartPayment to FeatureSupport.Optional, - Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional - ) - Bolt11Invoice.create( - chain = Chain.Mainnet, - amount = 195_000.msat, - paymentHash = randomBytes32(), - privateKey = recipientKey, - description = Either.Left("trampoline backwards-compatibility"), - minFinalCltvExpiryDelta = Bolt11Invoice.DEFAULT_MIN_FINAL_EXPIRY_DELTA, - features = Features(invoiceFeatures.toMap()), - ) - } - val payment = PayInvoice(UUID.randomUUID(), 200_000.msat, LightningOutgoingPayment.Details.Normal(invoice)) // we slightly overpay the invoice amount - testSinglePartTrampolinePayment(payment, invoice, recipientKey) - } - private suspend fun testSinglePartTrampolinePayment(payment: PayInvoice, invoice: Bolt11Invoice, recipientKey: PrivateKey) { val channels = makeChannels() val walletParams = defaultWalletParams.copy(trampolineFees = listOf(TrampolineFees(3.sat, 10_000, CltvExpiryDelta(144)))) @@ -910,13 +885,11 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { val paymentPreimage: ByteVector32 = randomBytes32() val paymentHash = Crypto.sha256(paymentPreimage).toByteVector32() - val invoiceFeatures = mutableMapOf( - Feature.VariableLengthOnion to FeatureSupport.Optional, - Feature.PaymentSecret to FeatureSupport.Mandatory, - Feature.BasicMultiPartPayment to FeatureSupport.Optional - ) - if (supportsTrampoline) { - invoiceFeatures[Feature.ExperimentalTrampolinePayment] = FeatureSupport.Optional + val invoiceFeatures = buildMap { + put(Feature.VariableLengthOnion, FeatureSupport.Optional) + put(Feature.PaymentSecret, FeatureSupport.Mandatory) + put(Feature.BasicMultiPartPayment, FeatureSupport.Optional) + if (supportsTrampoline) put(Feature.TrampolinePayment, FeatureSupport.Optional) } return Bolt11Invoice.create( @@ -926,7 +899,7 @@ class OutgoingPaymentHandlerTestsCommon : LightningTestSuite() { privateKey = privKey, description = Either.Left("unit test"), minFinalCltvExpiryDelta = Bolt11Invoice.DEFAULT_MIN_FINAL_EXPIRY_DELTA, - features = Features(invoiceFeatures.toMap()), + features = Features(invoiceFeatures), extraHops = extraHops ) } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt index a2b62079b..4417dd3af 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt @@ -158,6 +158,7 @@ class PaymentPacketTestsCommon : LightningTestSuite() { val trampolineOnion = outerPayload.records.get() assertNotNull(trampolineOnion) val decryptedInner = Sphinx.peel(privateKey, add.paymentHash, trampolineOnion.packet).right!! + assertTrue(decryptedInner.isLastPacket) val innerPayload = PaymentOnion.RelayToNonTrampolinePayload.read(decryptedInner.payload).right!! return Triple(outerPayload, innerPayload, decryptedInner.nextPacket) } @@ -312,12 +313,8 @@ class PaymentPacketTestsCommon : LightningTestSuite() { @Test fun `build a trampoline payment with non-trampoline recipient`() { - // simple trampoline route to e where e doesn't support trampoline: - // .--. - // / \ - // a -> b -> c d -> e - - val routingHints = listOf(Bolt11Invoice.TaggedField.ExtraHop(randomKey().publicKey(), ShortChannelId(42), 10.msat, 100, CltvExpiryDelta(144))) + // E uses a 1-hop routing hint from its LSP. + val routingHints = listOf(Bolt11Invoice.TaggedField.ExtraHop(d, channelUpdateDE.shortChannelId, channelUpdateDE.feeBaseMsat, channelUpdateDE.feeProportionalMillionths, channelUpdateDE.cltvExpiryDelta)) val invoiceFeatures = Features(Feature.VariableLengthOnion to FeatureSupport.Mandatory, Feature.PaymentSecret to FeatureSupport.Mandatory, Feature.BasicMultiPartPayment to FeatureSupport.Optional) val invoice = Bolt11Invoice( "lnbcrt", finalAmount, currentTimestampSeconds(), e, listOf( @@ -329,47 +326,19 @@ class PaymentPacketTestsCommon : LightningTestSuite() { Bolt11Invoice.TaggedField.RoutingInfo(routingHints) ), ByteVector.empty ) - val (amountAC, expiryAC, trampolineOnion) = OutgoingPaymentPacket.buildTrampolineToNonTrampolinePacket( - invoice, - trampolineHops, - PaymentOnion.FinalPayload.Standard.createSinglePartPayload(finalAmount, finalExpiry, randomBytes32(), null) - ) - assertEquals(amountBC, amountAC) - assertEquals(expiryBC, expiryAC) - - val (firstAmount, firstExpiry, onion) = OutgoingPaymentPacket.buildPacket( - paymentHash, - trampolineChannelHops, - PaymentOnion.FinalPayload.Standard.createTrampolinePayload(amountAC, amountAC, expiryAC, randomBytes32(), trampolineOnion.packet), - OnionRoutingPacket.PaymentPacketLength - ) - assertEquals(amountAB, firstAmount) - assertEquals(expiryAB, firstExpiry) - - val addB = UpdateAddHtlc(randomBytes32(), 1, firstAmount, paymentHash, firstExpiry, onion.packet) - val (_, packetC) = decryptChannelRelay(addB, privB) - val addC = UpdateAddHtlc(randomBytes32(), 2, amountBC, paymentHash, expiryBC, packetC) - val (outerC, innerC, packetD) = decryptNodeRelay(addC, privC) - assertIs(outerC) - assertEquals(amountBC, outerC.amount) - assertEquals(amountBC, outerC.totalAmount) - assertEquals(expiryBC, outerC.expiry) - assertNotEquals(invoice.paymentSecret, outerC.paymentSecret) - assertEquals(amountCD, innerC.amountToForward) - assertEquals(expiryCD, innerC.outgoingCltv) - assertEquals(d, innerC.outgoingNodeId) + // C pays that invoice using a trampoline node to relay to the invoice recipient. + val (firstAmount, firstExpiry, onion) = run { + val trampolineHop = NodeHop(d, invoice.nodeId, channelUpdateDE.cltvExpiryDelta, feeD) + val (trampolineAmount, trampolineExpiry, trampolineOnion) = OutgoingPaymentPacket.buildTrampolineToNonTrampolinePacket(invoice, trampolineHop, finalAmount, finalExpiry) + val trampolinePayload = PaymentOnion.FinalPayload.Standard.createTrampolinePayload(trampolineAmount, trampolineAmount, trampolineExpiry, randomBytes32(), trampolineOnion.packet) + OutgoingPaymentPacket.buildPacket(invoice.paymentHash, listOf(ChannelHop(c, d, channelUpdateCD)), trampolinePayload, OnionRoutingPacket.PaymentPacketLength) + } + assertEquals(amountCD, firstAmount) + assertEquals(expiryCD, firstExpiry) - // c forwards the trampoline payment to d. - val (amountD, expiryD, onionD) = OutgoingPaymentPacket.buildPacket( - paymentHash, - listOf(ChannelHop(c, d, channelUpdateCD)), - PaymentOnion.FinalPayload.Standard.createTrampolinePayload(amountCD, amountCD, expiryCD, randomBytes32(), packetD), - OnionRoutingPacket.PaymentPacketLength - ) - assertEquals(amountCD, amountD) - assertEquals(expiryCD, expiryD) - val addD = UpdateAddHtlc(randomBytes32(), 3, amountD, paymentHash, expiryD, onionD.packet) + // D decrypts the onion that contains routing hints in the trampoline onion. + val addD = UpdateAddHtlc(randomBytes32(), 1, firstAmount, paymentHash, firstExpiry, onion.packet) val (outerD, innerD, _) = decryptRelayToNonTrampolinePayload(addD, privD) assertIs(outerD) assertEquals(amountCD, outerD.amount) @@ -382,7 +351,7 @@ class PaymentPacketTestsCommon : LightningTestSuite() { assertEquals(finalAmount, innerD.totalAmount) assertEquals(invoice.paymentSecret, innerD.paymentSecret) assertEquals(invoice.paymentMetadata, innerD.paymentMetadata) - assertEquals(ByteVector("024100"), innerD.invoiceFeatures) // var_onion_optin, payment_secret, basic_mpp + assertEquals(invoice.features.toByteArray().byteVector(), innerD.invoiceFeatures) assertEquals(listOf(routingHints), innerD.invoiceRoutingInfo) } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt b/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt index 7b28cbddf..77789b4b9 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/tests/TestConstants.kt @@ -80,7 +80,7 @@ object TestConstants { Feature.Quiescence to FeatureSupport.Mandatory, Feature.ChannelType to FeatureSupport.Mandatory, Feature.PaymentMetadata to FeatureSupport.Optional, - Feature.ExperimentalTrampolinePayment to FeatureSupport.Optional, + Feature.TrampolinePayment to FeatureSupport.Optional, Feature.WakeUpNotificationProvider to FeatureSupport.Optional, Feature.ChannelBackupProvider to FeatureSupport.Optional, Feature.ExperimentalSplice to FeatureSupport.Optional, diff --git a/src/commonTest/kotlin/fr/acinq/lightning/wire/PaymentOnionTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/wire/PaymentOnionTestsCommon.kt index 1a2f75c2f..ca52f814f 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/wire/PaymentOnionTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/wire/PaymentOnionTestsCommon.kt @@ -11,9 +11,13 @@ import fr.acinq.lightning.payment.Bolt11Invoice import fr.acinq.lightning.tests.utils.LightningTestSuite import fr.acinq.lightning.utils.msat import fr.acinq.secp256k1.Hex -import kotlin.test.* +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue class PaymentOnionTestsCommon : LightningTestSuite() { + @Test fun `encode - decode onion packet`() { val bin = Hex.decode( @@ -38,7 +42,9 @@ class PaymentOnionTestsCommon : LightningTestSuite() { val testCases = mapOf( PaymentOnion.ChannelRelayPayload.create(ShortChannelId(0), MilliSatoshi(0), CltvExpiry(0)) to Hex.decode("0e 0200 0400 06080000000000000000"), PaymentOnion.ChannelRelayPayload.create(ShortChannelId(42), MilliSatoshi(142000), CltvExpiry(500000)) to Hex.decode("14 0203022ab0 040307a120 0608000000000000002a"), - PaymentOnion.ChannelRelayPayload.create(ShortChannelId(561), MilliSatoshi(1105), CltvExpiry(1729)) to Hex.decode("12 02020451 040206c1 06080000000000000231") + PaymentOnion.ChannelRelayPayload.create(ShortChannelId(561), MilliSatoshi(1105), CltvExpiry(1729)) to Hex.decode("12 02020451 040206c1 06080000000000000231"), + PaymentOnion.ChannelRelayPayload.create(ShortChannelId("572330x7x1105"), MilliSatoshi(100_005_000), CltvExpiry(800_250)) to Hex.decode("15 020405f5f488 04030c35fa 060808bbaa0000070451"), + PaymentOnion.ChannelRelayPayload.create(ShortChannelId("572330x42x1729"), MilliSatoshi(100_000_000), CltvExpiry(800_000)) to Hex.decode("15 020405f5e100 04030c3500 060808bbaa00002a06c1"), ) testCases.forEach { @@ -52,15 +58,15 @@ class PaymentOnionTestsCommon : LightningTestSuite() { @Test fun `encode - decode node relay per-hop payload`() { - val nodeId = PublicKey(Hex.decode("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619")) - val expected = PaymentOnion.NodeRelayPayload(TlvStream(OnionPaymentPayloadTlv.AmountToForward(561.msat), OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(42)), OnionPaymentPayloadTlv.OutgoingNodeId(nodeId))) - val bin = Hex.decode("2e 02020231 04012a fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619") + val nodeId = PublicKey(Hex.decode("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145")) + val expected = PaymentOnion.NodeRelayPayload(TlvStream(OnionPaymentPayloadTlv.AmountToForward(100_000_000.msat), OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(800_000)), OnionPaymentPayloadTlv.OutgoingNodeId(nodeId))) + val bin = Hex.decode("2e 020405f5e100 04030c3500 0e2102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145") val decoded = PaymentOnion.NodeRelayPayload.read(bin).right!! assertEquals(expected, decoded) - assertEquals(decoded.amountToForward, 561.msat) - assertEquals(decoded.totalAmount, 561.msat) - assertEquals(decoded.outgoingCltv, CltvExpiry(42)) + assertEquals(decoded.amountToForward, 100_000_000.msat) + assertEquals(decoded.totalAmount, 100_000_000.msat) + assertEquals(decoded.outgoingCltv, CltvExpiry(800_000)) assertEquals(decoded.outgoingNodeId, nodeId) val encoded = expected.write() @@ -89,7 +95,7 @@ class PaymentOnionTestsCommon : LightningTestSuite() { ) ) val bin = Hex.decode( - "fa 02020231 04012a 0822eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f2836866190451 fe00010231010a fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619 fe000102339b01036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e200000000000000010000000a00000064009002025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce148600000000000000020000001400000096000c02a051267759c3a149e3e72372f4e0c4054ba597ebfd0eda78a2273023667205ee00000000000000030000001e000000c80018" + "f6 02020231 04012a 0822eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f2836866190451 0e2102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619 fe00010231010a fe000102339b01036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e200000000000000010000000a00000064009002025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce148600000000000000020000001400000096000c02a051267759c3a149e3e72372f4e0c4054ba597ebfd0eda78a2273023667205ee00000000000000030000001e000000c80018" ) val decoded = PaymentOnion.RelayToNonTrampolinePayload.read(bin).right!! @@ -149,29 +155,48 @@ class PaymentOnionTestsCommon : LightningTestSuite() { setOf(GenericTlv(65535, ByteVector("06c1"))) ) to Hex.decode("2f 02020231 04012a 0820eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619 fdffff0206c1"), TlvStream( - OnionPaymentPayloadTlv.AmountToForward(561.msat), - OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(42)), - OnionPaymentPayloadTlv.PaymentData(ByteVector32("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 0.msat), + OnionPaymentPayloadTlv.AmountToForward(100_000_000.msat), + OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(800_000)), + OnionPaymentPayloadTlv.PaymentData(ByteVector32("2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"), 100_000_000.msat), + ) to Hex.decode( + "31 020405f5e100 04030c3500 08242a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a05f5e100" + ), + TlvStream( + OnionPaymentPayloadTlv.AmountToForward(100_005_000.msat), + OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(800_250)), + OnionPaymentPayloadTlv.PaymentData(ByteVector32("2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b"), 100_005_000.msat), OnionPaymentPayloadTlv.TrampolineOnion( OnionRoutingPacket( 0, - ByteVector("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), - ByteVector("cff34152f3a36e52ca94e74927203a560392b9cc7ce3c45809c6be52166c24a595716880f95f178bf5b30ca63141f74db6e92795c6130877cfdac3d4bd3087ee73c65d627ddd709112a848cc99e303f3706509aa43ba7c8a88cba175fccf9a8f5016ef06d3b935dbb15196d7ce16dc1a7157845566901d7b2197e52cab4ce487014b14816e5805f9fcacb4f8f88b8ff176f1b94f6ce6b00bc43221130c17d20ef629db7c5f7eafaa166578c720619561dd14b3277db557ec7dcdb793771aef0f2f667cfdbeae3ac8d331c5994779dffb31e5fc0dbdedc0c592ca6d21c18e47fe3528d6975c19517d7e2ea8c5391cf17d0fe30c80913ed887234ccb48808f7ef9425bcd815c3b586210979e3bb286ef2851bf9ce04e28c40a203df98fd648d2f1936fd2f1def0e77eecb277229b4b682322371c0a1dbfcd723a991993df8cc1f2696b84b055b40a1792a29f710295a18fbd351b0f3ff34cd13941131b8278ba79303c89117120eea691738a9954908195143b039dbeed98f26a92585f3d15cf742c953799d3272e0545e9b744be9d3b4c"), - ByteVector32("bb079bfc4b35190eee9f59a1d7b41ba2f773179f322dafb4b1af900c289ebd6c") + ByteVector("02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337"), + ByteVector("1860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306"), + ByteVector32("dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623") ) ) ) to Hex.decode( - "fd0203 02020231 04012a 0820ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff fe00010234fd01d20002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619cff34152f3a36e52ca94e74927203a560392b9cc7ce3c45809c6be52166c24a595716880f95f178bf5b30ca63141f74db6e92795c6130877cfdac3d4bd3087ee73c65d627ddd709112a848cc99e303f3706509aa43ba7c8a88cba175fccf9a8f5016ef06d3b935dbb15196d7ce16dc1a7157845566901d7b2197e52cab4ce487014b14816e5805f9fcacb4f8f88b8ff176f1b94f6ce6b00bc43221130c17d20ef629db7c5f7eafaa166578c720619561dd14b3277db557ec7dcdb793771aef0f2f667cfdbeae3ac8d331c5994779dffb31e5fc0dbdedc0c592ca6d21c18e47fe3528d6975c19517d7e2ea8c5391cf17d0fe30c80913ed887234ccb48808f7ef9425bcd815c3b586210979e3bb286ef2851bf9ce04e28c40a203df98fd648d2f1936fd2f1def0e77eecb277229b4b682322371c0a1dbfcd723a991993df8cc1f2696b84b055b40a1792a29f710295a18fbd351b0f3ff34cd13941131b8278ba79303c89117120eea691738a9954908195143b039dbeed98f26a92585f3d15cf742c953799d3272e0545e9b744be9d3b4cbb079bfc4b35190eee9f59a1d7b41ba2f773179f322dafb4b1af900c289ebd6c" - ) + "fd0116 020405f5f488 04030c35fa 08242b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b05f5f488 14e30002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe3371860c0749bfd613056cfc5718beecc25a2f255fc7abbea3cd75ff820e9d30807d19b30f33626452fa54bb2d822e918558ed3e6714deb3f9a2a10895e7553c6f088c9a852043530dbc9abcc486030894364b205f5de60171b451ff462664ebce23b672579bf2a444ebfe0a81875c26d2fa16d426795b9b02ccbc4bdf909c583f0c2ebe9136510645917153ecb05181ca0c1b207824578ee841804a148f4c3df7306dcea52d94222907c9187bc31c0880fc084f0d88716e195c0abe7672d15217623" + ), + TlvStream( + OnionPaymentPayloadTlv.AmountToForward(100_000_000.msat), + OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(800_000)), + OnionPaymentPayloadTlv.PaymentData(ByteVector32("2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c"), 100_000_000.msat), + OnionPaymentPayloadTlv.TrampolineOnion( + OnionRoutingPacket( + 0, + ByteVector("035e5c85814fdb522b4efeef99b44fe8a5d3d3412057bc213b98d6f605edb022c2"), + ByteVector("ae4a9141f6ac403790afeed975061f024e2723d485f9cb35a3eaf881732f468dc19009bf195b561590798fb895b7b7065b5537018dec330e509e8618700c9c6e1df5d15b900ac3c34104b6abb1099fd2eca3b640d7d5fda9370e20c09035168fc64d954baa80361b965314c400da2d7a64d0536bf9e494aebb80aec358327a4a1a667fcff1daf241c99dd8c4fa907de5b931fb9daed083c157f5ea1dd960d14295"), + ByteVector32("2f8ebe4e1ccaee4d565a093e2b91f94b04a884ce2e8c60aced3565e8d2d10de5") + ) + ) + ) to Hex.decode( + "fd0116 020405f5e100 04030c3500 08242c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c05f5e100 14e300035e5c85814fdb522b4efeef99b44fe8a5d3d3412057bc213b98d6f605edb022c2ae4a9141f6ac403790afeed975061f024e2723d485f9cb35a3eaf881732f468dc19009bf195b561590798fb895b7b7065b5537018dec330e509e8618700c9c6e1df5d15b900ac3c34104b6abb1099fd2eca3b640d7d5fda9370e20c09035168fc64d954baa80361b965314c400da2d7a64d0536bf9e494aebb80aec358327a4a1a667fcff1daf241c99dd8c4fa907de5b931fb9daed083c157f5ea1dd960d142952f8ebe4e1ccaee4d565a093e2b91f94b04a884ce2e8c60aced3565e8d2d10de5" + ), ) testCases.forEach { val expected = it.key val decoded = PaymentOnion.FinalPayload.Standard.read(it.value).right!! assertEquals(decoded, PaymentOnion.FinalPayload.Standard(expected)) - assertEquals(decoded.amount, 561.msat) - assertEquals(decoded.expiry, CltvExpiry(42)) - val encoded = PaymentOnion.FinalPayload.Standard(expected).write() assertContentEquals(it.value, encoded) } @@ -266,4 +291,5 @@ class PaymentOnionTestsCommon : LightningTestSuite() { assertTrue(PaymentOnion.FinalPayload.Standard.read(it).isLeft) } } + } \ No newline at end of file diff --git a/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json index f91025c22..47d2b7a48 100644 --- a/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Closing_Local_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json index 6c2866d63..5a4c86fea 100644 --- a/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Closing_Mutual_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json index a5c7b6d89..ff630802c 100644 --- a/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Closing_Remote_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json index 04d847f92..35945915e 100644 --- a/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Closing_Revoked_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/Negotiating_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Negotiating_ebb9087c/data.json index f3bc67db6..9c19ceb1e 100644 --- a/src/commonTest/resources/nonreg/v4/Negotiating_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Negotiating_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json index e0139c187..185157378 100644 --- a/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/Normal_ebb9087c/data.json @@ -39,11 +39,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, @@ -71,11 +71,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/ShuttingDown_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/ShuttingDown_ebb9087c/data.json index e5cfaefea..dc8c72b32 100644 --- a/src/commonTest/resources/nonreg/v4/ShuttingDown_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/ShuttingDown_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -72,11 +72,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/WaitForChannelReady_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/WaitForChannelReady_ebb9087c/data.json index c3a28ed52..750529496 100644 --- a/src/commonTest/resources/nonreg/v4/WaitForChannelReady_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/WaitForChannelReady_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -72,11 +72,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_ebb9087c/data.json index 1f77e3694..4186ee21a 100644 --- a/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/WaitForFundingConfirmed_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -71,11 +71,11 @@ "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", "channel_backup_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } }, diff --git a/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json b/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json index b9925d628..e3fe59455 100644 --- a/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json +++ b/src/commonTest/resources/nonreg/v4/WaitForRemotePublishFutureCommitment_ebb9087c/data.json @@ -35,11 +35,11 @@ "option_dual_fund": "Mandatory", "option_channel_type": "Mandatory", "wake_up_notification_provider": "Optional", - "channel_backup_provider": "Optional", - "trampoline_payment_experimental": "Optional" + "channel_backup_provider": "Optional" }, "unknown": [ - 139 + 139, + 149 ] } }, @@ -70,11 +70,11 @@ "option_channel_type": "Mandatory", "zero_reserve_channels": "Optional", "wake_up_notification_client": "Optional", - "trampoline_payment_experimental": "Optional", "splice_experimental": "Optional" }, "unknown": [ - 137 + 137, + 149 ] } },