Skip to content

Commit

Permalink
Handle new database types for offer payments
Browse files Browse the repository at this point in the history
  • Loading branch information
dpad85 committed May 3, 2024
1 parent 968f9c8 commit 6e3011d
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 32 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
object Versions {
const val lightningKmp = "1.6.1"
const val lightningKmp = "1.6.2-SNAPSHOT"
const val secp256k1 = "0.14.0"
const val torMobile = "0.2.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.PrivateKey
import fr.acinq.lightning.MilliSatoshi
import fr.acinq.lightning.db.*
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.Bolt12Invoice
import fr.acinq.lightning.payment.OfferPaymentMetadata
import fr.acinq.lightning.utils.currentTimestampMillis
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sum
Expand Down Expand Up @@ -133,7 +135,7 @@ private fun HeaderForOutgoing(
is LightningOutgoingPayment -> when (payment.details) {
is LightningOutgoingPayment.Details.Normal -> stringResource(R.string.paymentdetails_normal_outgoing)
is LightningOutgoingPayment.Details.SwapOut -> stringResource(R.string.paymentdetails_swapout)
is LightningOutgoingPayment.Details.KeySend -> stringResource(R.string.paymentdetails_keysend)
is LightningOutgoingPayment.Details.Blinded -> stringResource(id = R.string.paymentdetails_offer_outgoing)
}
is SpliceCpfpOutgoingPayment -> stringResource(id = R.string.paymentdetails_splice_cpfp_outgoing)
is InboundLiquidityOutgoingPayment -> stringResource(id = R.string.paymentdetails_inbound_liquidity)
Expand Down Expand Up @@ -175,6 +177,7 @@ private fun HeaderForIncoming(
is IncomingPayment.Origin.KeySend -> stringResource(R.string.paymentdetails_keysend)
is IncomingPayment.Origin.SwapIn -> stringResource(R.string.paymentdetails_swapin)
is IncomingPayment.Origin.OnChain -> stringResource(R.string.paymentdetails_swapin)
is IncomingPayment.Origin.Offer -> stringResource(id = R.string.paymentdetails_offer_incoming)
}
)
}
Expand Down Expand Up @@ -300,19 +303,14 @@ private fun DetailsForLightningOutgoingPayment(
// -- details of the payment
when (details) {
is LightningOutgoingPayment.Details.Normal -> {
when (val paymentRequest = details.paymentRequest) {
is Bolt11Invoice -> InvoiceSection(invoice = paymentRequest)
is Bolt12Invoice -> {
// TODO
}
}
Bolt11InvoiceSection(invoice = details.paymentRequest)
}
is LightningOutgoingPayment.Details.SwapOut -> {
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_bitcoin_address_label), value = details.address)
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = details.paymentHash.toHex())
}
is LightningOutgoingPayment.Details.KeySend -> {
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = details.paymentHash.toHex())
is LightningOutgoingPayment.Details.Blinded -> {
Bolt12InvoiceSection(invoice = details.paymentRequest, payerKey = details.payerKey)
}
}

Expand Down Expand Up @@ -408,7 +406,7 @@ private fun DetailsForIncoming(
// -- details about the origin of the payment
when (val origin = payment.origin) {
is IncomingPayment.Origin.Invoice -> {
InvoiceSection(invoice = origin.paymentRequest)
Bolt11InvoiceSection(invoice = origin.paymentRequest)
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_preimage_label), value = payment.preimage.toHex())
}
is IncomingPayment.Origin.SwapIn -> {
Expand All @@ -428,6 +426,9 @@ private fun DetailsForIncoming(
}
}
}
is IncomingPayment.Origin.Offer -> {
Bolt12MetadataSection(metadata = origin.metadata)
}
}
}

Expand Down Expand Up @@ -507,7 +508,7 @@ private fun LightningPart(
}

@Composable
private fun InvoiceSection(
private fun Bolt11InvoiceSection(
invoice: Bolt11Invoice
) {
val requestedAmount = invoice.amount
Expand All @@ -530,6 +531,49 @@ private fun InvoiceSection(
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_request_label), value = invoice.write())
}

@Composable
private fun Bolt12InvoiceSection(
invoice: Bolt12Invoice,
payerKey: PrivateKey,
) {
val requestedAmount = invoice.amount
if (requestedAmount != null) {
TechnicalRowAmount(
label = stringResource(id = R.string.paymentdetails_invoice_requested_label),
amount = requestedAmount,
rateThen = null
)
}

val description = invoice.description?.takeIf { it.isNotBlank() }
if (description != null) {
TechnicalRow(label = stringResource(id = R.string.paymentdetails_payment_request_description_label)) {
Text(text = description)
}
}

TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payerkey_label), value = payerKey.toHex())
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = invoice.paymentHash.toHex())
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_request_label), value = invoice.write())
}

