diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt index 97f91ca..69858c7 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt @@ -274,10 +274,6 @@ class Phoenixd : CliktCommand() { channel_close_outgoing_paymentsAdapter = Channel_close_outgoing_payments.Adapter( closing_info_typeAdapter = EnumColumnAdapter() ), - inbound_liquidity_outgoing_paymentsAdapter = Inbound_liquidity_outgoing_payments.Adapter( - lease_typeAdapter = EnumColumnAdapter(), - payment_details_typeAdapter = EnumColumnAdapter() - ), ) val channelsDb = SqliteChannelsDb(driver, database) val paymentsDb = SqlitePaymentsDb(database) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/DbTypesHelper.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/DbTypesHelper.kt index 316b790..080233a 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/DbTypesHelper.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/DbTypesHelper.kt @@ -36,16 +36,6 @@ object DbTypesHelper { subclass(IncomingReceivedWithData.Part.SpliceIn.V0::class) subclass(IncomingReceivedWithData.Part.FeeCredit.V0::class) } - polymorphic(InboundLiquidityPaymentDetailsData::class) { - subclass(InboundLiquidityPaymentDetailsData.ChannelBalance.V0::class) - subclass(InboundLiquidityPaymentDetailsData.FutureHtlc.V0::class) - subclass(InboundLiquidityPaymentDetailsData.FutureHtlcWithPreimage.V0::class) - subclass(InboundLiquidityPaymentDetailsData.ChannelBalanceForFutureHtlc.V0::class) - } - polymorphic(InboundLiquidityPurchaseData::class) { - subclass(InboundLiquidityPurchaseData.Standard.V0::class) - subclass(InboundLiquidityPurchaseData.WithFeeCredit.V0::class) - } } val polymorphicFormat = Json { serializersModule = module } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityPurchaseType.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityPurchaseType.kt deleted file mode 100644 index d85e64d..0000000 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityPurchaseType.kt +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2023 ACINQ SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:UseSerializers( - ByteVectorSerializer::class, - ByteVector32Serializer::class, - ByteVector64Serializer::class, - SatoshiSerializer::class, - MilliSatoshiSerializer::class -) - -package fr.acinq.lightning.bin.db.payments - -import fr.acinq.bitcoin.ByteVector -import fr.acinq.bitcoin.ByteVector32 -import fr.acinq.bitcoin.ByteVector64 -import fr.acinq.bitcoin.Satoshi -import fr.acinq.lightning.MilliSatoshi -import fr.acinq.lightning.bin.db.serializers.v1.* -import fr.acinq.lightning.db.InboundLiquidityOutgoingPayment -import fr.acinq.lightning.wire.LiquidityAds -import io.ktor.utils.io.charsets.* -import io.ktor.utils.io.core.* -import kotlinx.serialization.PolymorphicSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers - -enum class InboundLiquidityPurchaseType { - @Deprecated("obsolete with the new on-the-fly channel funding that replaces lease -> purchase") - LEASE_V0, - PURCHASE_STANDARD, - PURCHASE_FEE_CREDIT, -} - -enum class InboundLiquidityPaymentDetailsType { - CHANNEL_BALANCE, - FUTURE_HTLC, - FUTURE_HTLC_WITH_PREIMAGE, - CHANNEL_BALANCE_FUTURE_HTLC, -} - -@Suppress("DEPRECATION") -@Deprecated("obsolete with the new on-the-fly channel funding that replaces lease -> purchase") -sealed class InboundLiquidityLeaseData { - - @Serializable - data class V0( - // these legacy data can still be mapped to the new model - val amount: Satoshi, - val miningFees: Satoshi, - val serviceFee: Satoshi, - // the other legacy data are unused and ignored - val sellerSig: ByteVector64, - val witnessFundingScript: ByteVector, - val witnessLeaseDuration: Int, - val witnessLeaseEnd: Int, - val witnessMaxRelayFeeProportional: Int, - val witnessMaxRelayFeeBase: MilliSatoshi - ) : InboundLiquidityLeaseData() -} - -@Serializable -sealed class InboundLiquidityPaymentDetailsData { - sealed class ChannelBalance : InboundLiquidityPaymentDetailsData() { - @Serializable - data object V0 : ChannelBalance() - } - - sealed class FutureHtlc : InboundLiquidityPaymentDetailsData() { - @Serializable - data class V0(val paymentHashes: List) : FutureHtlc() - } - - sealed class FutureHtlcWithPreimage : InboundLiquidityPaymentDetailsData() { - @Serializable - data class V0(val preimages: List) : FutureHtlcWithPreimage() - } - - sealed class ChannelBalanceForFutureHtlc : InboundLiquidityPaymentDetailsData() { - @Serializable - data class V0(val paymentHashes: List) : ChannelBalanceForFutureHtlc() - } - - companion object { - /** Deserializes a json-encoded blob containing a [LiquidityAds.PaymentDetails] object. */ - fun deserialize(blob: ByteArray): LiquidityAds.PaymentDetails = DbTypesHelper.decodeBlob(blob) { json, _ -> - when (val data = DbTypesHelper.polymorphicFormat.decodeFromString(PolymorphicSerializer(InboundLiquidityPaymentDetailsData::class), json)) { - is ChannelBalance.V0 -> LiquidityAds.PaymentDetails.FromChannelBalance - is FutureHtlc.V0 -> LiquidityAds.PaymentDetails.FromFutureHtlc(data.paymentHashes) - is FutureHtlcWithPreimage.V0 -> LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage(data.preimages) - is ChannelBalanceForFutureHtlc.V0 -> LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(data.paymentHashes) - } - } - } -} - -@Serializable -sealed class InboundLiquidityPurchaseData { - - sealed class Standard : InboundLiquidityPurchaseData() { - @Serializable - data class V0( - val amount: Satoshi, - val miningFees: Satoshi, - val serviceFee: Satoshi, - ) : Standard() - } - - sealed class WithFeeCredit : InboundLiquidityPurchaseData() { - @Serializable - data class V0( - val amount: Satoshi, - val miningFees: Satoshi, - val serviceFee: Satoshi, - val feeCreditUsed: MilliSatoshi, - ) : WithFeeCredit() - } - - companion object { - /** - * Deserializes purchase and payment_details json-encoded blobs into a [LiquidityAds.Purchase] object. - * - * @param typeVersion only used for the legacy leased data, where the blob did not contain the type of the object. The "modern" blobs are expected - * to have been created by a polymorphic serializer, hence they already contain the type and do not need the type_version. - * */ - @Suppress("DEPRECATION") - fun deserialize( - typeVersion: InboundLiquidityPurchaseType, - blob: ByteArray, - paymentDetailsBlob: ByteArray, - ): LiquidityAds.Purchase = DbTypesHelper.decodeBlob(blob) { json, format -> - when (typeVersion) { - // map legacy lease data into the modern [LiquidityAds.Purchase] object ; uses fake payment details data - InboundLiquidityPurchaseType.LEASE_V0 -> format.decodeFromString(json).let { - LiquidityAds.Purchase.Standard( - amount = it.amount, - fees = LiquidityAds.Fees(miningFee = it.miningFees, serviceFee = it.serviceFee), - paymentDetails = LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(ByteVector32.Zeroes)) - ) - } - else -> { - when (val data = DbTypesHelper.polymorphicFormat.decodeFromString(PolymorphicSerializer(InboundLiquidityPurchaseData::class), json)) { - is Standard.V0 -> LiquidityAds.Purchase.Standard( - amount = data.amount, - fees = LiquidityAds.Fees(miningFee = data.miningFees, serviceFee = data.serviceFee), - paymentDetails = InboundLiquidityPaymentDetailsData.deserialize(paymentDetailsBlob) - ) - is WithFeeCredit.V0 -> LiquidityAds.Purchase.WithFeeCredit( - amount = data.amount, - fees = LiquidityAds.Fees(miningFee = data.miningFees, serviceFee = data.serviceFee), - feeCreditUsed = data.feeCreditUsed, - paymentDetails = InboundLiquidityPaymentDetailsData.deserialize(paymentDetailsBlob) - ) - } - } - } - } - } -} - -/** - * Maps a [LiquidityAds.Purchase] object into 2 pairs containing the purchase type+json and the payment details type+json. - * - * Note that payment details are mapped to a separate object because we want to store/view these payment details information - * in a separate column in the database (and not inside the purchase blob). - */ -fun InboundLiquidityOutgoingPayment.mapPurchaseToDb(): Pair, - Pair> { - - // map a [LiquidityAds.PaymentDetails] object into the relevant type/data pair. */ - val (detailsType, detailsData) = when (val d = purchase.paymentDetails) { - is LiquidityAds.PaymentDetails.FromChannelBalance -> InboundLiquidityPaymentDetailsType.CHANNEL_BALANCE to InboundLiquidityPaymentDetailsData.ChannelBalance.V0 - is LiquidityAds.PaymentDetails.FromFutureHtlc -> InboundLiquidityPaymentDetailsType.FUTURE_HTLC to InboundLiquidityPaymentDetailsData.FutureHtlc.V0(d.paymentHashes) - is LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage -> InboundLiquidityPaymentDetailsType.FUTURE_HTLC_WITH_PREIMAGE to InboundLiquidityPaymentDetailsData.FutureHtlcWithPreimage.V0(d.preimages) - is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> InboundLiquidityPaymentDetailsType.CHANNEL_BALANCE_FUTURE_HTLC to InboundLiquidityPaymentDetailsData.ChannelBalanceForFutureHtlc.V0(d.paymentHashes) - } - - // map a [LiquidityAds.Purchase] object into the relevant type/data pair. - val (purchaseType, purchaseData) = when (val p = this.purchase) { - is LiquidityAds.Purchase.Standard -> InboundLiquidityPurchaseType.PURCHASE_STANDARD to InboundLiquidityPurchaseData.Standard.V0( - amount = purchase.amount, - miningFees = purchase.fees.miningFee, - serviceFee = purchase.fees.serviceFee, - ) - - is LiquidityAds.Purchase.WithFeeCredit -> InboundLiquidityPurchaseType.PURCHASE_FEE_CREDIT to InboundLiquidityPurchaseData.WithFeeCredit.V0( - amount = purchase.amount, - miningFees = purchase.fees.miningFee, - serviceFee = purchase.fees.serviceFee, - feeCreditUsed = p.feeCreditUsed, - ) - } - - // encode data with a polymorphic serializer - return (purchaseType to purchaseData.let { - DbTypesHelper.polymorphicFormat.encodeToString(PolymorphicSerializer(InboundLiquidityPurchaseData::class), it).toByteArray(Charsets.UTF_8) - }) to (detailsType to detailsData.let { - DbTypesHelper.polymorphicFormat.encodeToString(PolymorphicSerializer(InboundLiquidityPaymentDetailsData::class), it).toByteArray(Charsets.UTF_8) - }) -} diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityQueries.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityQueries.kt index 7ebf768..37fc395 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityQueries.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityQueries.kt @@ -17,10 +17,13 @@ package fr.acinq.lightning.bin.db.payments import fr.acinq.bitcoin.TxId +import fr.acinq.lightning.bin.db.payments.liquidityads.PurchaseData +import fr.acinq.lightning.bin.db.payments.liquidityads.PurchaseData.Companion.encodeForDb import fr.acinq.lightning.db.InboundLiquidityOutgoingPayment import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.toByteVector32 +import fr.acinq.lightning.wire.LiquidityAds import fr.acinq.phoenix.db.PhoenixDatabase class InboundLiquidityQueries(val database: PhoenixDatabase) { @@ -28,16 +31,22 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { fun add(payment: InboundLiquidityOutgoingPayment) { database.transaction { - val (purchase, details) = payment.mapPurchaseToDb() queries.insert( id = payment.id.toString(), mining_fees_sat = payment.miningFees.sat, channel_id = payment.channelId.toByteArray(), tx_id = payment.txId.value.toByteArray(), - lease_type = purchase.first, - lease_blob = purchase.second, - payment_details_type = details.first, - payment_details_blob = details.second, + lease_type = when (payment.purchase) { + is LiquidityAds.Purchase.Standard -> "STANDARD" + is LiquidityAds.Purchase.WithFeeCredit -> "WITH_FEE_CREDIT" + }, + lease_blob = payment.purchase.encodeForDb(), + payment_details_type = when (payment.purchase.paymentDetails) { + is LiquidityAds.PaymentDetails.FromChannelBalance -> "FROM_CHANNEL_BALANCE" + is LiquidityAds.PaymentDetails.FromFutureHtlc -> "FROM_FUTURE_HTLC" + is LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage -> "FROM_FUTURE_HTLC_WITH_PREIMAGE" + is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> "FROM_CHANNEL_BALANCE_FOR_FUTURE_HTLC" + }, created_at = payment.createdAt, confirmed_at = payment.confirmedAt, locked_at = payment.lockedAt, @@ -73,9 +82,9 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { mining_fees_sat: Long, channel_id: ByteArray, tx_id: ByteArray, - lease_type: InboundLiquidityPurchaseType, + lease_type: String, lease_blob: ByteArray, - payment_details_blob: ByteArray, + payment_details_type: String?, created_at: Long, confirmed_at: Long?, locked_at: Long? @@ -85,7 +94,7 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { miningFees = mining_fees_sat.sat, channelId = channel_id.toByteVector32(), txId = TxId(tx_id), - purchase = InboundLiquidityPurchaseData.deserialize(lease_type, lease_blob, payment_details_blob), + purchase = PurchaseData.decodeDataToCanonical(lease_type, lease_blob), createdAt = created_at, confirmedAt = confirmed_at, lockedAt = locked_at diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/IncomingReceivedWithType.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/IncomingReceivedWithType.kt index a4ec7c6..5d00aae 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/IncomingReceivedWithType.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/IncomingReceivedWithType.kt @@ -27,9 +27,11 @@ import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.Satoshi import fr.acinq.bitcoin.TxId import fr.acinq.lightning.MilliSatoshi +import fr.acinq.lightning.bin.db.payments.liquidityads.FundingFeeData +import fr.acinq.lightning.bin.db.payments.liquidityads.FundingFeeData.Companion.asCanonical +import fr.acinq.lightning.bin.db.payments.liquidityads.FundingFeeData.Companion.asDb import fr.acinq.lightning.bin.db.serializers.v1.* import fr.acinq.lightning.db.IncomingPayment -import fr.acinq.lightning.wire.LiquidityAds import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import kotlinx.serialization.* @@ -58,7 +60,7 @@ sealed class IncomingReceivedWithData { val amountReceived: MilliSatoshi, val channelId: ByteVector32, val htlcId: Long, - @Serializable(with = FundingFeeSerializer::class) val fundingFee: LiquidityAds.FundingFee?, + val fundingFee: FundingFeeData?, ) : Htlc() } @@ -116,7 +118,7 @@ sealed class IncomingReceivedWithData { amountReceived = it.amountReceived, channelId = it.channelId, htlcId = it.htlcId, - fundingFee = it.fundingFee + fundingFee = it.fundingFee?.asCanonical() ) is Part.NewChannel.V2 -> IncomingPayment.ReceivedWith.NewChannel( amountReceived = it.amount, @@ -153,7 +155,7 @@ fun List.mapToDb(): Pair IncomingReceivedWithData.Part.NewChannel.V2( amount = it.amountReceived, diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/FundingFeeData.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/FundingFeeData.kt new file mode 100644 index 0000000..12f146c --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/FundingFeeData.kt @@ -0,0 +1,28 @@ +@file:UseSerializers( + MilliSatoshiSerializer::class, + TxIdSerializer::class, +) + +package fr.acinq.lightning.bin.db.payments.liquidityads + +import fr.acinq.bitcoin.TxId +import fr.acinq.lightning.MilliSatoshi +import fr.acinq.lightning.bin.db.serializers.v1.MilliSatoshiSerializer +import fr.acinq.lightning.bin.db.serializers.v1.TxIdSerializer +import fr.acinq.lightning.wire.LiquidityAds +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers + +@Serializable +sealed class FundingFeeData { + + @Serializable + data class V0(val amount: MilliSatoshi, val fundingTxId: TxId) : FundingFeeData() + + companion object { + fun FundingFeeData.asCanonical(): LiquidityAds.FundingFee = when (this) { + is V0 -> LiquidityAds.FundingFee(amount = amount, fundingTxId = fundingTxId) + } + fun LiquidityAds.FundingFee.asDb(): FundingFeeData = V0(amount = amount, fundingTxId = fundingTxId) + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/LegacyLeaseData.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/LegacyLeaseData.kt new file mode 100644 index 0000000..bf1b031 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/LegacyLeaseData.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2023 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:UseSerializers( + ByteVectorSerializer::class, + ByteVector32Serializer::class, + ByteVector64Serializer::class, + SatoshiSerializer::class, + MilliSatoshiSerializer::class +) + +package fr.acinq.lightning.bin.db.payments.liquidityads + +import fr.acinq.bitcoin.ByteVector +import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.bitcoin.ByteVector64 +import fr.acinq.bitcoin.Satoshi +import fr.acinq.lightning.MilliSatoshi +import fr.acinq.lightning.bin.db.serializers.v1.* +import fr.acinq.lightning.wire.LiquidityAds +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers + +enum class InboundLiquidityLeaseType { + @Deprecated("obsolete with the new on-the-fly channel funding that replaces lease -> purchase") + LEASE_V0 +} + +@Suppress("DEPRECATION_WARNING") +@Deprecated("obsolete with the new on-the-fly channel funding that replaces lease with purchase") +@Serializable +data class LeaseV0( + val amount: Satoshi, + val miningFees: Satoshi, + val serviceFee: Satoshi, + val sellerSig: ByteVector64, + val witnessFundingScript: ByteVector, + val witnessLeaseDuration: Int, + val witnessLeaseEnd: Int, + val witnessMaxRelayFeeProportional: Int, + val witnessMaxRelayFeeBase: MilliSatoshi +) { + /** Maps a legacy lease data into the modern [LiquidityAds.Purchase] object using fake payment details data. */ + fun toLiquidityAdsPurchase(): LiquidityAds.Purchase = LiquidityAds.Purchase.Standard( + amount = amount, + fees = LiquidityAds.Fees(miningFee = miningFees, serviceFee = serviceFee), + paymentDetails = LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(ByteVector32.Zeroes)) + ) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PaymentDetailsData.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PaymentDetailsData.kt new file mode 100644 index 0000000..603e355 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PaymentDetailsData.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2024 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:UseSerializers( + ByteVectorSerializer::class, + ByteVector32Serializer::class, + ByteVector64Serializer::class, + SatoshiSerializer::class, + MilliSatoshiSerializer::class +) + +package fr.acinq.lightning.bin.db.payments.liquidityads + +import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.lightning.bin.db.serializers.v1.* +import fr.acinq.lightning.wire.LiquidityAds +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers + + +@Serializable +sealed class PaymentDetailsData { + sealed class ChannelBalance : PaymentDetailsData() { + @Serializable + data object V0 : ChannelBalance() + } + + sealed class FutureHtlc : PaymentDetailsData() { + @Serializable + data class V0(val paymentHashes: List) : FutureHtlc() + } + + sealed class FutureHtlcWithPreimage : PaymentDetailsData() { + @Serializable + data class V0(val preimages: List) : FutureHtlcWithPreimage() + } + + sealed class ChannelBalanceForFutureHtlc : PaymentDetailsData() { + @Serializable + data class V0(val paymentHashes: List) : ChannelBalanceForFutureHtlc() + } + + companion object { + fun PaymentDetailsData.asCanonical(): LiquidityAds.PaymentDetails = when (this) { + is ChannelBalance.V0 -> LiquidityAds.PaymentDetails.FromChannelBalance + is FutureHtlc.V0 -> LiquidityAds.PaymentDetails.FromFutureHtlc(this.paymentHashes) + is FutureHtlcWithPreimage.V0 -> LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage(this.preimages) + is ChannelBalanceForFutureHtlc.V0 -> LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(this.paymentHashes) + } + + fun LiquidityAds.PaymentDetails.asDb(): PaymentDetailsData = when (this) { + is LiquidityAds.PaymentDetails.FromChannelBalance -> ChannelBalance.V0 + is LiquidityAds.PaymentDetails.FromFutureHtlc -> FutureHtlc.V0(this.paymentHashes) + is LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage -> FutureHtlcWithPreimage.V0(this.preimages) + is LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc -> ChannelBalanceForFutureHtlc.V0(this.paymentHashes) + } + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PurchaseData.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PurchaseData.kt new file mode 100644 index 0000000..a8d29d2 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PurchaseData.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2023 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:UseSerializers( + ByteVectorSerializer::class, + ByteVector32Serializer::class, + ByteVector64Serializer::class, + SatoshiSerializer::class, + MilliSatoshiSerializer::class +) + +package fr.acinq.lightning.bin.db.payments.liquidityads + +import fr.acinq.bitcoin.ByteVector +import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.bitcoin.ByteVector64 +import fr.acinq.bitcoin.Satoshi +import fr.acinq.lightning.MilliSatoshi +import fr.acinq.lightning.bin.db.payments.DbTypesHelper +import fr.acinq.lightning.bin.db.payments.liquidityads.PaymentDetailsData.Companion.asCanonical +import fr.acinq.lightning.bin.db.payments.liquidityads.PaymentDetailsData.Companion.asDb +import fr.acinq.lightning.bin.db.serializers.v1.ByteVectorSerializer +import fr.acinq.lightning.bin.db.serializers.v1.ByteVector32Serializer +import fr.acinq.lightning.bin.db.serializers.v1.ByteVector64Serializer +import fr.acinq.lightning.bin.db.serializers.v1.SatoshiSerializer +import fr.acinq.lightning.bin.db.serializers.v1.MilliSatoshiSerializer +import fr.acinq.lightning.wire.LiquidityAds +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.text.toByteArray + + +sealed class PurchaseData { + sealed class Standard : PurchaseData() { + @Serializable + data class V0( + val amount: Satoshi, + val miningFees: Satoshi, + val serviceFee: Satoshi, + val paymentDetails: PaymentDetailsData, + ) : Standard() + } + sealed class WithFeeCredit : PurchaseData() { + @Serializable + data class V0( + val amount: Satoshi, + val miningFees: Satoshi, + val serviceFee: Satoshi, + val feeCreditUsed: MilliSatoshi, + val paymentDetails: PaymentDetailsData, + ) : WithFeeCredit() + } + + companion object { + private fun PurchaseData.asCanonical(): LiquidityAds.Purchase = when (this) { + is Standard.V0 -> LiquidityAds.Purchase.Standard( + amount = amount, + fees = LiquidityAds.Fees(miningFee = miningFees, serviceFee = serviceFee), + paymentDetails = paymentDetails.asCanonical() + ) + is WithFeeCredit.V0 -> LiquidityAds.Purchase.WithFeeCredit( + amount = amount, + fees = LiquidityAds.Fees(miningFee = miningFees, serviceFee = serviceFee), + feeCreditUsed = feeCreditUsed, + paymentDetails = paymentDetails.asCanonical() + ) + } + + private fun LiquidityAds.Purchase.asDb(): PurchaseData = when (val value = this) { + is LiquidityAds.Purchase.Standard -> Standard.V0( + amount = value.amount, + miningFees = value.fees.miningFee, + serviceFee = value.fees.serviceFee, + paymentDetails = value.paymentDetails.asDb() + ) + is LiquidityAds.Purchase.WithFeeCredit -> WithFeeCredit.V0( + amount = value.amount, value.fees.miningFee, + serviceFee = value.fees.serviceFee, + paymentDetails = value.paymentDetails.asDb(), + feeCreditUsed = value.feeCreditUsed + ) + } + + /** + * Deserializes a json-encoded blob into a [LiquidityAds.Purchase] object. + * + * @param typeVersion only used for the legacy leased data, where the blob did not contain the type of the object. + */ + @Suppress("DEPRECATION") + fun decodeDataToCanonical( + typeVersion: String, + blob: ByteArray, + ): LiquidityAds.Purchase = DbTypesHelper.decodeBlob(blob) { json, format -> + when (typeVersion) { + InboundLiquidityLeaseType.LEASE_V0.name -> format.decodeFromString(json).toLiquidityAdsPurchase() + else -> format.decodeFromString(json).asCanonical() + } + } + + fun LiquidityAds.Purchase.encodeForDb(): ByteArray = Json.encodeToString(this.asDb()).toByteArray(Charsets.UTF_8) + } +} diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/FundingFeeSerializer.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/FundingFeeSerializer.kt deleted file mode 100644 index 1433d7a..0000000 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/FundingFeeSerializer.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024 ACINQ SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package fr.acinq.lightning.bin.db.serializers.v1 - -import fr.acinq.bitcoin.TxId -import fr.acinq.lightning.MilliSatoshi -import fr.acinq.lightning.wire.LiquidityAds -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder - -object TxIdSerializer : AbstractStringSerializer( - name = "TxId", - toString = TxId::toString, - fromString = ::TxId -) - -object FundingFeeSerializer : KSerializer { - - @Serializable - private data class FundingFeeSurrogate( - @Serializable(with = MilliSatoshiSerializer::class) val amount: MilliSatoshi, - @Serializable(with = TxIdSerializer::class) val fundingTxId: TxId - ) - - override val descriptor: SerialDescriptor = FundingFeeSurrogate.serializer().descriptor - - override fun serialize(encoder: Encoder, value: LiquidityAds.FundingFee) { - val surrogate = FundingFeeSurrogate(amount = value.amount, fundingTxId = value.fundingTxId) - return encoder.encodeSerializableValue(FundingFeeSurrogate.serializer(), surrogate) - } - - override fun deserialize(decoder: Decoder): LiquidityAds.FundingFee { - val surrogate = decoder.decodeSerializableValue(FundingFeeSurrogate.serializer()) - return LiquidityAds.FundingFee(amount = surrogate.amount, fundingTxId = surrogate.fundingTxId) - } -} diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/TxIdSerializer.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/TxIdSerializer.kt new file mode 100644 index 0000000..de17a5f --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/serializers/v1/TxIdSerializer.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.lightning.bin.db.serializers.v1 + +import fr.acinq.bitcoin.TxId + +object TxIdSerializer : AbstractStringSerializer( + name = "TxId", + toString = TxId::toString, + fromString = ::TxId +) diff --git a/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq b/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq index 58a3635..6e3f7d0 100644 --- a/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq +++ b/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq @@ -1,5 +1,4 @@ -import fr.acinq.lightning.bin.db.payments.InboundLiquidityPurchaseType; -import fr.acinq.lightning.bin.db.payments.InboundLiquidityPaymentDetailsType; +import fr.acinq.lightning.bin.db.payments.liquidityads.InboundLiquidityPurchaseType; -- Stores in a flat row payments standing for an inbound liquidity request (which are done through a splice). -- The purchase data are stored in a complex column, as a json-encoded blob. See InboundLiquidityLeaseType file. @@ -11,10 +10,9 @@ CREATE TABLE inbound_liquidity_outgoing_payments ( mining_fees_sat INTEGER NOT NULL, channel_id BLOB NOT NULL, tx_id BLOB NOT NULL, - lease_type TEXT AS InboundLiquidityPurchaseType NOT NULL, + lease_type TEXT NOT NULL, lease_blob BLOB NOT NULL, - payment_details_type TEXT AS InboundLiquidityPaymentDetailsType NOT NULL, - payment_details_blob BLOB NOT NULL, + payment_details_type TEXT DEFAULT NULL, created_at INTEGER NOT NULL, confirmed_at INTEGER DEFAULT NULL, locked_at INTEGER DEFAULT NULL @@ -22,8 +20,8 @@ CREATE TABLE inbound_liquidity_outgoing_payments ( insert: INSERT INTO inbound_liquidity_outgoing_payments ( - id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_type, payment_details_blob, created_at, confirmed_at, locked_at -) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); + id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_type, created_at, confirmed_at, locked_at +) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); setConfirmed: UPDATE inbound_liquidity_outgoing_payments SET confirmed_at=? WHERE id=?; @@ -32,12 +30,12 @@ setLocked: UPDATE inbound_liquidity_outgoing_payments SET locked_at=? WHERE id=?; get: -SELECT id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_blob, created_at, confirmed_at, locked_at +SELECT id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_type, created_at, confirmed_at, locked_at FROM inbound_liquidity_outgoing_payments WHERE id=?; getByTxId: -SELECT id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_blob, created_at, confirmed_at, locked_at +SELECT id, mining_fees_sat, channel_id, tx_id, lease_type, lease_blob, payment_details_type, created_at, confirmed_at, locked_at FROM inbound_liquidity_outgoing_payments WHERE tx_id=?; diff --git a/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm b/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm index 690f424..03b9184 100644 --- a/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm +++ b/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm @@ -1,4 +1,4 @@ -import fr.acinq.lightning.bin.db.payments.InboundLiquidityPaymentDetailsType; +import fr.acinq.lightning.bin.db.payments.liquidityads.InboundLiquidityPaymentDetailsType; -- Migration: v2 -> v3 -- @@ -8,5 +8,4 @@ import fr.acinq.lightning.bin.db.payments.InboundLiquidityPaymentDetailsType; -- * Added column [payment_details_type] in table [inbound_liquidity_outgoing_payments] -- * Added column [payment_details_blob] in table [inbound_liquidity_outgoing_payments] -ALTER TABLE inbound_liquidity_outgoing_payments ADD COLUMN payment_details_type TEXT AS InboundLiquidityPaymentDetailsTypeVersion NOT NULL; -ALTER TABLE inbound_liquidity_outgoing_payments ADD COLUMN payment_details_blob BLOB NOT NULL; +ALTER TABLE inbound_liquidity_outgoing_payments ADD COLUMN payment_details_type TEXT DEFAULT NULL;