Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
thomash-acinq committed Jan 29, 2024
1 parent 129e59a commit 1b0d7a9
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 27 deletions.
13 changes: 4 additions & 9 deletions src/commonMain/kotlin/fr/acinq/lightning/Features.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import fr.acinq.lightning.utils.or
import kotlinx.serialization.Serializable

/** Feature scope as defined in Bolt 9. */
enum class FeatureScope { Init, Node, Invoice }
enum class FeatureScope { Init, Node, Invoice, Bolt12 }

enum class FeatureSupport {
Mandatory {
Expand Down Expand Up @@ -88,7 +88,7 @@ sealed class Feature {
object BasicMultiPartPayment : Feature() {
override val rfcName get() = "basic_mpp"
override val mandatory get() = 16
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice, FeatureScope.Bolt12)
}

@Serializable
Expand Down Expand Up @@ -242,13 +242,6 @@ sealed class Feature {
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
}

@Serializable
object ExperimentalBlindedTrampolinePayment : Feature() {
override val rfcName get() = "blinded_trampoline_payment_experimental"
override val mandatory get() = 150
override val scopes: Set<FeatureScope> get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice)
}

@Serializable
object ExperimentalSplice : Feature() {
override val rfcName get() = "splice_experimental"
Expand All @@ -275,6 +268,8 @@ data class Features(val activated: Map<Feature, FeatureSupport>, val unknown: Se

fun invoiceFeatures(): Features = Features(activated.filter { it.key.scopes.contains(FeatureScope.Invoice) }, unknown)

fun bolt12Features(): Features = Features(activated.filter { it.key.scopes.contains(FeatureScope.Bolt12) }, unknown)

/** NB: this method is not reflexive, see [[Features.areCompatible]] if you want symmetric validation. */
fun areSupported(remoteFeatures: Features): Boolean {
// we allow unknown odd features (it's ok to be odd)
Expand Down
38 changes: 38 additions & 0 deletions src/commonMain/kotlin/fr/acinq/lightning/payment/OfferPayment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fr.acinq.lightning.payment

import fr.acinq.lightning.Lightning.randomKey
import fr.acinq.lightning.MilliSatoshi
import fr.acinq.lightning.NodeParams
import fr.acinq.lightning.message.Postman
import fr.acinq.lightning.utils.Either
import fr.acinq.lightning.utils.currentTimestampSeconds
import fr.acinq.lightning.wire.OfferTypes
import fr.acinq.lightning.wire.OnionMessagePayloadTlv
import fr.acinq.lightning.wire.TlvStream

/*class OfferPayment(val nodeParams: NodeParams, val postman: Postman, val outgoingPaymentHandler: OutgoingPaymentHandler) {
suspend fun payOffer(offer: OfferTypes.Offer, amount: MilliSatoshi, quantity: Long) {
if (!nodeParams.features.bolt12Features().areSupported(offer.features)) {
return UnsupportedFeatures(offer.features.invoiceFeatures())
} else if (!offer.chains.contains(nodeParams.chainHash)) {
return UnsupportedChains(offer.chains)
} else if (offer.expirySeconds?.let { it < currentTimestampSeconds() } == true) {
return ExpiredOffer(offer.expiry.get)
} else if ((offer.quantityMax ?: 1) < quantity) {
return QuantityTooHigh(offer.quantityMax ?: 1)
} else if (offer.amount?.let { it * quantity > amount } == true) {
return AmountInsufficient(offer.amount * quantity)
} else {
val payerKey = randomKey()
val invoiceRequest = OfferTypes.InvoiceRequest(offer, amount, quantity, nodeParams.features.bolt12Features(), payerKey, nodeParams.chainHash)
val contactInfo = invoiceRequest.offer.contactInfos[attemptNumber % invoiceRequest.offer.contactInfos.size]
val messageContent = TlvStream<OnionMessagePayloadTlv>(OnionMessagePayloadTlv.InvoiceRequest(invoiceRequest.records))
when (postman.sendMessageExpectingReply(contactInfo, messageContent, intermediateNodes)) {
is Either.Left -> TODO()
is Either.Right -> TODO()
}
}
}
}*/
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,9 @@ class OutgoingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
}
is Bolt12Invoice -> {
val nodes = listOf(walletParams.trampolineNode.id, request.recipient)
val trampolineRoute = listOf(TrampolineBlindedHop(request.paymentRequest.blindedPaths, fees.cltvExpiryDelta, fees.calculateFees(request.amount)))
val trampolineRoute = listOf(TrampolineToBlindedHop(request.paymentRequest.blindedPaths, fees.cltvExpiryDelta, fees.calculateFees(request.amount)))
val finalExpiry = nodeParams.paymentRecipientExpiryParams.computeFinalExpiry(currentBlockHeight, CltvExpiryDelta(0))
if (invoiceFeatures.hasFeature(Feature.ExperimentalBlindedTrampolinePayment)) {
val finalPayload = PaymentOnion.FinalPayload.Standard.createSinglePartPayload(request.amount, finalExpiry, randomBytes32(), null)
OutgoingPaymentPacket.buildPacket(request.paymentHash, nodes, trampolineRoute, finalPayload, OnionRoutingPacket.TrampolinePacketLength)
} else {
OutgoingPaymentPacket.buildTrampolineToLegacyPacket(request.paymentRequest, nodes, trampolineRoute, request.amount, request.amount, finalExpiry)
}
OutgoingPaymentPacket.buildTrampolineToLegacyPacket(request.paymentRequest, nodes, trampolineRoute, request.amount, request.amount, finalExpiry)
}
}
return Triple(trampolineAmount, trampolineExpiry, trampolineOnion.packet)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object OutgoingPaymentPacket {
finalExpiry: CltvExpiry): Triple<MilliSatoshi, CltvExpiry, PacketAndSecrets> {
// 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.DummyPayload
val dummyFinalPayload = PaymentOnion.EmptyPayload
val (firstAmount, firstExpiry, payloads) = hops.reversed().fold(Triple(finalAmount, finalExpiry, listOf<PaymentOnion.PerHopPayload>(dummyFinalPayload))) { triple, hop ->
val (amount, expiry, payloads) = triple
val payload = when (payloads.size) {
Expand Down
6 changes: 3 additions & 3 deletions src/commonMain/kotlin/fr/acinq/lightning/router/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ data class NodeHop(val nodeId: PublicKey, val nextNodeId: PublicKey, override va
PaymentOnion.NodeRelayPayload.createNodeRelayToNonTrampolinePayload(amount, totalAmount, expiry, nextNodeId, invoice)
}

data class TrampolineBlindedHop(val paths: List<PaymentBlindedContactInfo>, override val cltvExpiryDelta: CltvExpiryDelta, val fee: MilliSatoshi) : TrampolineHop<Bolt12Invoice>() {
data class TrampolineToBlindedHop(val paths: List<PaymentBlindedContactInfo>, override val cltvExpiryDelta: CltvExpiryDelta, val fee: MilliSatoshi) : TrampolineHop<Bolt12Invoice>() {
override fun fee(amount: MilliSatoshi): MilliSatoshi = fee

override fun createPayload(amount: MilliSatoshi, expiry: CltvExpiry): PaymentOnion.PerHopPayload =
TODO()
PaymentOnion.NodeRelayPayload.createToBlinded(amount, expiry, paths)

override fun createPayloadToLegacy(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, invoice: Bolt12Invoice): PaymentOnion.PerHopPayload =
TODO()
PaymentOnion.NodeRelayPayload.createBlindedToNonTrampolinePayload(amount, totalAmount, expiry, paths, invoice)
}
26 changes: 19 additions & 7 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Input
import fr.acinq.bitcoin.io.Output
import fr.acinq.lightning.*
import fr.acinq.lightning.crypto.RouteBlinding
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.Bolt12Invoice
import fr.acinq.lightning.payment.Bolt12Invoice.Companion.PaymentBlindedContactInfo
import fr.acinq.lightning.payment.PaymentRequest
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.toByteVector

Expand Down Expand Up @@ -385,10 +383,6 @@ object PaymentOnion {

fun create(outgoingChannelId: ShortChannelId, amountToForward: MilliSatoshi, outgoingCltv: CltvExpiry): ChannelRelayPayload =
ChannelRelayPayload(TlvStream(OnionPaymentPayloadTlv.AmountToForward(amountToForward), OnionPaymentPayloadTlv.OutgoingCltv(outgoingCltv), OnionPaymentPayloadTlv.OutgoingChannelId(outgoingChannelId)))

fun createBlinded(outgoingChannelId: ShortChannelId, amountToForward: MilliSatoshi, outgoingCltv: CltvExpiry): ChannelRelayPayload =
// TODO
ChannelRelayPayload(TlvStream(OnionPaymentPayloadTlv.AmountToForward(amountToForward), OnionPaymentPayloadTlv.OutgoingCltv(outgoingCltv), OnionPaymentPayloadTlv.OutgoingChannelId(outgoingChannelId)))
}
}

Expand Down Expand Up @@ -419,6 +413,13 @@ object PaymentOnion {
fun create(amount: MilliSatoshi, expiry: CltvExpiry, nextNodeId: PublicKey) =
NodeRelayPayload(TlvStream(OnionPaymentPayloadTlv.AmountToForward(amount), OnionPaymentPayloadTlv.OutgoingCltv(expiry), OnionPaymentPayloadTlv.OutgoingNodeId(nextNodeId)))

fun createToBlinded(amount: MilliSatoshi, expiry: CltvExpiry, nextBlindedPaths: List<PaymentBlindedContactInfo>) =
NodeRelayPayload(TlvStream(
OnionPaymentPayloadTlv.AmountToForward(amount),
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
OnionPaymentPayloadTlv.OutgoingBlindedPaths(nextBlindedPaths)
))

/** Create a trampoline inner payload instructing the trampoline node to relay via a non-trampoline payment. */
fun createNodeRelayToNonTrampolinePayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, targetNodeId: PublicKey, invoice: Bolt11Invoice): NodeRelayPayload {
// NB: we limit the number of routing hints to ensure we don't overflow the onion.
Expand All @@ -444,10 +445,21 @@ object PaymentOnion {
)
)
}

fun createBlindedToNonTrampolinePayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, targetPaths: List<PaymentBlindedContactInfo>, invoice: Bolt12Invoice): NodeRelayPayload {
// TODO: encrypted recipient data
return NodeRelayPayload(TlvStream(
OnionPaymentPayloadTlv.AmountToForward(amount),
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
OnionPaymentPayloadTlv.OutgoingBlindedPaths(targetPaths),
OnionPaymentPayloadTlv.TotalAmount(totalAmount),
OnionPaymentPayloadTlv.InvoiceFeatures(invoice.features.toByteArray().toByteVector())
))
}
}
}

object DummyPayload : PerHopPayload() {
object EmptyPayload : PerHopPayload() {
override fun write(out: Output) = tlvSerializer.write(TlvStream.empty(), out)
}
}

0 comments on commit 1b0d7a9

Please sign in to comment.