@Composable
private fun Bolt12MetadataSection(
metadata: OfferPaymentMetadata
) {
TechnicalRowAmount(
label = stringResource(id = R.string.paymentdetails_invoice_requested_label),
amount = metadata.amount,
rateThen = null
)
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payment_hash_label), value = metadata.paymentHash.toHex())
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_preimage_label), value = metadata.preimage.toHex())
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_offerid_label), value = metadata.offerId.toHex())
if (metadata is OfferPaymentMetadata.V1) {
TechnicalRowSelectable(label = stringResource(id = R.string.paymentdetails_payerkey_label), value = metadata.payerKey.toHex())
}
}

// ============== utility components for this view

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ object LegacyMigrationHelper {
} else if (paymentRequest != null) {
LightningOutgoingPayment.Details.Normal(paymentRequest)
} else {
LightningOutgoingPayment.Details.KeySend(preimage = Lightning.randomBytes32().sha256())
throw RuntimeException("unhandled outgoing payment details")
}

val parts = listOfParts.filter { it.paymentType() == PaymentType.Standard() }.map { part ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ fun Connection.CLOSED.isBadCertificate() = this.reason?.cause is CertificateExce
fun WalletPayment.smartDescription(context: Context): String? = when (this) {
is LightningOutgoingPayment -> when (val details = this.details) {
is LightningOutgoingPayment.Details.Normal -> details.paymentRequest.desc
is LightningOutgoingPayment.Details.KeySend -> context.getString(R.string.paymentdetails_desc_keysend)
is LightningOutgoingPayment.Details.SwapOut -> context.getString(R.string.paymentdetails_desc_swapout, details.address)
is LightningOutgoingPayment.Details.Blinded -> details.paymentRequest.description
}
is IncomingPayment -> when (val origin = this.origin) {
is IncomingPayment.Origin.Invoice -> origin.paymentRequest.description
is IncomingPayment.Origin.KeySend -> context.getString(R.string.paymentdetails_desc_keysend)
is IncomingPayment.Origin.SwapIn, is IncomingPayment.Origin.OnChain -> context.getString(R.string.paymentdetails_desc_swapin)
is IncomingPayment.Origin.Offer -> context.getString(R.string.paymentdetails_desc_offer_incoming, origin.metadata.offerId.toHex())
}
is SpliceOutgoingPayment -> context.getString(R.string.paymentdetails_desc_splice_out)
is ChannelCloseOutgoingPayment -> context.getString(R.string.paymentdetails_desc_closing_channel)
Expand Down
5 changes: 5 additions & 0 deletions phoenix-android/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
<string name="paymentdetails_desc_swapout">Swap-out to %1$s</string>
<string name="paymentdetails_desc_swapin">On-chain deposit</string>
<string name="paymentdetails_desc_identifier">Payment to %1$s</string>
<string name="paymentdetails_desc_offer_incoming">Payment to %1$s</string>

<string name="paymentdetails_edit_dialog_title">Add a custom description to this payment</string>
<string name="paymentdetails_edit_dialog_input_label">Description</string>
Expand All @@ -388,6 +389,8 @@
<string name="paymentdetails_swapin">Swap-in Bitcoin deposit</string>
<string name="paymentdetails_swapout">Swap-out to Bitcoin address</string>
<string name="paymentdetails_keysend">Keysend (spontaneous payment)</string>
<string name="paymentdetails_offer_outgoing">Offer outgoing Lightning payment</string>
<string name="paymentdetails_offer_incoming">Offer incoming Lightning payment</string>
<string name="paymentdetails_swapin_address_label">Deposit address</string>
<string name="paymentdetails_bitcoin_address_label">Bitcoin address</string>

Expand All @@ -410,6 +413,8 @@
<string name="paymentdetails_payment_hash_label">Payment Hash</string>
<string name="paymentdetails_payment_request_label">Invoice</string>
<string name="paymentdetails_preimage_label">Preimage</string>
<string name="paymentdetails_payerkey_label">Payer key</string>
<string name="paymentdetails_offerid_label">Offer ID</string>

<string name="paymentdetails_status_label">Payment status</string>
<string name="paymentdetails_status_success">Successful</string>
Expand Down
4 changes: 4 additions & 0 deletions phoenix-shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ if (includeAndroid) {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

lint {
disable.add("Deprecation")
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.Chain
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.*
import fr.acinq.lightning.db.LightningOutgoingPayment
import fr.acinq.lightning.io.PayInvoice
import fr.acinq.lightning.io.SendPayment
import fr.acinq.lightning.logging.LoggerFactory
import fr.acinq.lightning.utils.*
Expand Down Expand Up @@ -222,11 +223,10 @@ class AppScanController(
}

peer.send(
SendPayment(
PayInvoice(
paymentId = paymentId,
amount = amountToSend,
recipient = invoice.nodeId,
paymentRequest = invoice,
paymentDetails = LightningOutgoingPayment.Details.Normal(paymentRequest = invoice),
trampolineFeesOverride = listOf(trampolineFees)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@
@file:UseSerializers(
OutpointSerializer::class,
ByteVector32Serializer::class,
ByteVectorSerializer::class,
)

package fr.acinq.phoenix.db.payments

import fr.acinq.bitcoin.ByteVector
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.OutPoint
import fr.acinq.bitcoin.TxId
import fr.acinq.lightning.db.IncomingPayment
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.OfferPaymentMetadata
import fr.acinq.phoenix.db.payments.DbTypesHelper.decodeBlob
import fr.acinq.phoenix.db.serializers.v1.ByteVector32Serializer
import fr.acinq.phoenix.db.serializers.v1.ByteVectorSerializer
import fr.acinq.phoenix.db.serializers.v1.OutpointSerializer
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
Expand All @@ -40,6 +44,7 @@ enum class IncomingOriginTypeVersion {
INVOICE_V0,
SWAPIN_V0,
ONCHAIN_V0,
OFFER_V0,
}

sealed class IncomingOriginData {
Expand All @@ -65,6 +70,11 @@ sealed class IncomingOriginData {
data class V0(@Serializable val txId: ByteVector32, val outpoints: List<@Serializable OutPoint>) : SwapIn()
}

sealed class Offer : IncomingOriginData() {
@Serializable
data class V0(@Serializable val encodedMetadata: ByteVector) : Offer()
}

companion object {
fun deserialize(typeVersion: IncomingOriginTypeVersion, blob: ByteArray): IncomingPayment.Origin = decodeBlob(blob) { json, format ->
when (typeVersion) {
Expand All @@ -74,6 +84,9 @@ sealed class IncomingOriginData {
IncomingOriginTypeVersion.ONCHAIN_V0 -> format.decodeFromString<OnChain.V0>(json).let {
IncomingPayment.Origin.OnChain(TxId(it.txId), it.outpoints.toSet())
}
IncomingOriginTypeVersion.OFFER_V0 -> format.decodeFromString<Offer.V0>(json).let {
IncomingPayment.Origin.Offer(metadata = OfferPaymentMetadata.decode(it.encodedMetadata))
}
}
}
}
Expand All @@ -88,4 +101,6 @@ fun IncomingPayment.Origin.mapToDb(): Pair<IncomingOriginTypeVersion, ByteArray>
Json.encodeToString(IncomingOriginData.SwapIn.V0(address)).toByteArray(Charsets.UTF_8)
is IncomingPayment.Origin.OnChain -> IncomingOriginTypeVersion.ONCHAIN_V0 to
Json.encodeToString(IncomingOriginData.OnChain.V0(txId.value, localInputs.toList())).toByteArray(Charsets.UTF_8)
is IncomingPayment.Origin.Offer -> IncomingOriginTypeVersion.OFFER_V0 to
Json.encodeToString(IncomingOriginData.Offer.V0(metadata.encode())).toByteArray(Charsets.UTF_8)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,27 @@
package fr.acinq.phoenix.db.payments

import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.PrivateKey
import fr.acinq.bitcoin.Satoshi
import fr.acinq.lightning.db.LightningOutgoingPayment
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.Bolt12Invoice
import fr.acinq.phoenix.db.serializers.v1.ByteVector32Serializer
import fr.acinq.phoenix.db.serializers.v1.SatoshiSerializer
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json


enum class OutgoingDetailsTypeVersion {
NORMAL_V0,
KEYSEND_V0,
SWAPOUT_V0,
@Deprecated("channel close are now stored in their own table")
CLOSING_V0,
BLINDED_V0,
}

sealed class OutgoingDetailsData {
Expand All @@ -50,20 +52,19 @@ sealed class OutgoingDetailsData {
data class V0(val paymentRequest: String) : Normal()
}

sealed class KeySend : OutgoingDetailsData() {
sealed class SwapOut : OutgoingDetailsData() {
@Serializable
data class V0(@Serializable val preimage: ByteVector32) : KeySend()
data class V0(val address: String, val paymentRequest: String, @Serializable val swapOutFee: Satoshi) : SwapOut()
}

sealed class SwapOut : OutgoingDetailsData() {
sealed class Blinded : OutgoingDetailsData() {
@Serializable
data class V0(val address: String, val paymentRequest: String, @Serializable val swapOutFee: Satoshi) : SwapOut()
data class V0(val paymentRequest: String, val payerKey: String) : Blinded()
}

@Deprecated("channel close are now stored in their own table")
sealed class Closing : OutgoingDetailsData() {
@Serializable
@Suppress("DEPRECATION")
data class V0(
@Serializable val channelId: ByteVector32,
val closingAddress: String,
Expand All @@ -73,18 +74,25 @@ sealed class OutgoingDetailsData {

companion object {
/** Deserialize the details of an outgoing payment. Return null if the details is for a legacy channel closing payment (see [deserializeLegacyClosingDetails]). */
@Suppress("DEPRECATION")
fun deserialize(typeVersion: OutgoingDetailsTypeVersion, blob: ByteArray): LightningOutgoingPayment.Details? = DbTypesHelper.decodeBlob(blob) { json, format ->
when (typeVersion) {
OutgoingDetailsTypeVersion.NORMAL_V0 -> format.decodeFromString<Normal.V0>(json).let { LightningOutgoingPayment.Details.Normal(Bolt11Invoice.read(it.paymentRequest).get()) }
OutgoingDetailsTypeVersion.KEYSEND_V0 -> format.decodeFromString<KeySend.V0>(json).let { LightningOutgoingPayment.Details.KeySend(it.preimage) }
OutgoingDetailsTypeVersion.SWAPOUT_V0 -> format.decodeFromString<SwapOut.V0>(json).let { LightningOutgoingPayment.Details.SwapOut(it.address, Bolt11Invoice.read(it.paymentRequest).get(), it.swapOutFee) }
OutgoingDetailsTypeVersion.NORMAL_V0 -> format.decodeFromString<Normal.V0>(json).let {
LightningOutgoingPayment.Details.Normal(Bolt11Invoice.read(it.paymentRequest).get())
}
OutgoingDetailsTypeVersion.SWAPOUT_V0 -> format.decodeFromString<SwapOut.V0>(json).let {
LightningOutgoingPayment.Details.SwapOut(it.address, Bolt11Invoice.read(it.paymentRequest).get(), it.swapOutFee)
}
OutgoingDetailsTypeVersion.CLOSING_V0 -> null
OutgoingDetailsTypeVersion.BLINDED_V0 -> format.decodeFromString<Blinded.V0>(json).let {
LightningOutgoingPayment.Details.Blinded(
paymentRequest = Bolt12Invoice.fromString(it.paymentRequest).get(),
payerKey = PrivateKey.fromHex(it.payerKey),
)
}
}
}

/** Returns the channel closing details from a blob, for backward-compatibility purposes. */
@Suppress("DEPRECATION")
fun deserializeLegacyClosingDetails(blob: ByteArray): Closing.V0 = DbTypesHelper.decodeBlob(blob) { json, format ->
format.decodeFromString(json)
}
Expand All @@ -94,8 +102,8 @@ sealed class OutgoingDetailsData {
fun LightningOutgoingPayment.Details.mapToDb(): Pair<OutgoingDetailsTypeVersion, ByteArray> = when (this) {
is LightningOutgoingPayment.Details.Normal -> OutgoingDetailsTypeVersion.NORMAL_V0 to
Json.encodeToString(OutgoingDetailsData.Normal.V0(paymentRequest.write())).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Details.KeySend -> OutgoingDetailsTypeVersion.KEYSEND_V0 to
Json.encodeToString(OutgoingDetailsData.KeySend.V0(preimage)).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Details.SwapOut -> OutgoingDetailsTypeVersion.SWAPOUT_V0 to
Json.encodeToString(OutgoingDetailsData.SwapOut.V0(address, paymentRequest.write(), swapOutFee)).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Details.Blinded -> OutgoingDetailsTypeVersion.BLINDED_V0 to
Json.encodeToString(OutgoingDetailsData.Blinded.V0(paymentRequest.write(), payerKey.toHex())).toByteArray(Charsets.UTF_8)
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,14 @@ class CsvWriter {
is IncomingPayment.Origin.OnChain -> {
"Swap-in with inputs: ${origin.localInputs.map { it.txid.toString() } }"
}
is IncomingPayment.Origin.Offer -> {
"Incoming offer ${origin.metadata.offerId}"
}
}
is LightningOutgoingPayment -> when (val details = payment.details) {
is LightningOutgoingPayment.Details.Normal -> "Outgoing LN payment to ${details.paymentRequest.nodeId.toHex()}"
is LightningOutgoingPayment.Details.KeySend -> "Outgoing LN payment (keysend)"
is LightningOutgoingPayment.Details.SwapOut -> "Swap-out to ${details.address}"
is LightningOutgoingPayment.Details.Blinded -> "Offer to ${details.payerKey.publicKey()}"
}
is SpliceOutgoingPayment -> "Outgoing splice to ${payment.address}"
is ChannelCloseOutgoingPayment -> "Channel closing to ${payment.address}"
Expand Down

0 comments on commit 6e3011d

Please sign in to comment.