diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 94ea9bf..7d046bb 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,6 +1,6 @@ object Versions { val kotlin = "1.9.23" - val lightningKmp = "1.7.3-FEECREDIT-11" + val lightningKmp = "1.8.0" val sqlDelight = "2.0.1" val okio = "3.8.0" val clikt = "4.2.2" diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt index e6ba690..4d0cc7e 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/Api.kt @@ -27,6 +27,7 @@ import fr.acinq.lightning.bin.payments.lnurl.models.LnurlWithdraw import fr.acinq.lightning.blockchain.fee.FeeratePerByte import fr.acinq.lightning.blockchain.fee.FeeratePerKw import fr.acinq.lightning.channel.ChannelCommand +import fr.acinq.lightning.channel.ChannelFundingResponse import fr.acinq.lightning.channel.states.ChannelStateWithCommitments import fr.acinq.lightning.channel.states.Closed import fr.acinq.lightning.channel.states.Closing @@ -162,13 +163,19 @@ class Api( .filterNot { it is Closing || it is Closed } .map { it.commitments.active.first().availableBalanceForSend(it.commitments.params, it.commitments.changes) } .sum().truncateToSatoshi() - call.respond(Balance(balance, nodeParams.feeCredit.value)) + call.respond(Balance(balance, peer.feeCreditFlow.value.truncateToSatoshi())) } get("estimateliquidityfees") { val amount = call.parameters.getLong("amountSat").sat val feerate = peer.onChainFeeratesFlow.filterNotNull().first().fundingFeerate - val liquidityFees = LSP.liquidityFees(amount, feerate, isNew = peer.channels.isEmpty()) - call.respond(LiquidityFees(liquidityFees)) + val fundingRates = peer.remoteFundingRates.filterNotNull().first() + when (val fundingRate = fundingRates.findRate(amount)) { + null -> badRequest("no available funding rates for amount=$amount") + else -> { + val liquidityFees = fundingRate.fees(feerate, amount, amount, isChannelCreation = peer.channels.isEmpty()) + call.respond(LiquidityFees(liquidityFees)) + } + } } get("listchannels") { call.respond(peer.channels.values.toList()) @@ -388,8 +395,8 @@ class Api( }.toEither() when (res) { is Either.Right -> when (val r = res.value) { - is ChannelCommand.Commitment.Splice.Response.Created -> call.respondText(r.fundingTxId.toString()) - is ChannelCommand.Commitment.Splice.Response.Failure -> call.respondText(r.toString()) + is ChannelFundingResponse.Success -> call.respondText(r.fundingTxId.toString()) + is ChannelFundingResponse.Failure -> call.respondText(r.toString()) else -> call.respondText("no channel available") } is Either.Left -> call.respondText(res.value.message.toString()) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt index 11d6ae8..fdc90a0 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/Main.kt @@ -40,17 +40,13 @@ import fr.acinq.lightning.bin.logs.stringTimestamp import fr.acinq.lightning.blockchain.mempool.MempoolSpaceClient import fr.acinq.lightning.blockchain.mempool.MempoolSpaceWatcher import fr.acinq.lightning.crypto.LocalKeyManager -import fr.acinq.lightning.db.ChannelsDb -import fr.acinq.lightning.db.Databases -import fr.acinq.lightning.db.PaymentsDb +import fr.acinq.lightning.db.* import fr.acinq.lightning.io.Peer import fr.acinq.lightning.io.TcpSocket import fr.acinq.lightning.logging.LoggerFactory import fr.acinq.lightning.payment.LiquidityPolicy -import fr.acinq.lightning.utils.Connection -import fr.acinq.lightning.utils.msat -import fr.acinq.lightning.utils.sat -import fr.acinq.lightning.utils.toByteVector +import fr.acinq.lightning.utils.* +import fr.acinq.lightning.wire.LiquidityAds import fr.acinq.phoenix.db.* import io.ktor.http.* import io.ktor.server.application.* @@ -90,14 +86,14 @@ class Phoenixd : CliktCommand() { } private val agreeToTermsOfService by option("--agree-to-terms-of-service", hidden = true, help = "Agree to terms of service").flag() private val chain by option("--chain", help = "Bitcoin chain to use").choice( - "mainnet" to Chain.Mainnet, "testnet" to Chain.Testnet + "mainnet" to Chain.Mainnet, "testnet" to Chain.Testnet3 ).default(Chain.Mainnet, defaultForHelp = "mainnet") private val mempoolSpaceUrl by option("--mempool-space-url", help = "Custom mempool.space instance") .convert { Url(it) } .defaultLazy { when (chain) { Chain.Mainnet -> MempoolSpaceClient.OfficialMempoolMainnet - Chain.Testnet -> MempoolSpaceClient.OfficialMempoolTestnet + Chain.Testnet3 -> MempoolSpaceClient.OfficialMempoolTestnet else -> error("unsupported chain") } } @@ -155,7 +151,7 @@ class Phoenixd : CliktCommand() { "off" to 0.sat, "50k" to 50_000.sat, "100k" to 100_000.sat, - ).default(100_000.sat, "100k") + ).convert { it.toMilliSatoshi() }.default(100_000.sat.toMilliSatoshi(), "100k") private val maxRelativeFeePct by option("--max-relative-fee-percent", help = "Max relative fee for on-chain operations in percent.", hidden = true) .int() .restrictTo(1..50) @@ -244,10 +240,11 @@ class Phoenixd : CliktCommand() { ) val lsp = LSP.from(chain) val liquidityPolicy = LiquidityPolicy.Auto( - maxMiningFee = liquidityOptions.maxMiningFee, + inboundLiquidityTarget = liquidityOptions.autoLiquidity, + maxAbsoluteFee = liquidityOptions.maxMiningFee, maxRelativeFeeBasisPoints = liquidityOptions.maxRelativeFeeBasisPoints, - skipMiningFeeCheck = false, - maxAllowedCredit = liquidityOptions.maxFeeCredit + skipAbsoluteFeeCheck = false, + maxAllowedFeeCredit = liquidityOptions.maxFeeCredit ) val keyManager = LocalKeyManager(seed.seed, chain, lsp.swapInXpub) val nodeParams = NodeParams(chain, loggerFactory, keyManager) @@ -276,9 +273,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() - ) ) val channelsDb = SqliteChannelsDb(driver, database) val paymentsDb = SqlitePaymentsDb(database) @@ -324,39 +318,59 @@ class Phoenixd : CliktCommand() { } launch { nodeParams.nodeEvents - .filterIsInstance() - .filter { it.amount > 0.msat } + .filterIsInstance() .collect { - consoleLog("received lightning payment: ${it.amount.truncateToSatoshi()} (${it.receivedWith.joinToString { part -> part::class.simpleName.toString().lowercase() }})") + when (it) { + is PaymentEvents.PaymentReceived -> { + val fee = it.receivedWith.filterIsInstance().map { it.fundingFee?.amount ?: 0.msat }.sum().truncateToSatoshi() + val type = it.receivedWith.joinToString { part -> part::class.simpleName.toString().lowercase() } + consoleLog("received lightning payment: ${it.amount.truncateToSatoshi()} ($type${if (fee > 0.sat) " fee=$fee" else ""})") + } + is PaymentEvents.PaymentSent -> + when (val payment = it.payment) { + is InboundLiquidityOutgoingPayment -> { + val totalFee = payment.fees.truncateToSatoshi() + val feePaidFromBalance = payment.feePaidFromChannelBalance.total + val feePaidFromFeeCredit = payment.feeCreditUsed.truncateToSatoshi() + val feeRemaining = totalFee - feePaidFromBalance - feePaidFromFeeCredit + val purchaseType = payment.purchase.paymentDetails.paymentType::class.simpleName.toString().lowercase() + consoleLog("purchased inbound liquidity: ${payment.purchase.amount} (totalFee=$totalFee feePaidFromBalance=$feePaidFromBalance feePaidFromFeeCredit=$feePaidFromFeeCredit feeRemaining=$feeRemaining purchaseType=$purchaseType)") + } + else -> {} + } + } } } launch { nodeParams.nodeEvents - .filterIsInstance() + .filterIsInstance() .collect { when (val reason = it.reason) { - is LiquidityEvents.Decision.Rejected.Reason.OverMaxCredit -> { - consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): over max fee credit (max=${reason.maxAllowedCredit})")) - } - is LiquidityEvents.Decision.Rejected.Reason.TooExpensive.OverMaxMiningFee -> { - consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): over max mining fee (max=${reason.maxMiningFee})")) - } - is LiquidityEvents.Decision.Rejected.Reason.TooExpensive.OverRelativeFee -> { - consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): fee=${it.fee.truncateToSatoshi()} more than ${reason.maxRelativeFeeBasisPoints.toDouble() / 100}% of amount")) - } - LiquidityEvents.Decision.Rejected.Reason.ChannelInitializing -> { - consoleLog(yellow("channels are initializing")) - } - LiquidityEvents.Decision.Rejected.Reason.PolicySetToDisabled -> { + // TODO: put this back after rework of LiquidityPolicy to handle fee credit +// is LiquidityEvents.Rejected.Reason.OverMaxCredit -> { +// consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): over max fee credit (max=${reason.maxAllowedCredit})")) +// } + is LiquidityEvents.Rejected.Reason.TooExpensive.OverAbsoluteFee -> + consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): over absolute fee (fee=${it.fee.truncateToSatoshi()} max=${reason.maxAbsoluteFee})")) + is LiquidityEvents.Rejected.Reason.TooExpensive.OverRelativeFee -> + consoleLog(yellow("lightning payment rejected (amount=${it.amount.truncateToSatoshi()}): over relative fee (fee=${it.fee.truncateToSatoshi()} max=${reason.maxRelativeFeeBasisPoints.toDouble() / 100}%)")) + LiquidityEvents.Rejected.Reason.PolicySetToDisabled -> consoleLog(yellow("automated liquidity is disabled")) - } + LiquidityEvents.Rejected.Reason.ChannelFundingInProgress -> + consoleLog(yellow("channel operation is in progress")) + is LiquidityEvents.Rejected.Reason.MissingOffChainAmountTooLow -> + consoleLog(yellow("missing offchain amount is too low (missingOffChainAmount=${reason.missingOffChainAmount} currentFeeCredit=${reason.currentFeeCredit}")) + LiquidityEvents.Rejected.Reason.NoMatchingFundingRate -> + consoleLog(yellow("no matching funding rates")) + is LiquidityEvents.Rejected.Reason.TooManyParts -> + consoleLog(yellow("too many payment parts")) } } } launch { - nodeParams.feeCredit - .drop(1) // we drop the initial value which is 0 sat - .collect { feeCredit -> consoleLog("fee credit: $feeCredit") } + peer.feeCreditFlow + .drop(1) // we drop the initial value which is 0 msat + .collect { feeCredit -> consoleLog("fee credit: ${feeCredit.truncateToSatoshi()}") } } } @@ -370,8 +384,6 @@ class Phoenixd : CliktCommand() { runBlocking { peer.connectionState.first { it == Connection.ESTABLISHED } - peer.registerFcmToken("super-${randomBytes32().toHex()}") - peer.setAutoLiquidityParams(liquidityOptions.autoLiquidity) } val server = embeddedServer(CIO, port = httpBindPort, host = httpBindIp, diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/conf/Lsp.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/conf/Lsp.kt index 4c43db7..a55f417 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/conf/Lsp.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/conf/Lsp.kt @@ -2,12 +2,9 @@ package fr.acinq.lightning.bin.conf import fr.acinq.bitcoin.Chain import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.Satoshi import fr.acinq.lightning.* -import fr.acinq.lightning.blockchain.fee.FeeratePerKw import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.sat -import fr.acinq.lightning.wire.LiquidityAds data class LSP(val walletParams: WalletParams, val swapInXpub: String) { @@ -44,7 +41,7 @@ data class LSP(val walletParams: WalletParams, val swapInXpub: String) { swapInParams ) ) - is Chain.Testnet -> LSP( + is Chain.Testnet3 -> LSP( swapInXpub = "tpubDAmCFB21J9ExKBRPDcVxSvGs9jtcf8U1wWWbS1xTYmnUsuUHPCoFdCnEGxLE3THSWcQE48GHJnyz8XPbYUivBMbLSMBifFd3G9KmafkM9og", walletParams = WalletParams( trampolineNode = NodeUri(PublicKey.fromHex("03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134"), "13.248.222.197", 9735), @@ -55,35 +52,5 @@ data class LSP(val walletParams: WalletParams, val swapInXpub: String) { ) else -> error("unsupported chain $chain") } - - fun liquidityFees(amount: Satoshi, feerate: FeeratePerKw, isNew: Boolean): LiquidityAds.LeaseFees { - val creationFee = if (isNew) 1_000.sat else 0.sat - val leaseRate = liquidityLeaseRate(amount) - val leaseFees = leaseRate.fees(feerate, requestedAmount = amount, contributedAmount = amount) - return leaseFees.copy(serviceFee = creationFee + leaseFees.serviceFee) - } - - private fun liquidityLeaseRate(amount: Satoshi): LiquidityAds.LeaseRate { - // WARNING : THIS MUST BE KEPT IN SYNC WITH LSP OTHERWISE FUNDING REQUEST WILL BE REJECTED BY PHOENIX - val fundingWeight = if (amount <= 100_000.sat) { - 271 * 2 // 2-inputs (wpkh) / 0-change - } else if (amount <= 250_000.sat) { - 271 * 2 // 2-inputs (wpkh) / 0-change - } else if (amount <= 500_000.sat) { - 271 * 4 // 4-inputs (wpkh) / 0-change - } else if (amount <= 1_000_000.sat) { - 271 * 4 // 4-inputs (wpkh) / 0-change - } else { - 271 * 6 // 6-inputs (wpkh) / 0-change - } - return LiquidityAds.LeaseRate( - leaseDuration = 0, - fundingWeight = fundingWeight, - leaseFeeProportional = 100, // 1% - leaseFeeBase = 0.sat, - maxRelayFeeProportional = 100, - maxRelayFeeBase = 1_000.msat - ) - } } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/SqlitePaymentsDb.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/SqlitePaymentsDb.kt index 18ad171..e081a16 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/SqlitePaymentsDb.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/SqlitePaymentsDb.kt @@ -98,6 +98,12 @@ class SqlitePaymentsDb(val database: PhoenixDatabase) : PaymentsDb { } } + override suspend fun getInboundLiquidityPurchase(fundingTxId: TxId): InboundLiquidityOutgoingPayment? { + return withContext(Dispatchers.Default) { + inboundLiquidityQueries.getByTxId(fundingTxId) + } + } + override suspend fun completeOutgoingPaymentOffchain( id: UUID, finalFailure: FinalFailure, 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 3947f46..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 @@ -29,7 +29,9 @@ object DbTypesHelper { val module = SerializersModule { polymorphic(IncomingReceivedWithData.Part::class) { + @Suppress("DEPRECATION") subclass(IncomingReceivedWithData.Part.Htlc.V0::class) + subclass(IncomingReceivedWithData.Part.Htlc.V1::class) subclass(IncomingReceivedWithData.Part.NewChannel.V2::class) subclass(IncomingReceivedWithData.Part.SpliceIn.V0::class) subclass(IncomingReceivedWithData.Part.FeeCredit.V0::class) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityLeaseType.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityLeaseType.kt deleted file mode 100644 index 2801eb9..0000000 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/InboundLiquidityLeaseType.kt +++ /dev/null @@ -1,103 +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.ByteVector64 -import fr.acinq.bitcoin.Satoshi -import fr.acinq.lightning.MilliSatoshi -import fr.acinq.lightning.db.InboundLiquidityOutgoingPayment -import fr.acinq.lightning.wire.LiquidityAds -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.ByteVectorSerializer -import fr.acinq.lightning.bin.db.serializers.v1.MilliSatoshiSerializer -import fr.acinq.lightning.bin.db.serializers.v1.SatoshiSerializer -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.toByteArray -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -enum class InboundLiquidityLeaseTypeVersion { - LEASE_V0, -} - -sealed class InboundLiquidityLeaseData { - - @Serializable - data class V0( - 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 - ) : InboundLiquidityLeaseData() - - companion object { - /** Deserializes a json-encoded blob containing data for an [LiquidityAds.Lease] object. */ - fun deserialize( - typeVersion: InboundLiquidityLeaseTypeVersion, - blob: ByteArray, - ): LiquidityAds.Lease = DbTypesHelper.decodeBlob(blob) { json, format -> - when (typeVersion) { - InboundLiquidityLeaseTypeVersion.LEASE_V0 -> format.decodeFromString(json).let { - LiquidityAds.Lease( - amount = it.amount, - fees = LiquidityAds.LeaseFees(miningFee = it.miningFees, serviceFee = it.serviceFee), - sellerSig = it.sellerSig, - witness = LiquidityAds.LeaseWitness( - fundingScript = it.witnessFundingScript, - leaseDuration = it.witnessLeaseDuration, - leaseEnd = it.witnessLeaseEnd, - maxRelayFeeProportional = it.witnessMaxRelayFeeProportional, - maxRelayFeeBase = it.witnessMaxRelayFeeBase, - ) - ) - } - } - } - } -} - -fun InboundLiquidityOutgoingPayment.mapLeaseToDb() = InboundLiquidityLeaseTypeVersion.LEASE_V0 to - InboundLiquidityLeaseData.V0( - amount = lease.amount, - miningFees = lease.fees.miningFee, - serviceFee = lease.fees.serviceFee, - sellerSig = lease.sellerSig, - witnessFundingScript = lease.witness.fundingScript, - witnessLeaseDuration = lease.witness.leaseDuration, - witnessLeaseEnd = lease.witness.leaseEnd, - witnessMaxRelayFeeProportional = lease.witness.maxRelayFeeProportional, - witnessMaxRelayFeeBase = lease.witness.maxRelayFeeBase, - ).let { - Json.encodeToString(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 3600e7e..835d8a8 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.encodeAsDb 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,14 +31,22 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { fun add(payment: InboundLiquidityOutgoingPayment) { database.transaction { - val (leaseType, leaseData) = payment.mapLeaseToDb() 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 = leaseType, - lease_blob = leaseData, + lease_type = when (payment.purchase) { + is LiquidityAds.Purchase.Standard -> "STANDARD" + is LiquidityAds.Purchase.WithFeeCredit -> "WITH_FEE_CREDIT" + }, + lease_blob = payment.purchase.encodeAsDb(), + 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, @@ -48,6 +59,11 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { .executeAsOneOrNull() } + fun getByTxId(txId: TxId): InboundLiquidityOutgoingPayment? { + return queries.getByTxId(tx_id = txId.value.toByteArray(), mapper = Companion::mapPayment) + .executeAsOneOrNull() + } + fun setConfirmed(id: UUID, confirmedAt: Long) { database.transaction { queries.setConfirmed(confirmed_at = confirmedAt, id = id.toString()) @@ -66,8 +82,9 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { mining_fees_sat: Long, channel_id: ByteArray, tx_id: ByteArray, - lease_type: InboundLiquidityLeaseTypeVersion, + lease_type: String, lease_blob: ByteArray, + @Suppress("UNUSED_PARAMETER") payment_details_type: String?, created_at: Long, confirmed_at: Long?, locked_at: Long? @@ -77,7 +94,7 @@ class InboundLiquidityQueries(val database: PhoenixDatabase) { miningFees = mining_fees_sat.sat, channelId = channel_id.toByteVector32(), txId = TxId(tx_id), - lease = InboundLiquidityLeaseData.deserialize(lease_type, lease_blob), + purchase = PurchaseData.decodeAsCanonical(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 9e58443..c716f0b 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,11 +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.bin.db.serializers.v1.ByteVector32Serializer -import fr.acinq.lightning.bin.db.serializers.v1.MilliSatoshiSerializer -import fr.acinq.lightning.bin.db.serializers.v1.UUIDSerializer -import fr.acinq.lightning.bin.db.serializers.v1.SatoshiSerializer import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import kotlinx.serialization.* @@ -47,12 +47,21 @@ sealed class IncomingReceivedWithData { @Serializable sealed class Part : IncomingReceivedWithData() { sealed class Htlc : Part() { + @Deprecated("Replaced by [Htlc.V1], which supports the liquidity ads funding fee") @Serializable data class V0( @Serializable val amount: MilliSatoshi, @Serializable val channelId: ByteVector32, val htlcId: Long ) : Htlc() + + @Serializable + data class V1( + val amountReceived: MilliSatoshi, + val channelId: ByteVector32, + val htlcId: Long, + val fundingFee: FundingFeeData?, + ) : Htlc() } sealed class NewChannel : Part() { @@ -98,14 +107,22 @@ sealed class IncomingReceivedWithData { ): List = DbTypesHelper.decodeBlob(blob) { json, _ -> when (typeVersion) { IncomingReceivedWithTypeVersion.MULTIPARTS_V1 -> DbTypesHelper.polymorphicFormat.decodeFromString(SetSerializer(PolymorphicSerializer(Part::class)), json).map { + @Suppress("DEPRECATION") when (it) { is Part.Htlc.V0 -> IncomingPayment.ReceivedWith.LightningPayment( - amount = it.amount, + amountReceived = it.amount, + channelId = it.channelId, + htlcId = it.htlcId, + fundingFee = null + ) + is Part.Htlc.V1 -> IncomingPayment.ReceivedWith.LightningPayment( + amountReceived = it.amountReceived, channelId = it.channelId, - htlcId = it.htlcId + htlcId = it.htlcId, + fundingFee = it.fundingFee?.asCanonical() ) is Part.NewChannel.V2 -> IncomingPayment.ReceivedWith.NewChannel( - amount = it.amount, + amountReceived = it.amount, serviceFee = it.serviceFee, miningFee = it.miningFee, channelId = it.channelId, @@ -114,7 +131,7 @@ sealed class IncomingReceivedWithData { lockedAt = it.lockedAt, ) is Part.SpliceIn.V0 -> IncomingPayment.ReceivedWith.SpliceIn( - amount = it.amount, + amountReceived = it.amount, serviceFee = it.serviceFee, miningFee = it.miningFee, channelId = it.channelId, @@ -122,8 +139,8 @@ sealed class IncomingReceivedWithData { confirmedAt = it.confirmedAt, lockedAt = it.lockedAt, ) - is Part.FeeCredit.V0 -> IncomingPayment.ReceivedWith.FeeCreditPayment( - amount = it.amount + is Part.FeeCredit.V0 -> IncomingPayment.ReceivedWith.AddedToFeeCredit( + amountReceived = it.amount ) } } @@ -135,13 +152,14 @@ sealed class IncomingReceivedWithData { /** Only serialize received_with into the [IncomingReceivedWithTypeVersion.MULTIPARTS_V1] type. */ fun List.mapToDb(): Pair? = map { when (it) { - is IncomingPayment.ReceivedWith.LightningPayment -> IncomingReceivedWithData.Part.Htlc.V0( - amount = it.amount, + is IncomingPayment.ReceivedWith.LightningPayment -> IncomingReceivedWithData.Part.Htlc.V1( + amountReceived = it.amountReceived, channelId = it.channelId, - htlcId = it.htlcId + htlcId = it.htlcId, + fundingFee = it.fundingFee?.asDb() ) is IncomingPayment.ReceivedWith.NewChannel -> IncomingReceivedWithData.Part.NewChannel.V2( - amount = it.amount, + amount = it.amountReceived, serviceFee = it.serviceFee, miningFee = it.miningFee, channelId = it.channelId, @@ -150,7 +168,7 @@ fun List.mapToDb(): Pair IncomingReceivedWithData.Part.SpliceIn.V0( - amount = it.amount, + amount = it.amountReceived, serviceFee = it.serviceFee, miningFee = it.miningFee, channelId = it.channelId, @@ -158,8 +176,8 @@ fun List.mapToDb(): Pair IncomingReceivedWithData.Part.FeeCredit.V0( - amount = it.amount + is IncomingPayment.ReceivedWith.AddedToFeeCredit -> IncomingReceivedWithData.Part.FeeCredit.V0( + amount = it.amountReceived ) } }.takeIf { it.isNotEmpty() }?.toSet()?.let { 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..65737d5 --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PaymentDetailsData.kt @@ -0,0 +1,67 @@ +/* + * 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( + ByteVector32Serializer::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..1bf551b --- /dev/null +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/db/payments/liquidityads/PurchaseData.kt @@ -0,0 +1,106 @@ +/* + * 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( + SatoshiSerializer::class, + MilliSatoshiSerializer::class +) + +package fr.acinq.lightning.bin.db.payments.liquidityads + +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.* +import fr.acinq.lightning.wire.LiquidityAds +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +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 decodeAsCanonical( + 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.encodeAsDb(): ByteArray = Json.encodeToString(this.asDb()).encodeToByteArray() + } +} 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/kotlin/fr/acinq/lightning/bin/json/JsonSerializers.kt b/src/commonMain/kotlin/fr/acinq/lightning/bin/json/JsonSerializers.kt index 22819c2..aa15290 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/bin/json/JsonSerializers.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/bin/json/JsonSerializers.kt @@ -77,7 +77,7 @@ sealed class ApiType { @Serializable data class LiquidityFees(@SerialName("miningFeeSat") val miningFee: Satoshi, @SerialName("serviceFeeSat") val serviceFee: Satoshi) : ApiType() { - constructor(leaseFees: LiquidityAds.LeaseFees) : this(leaseFees.miningFee, leaseFees.serviceFee) + constructor(leaseFees: LiquidityAds.Fees) : this(leaseFees.miningFee, leaseFees.serviceFee) } @Serializable 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 d726016..e2ae404 100644 --- a/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq +++ b/src/commonMain/sqldelight/phoenixdb/fr/acinq/phoenix/db/InboundLiquidityOutgoingPayments.sq @@ -1,14 +1,16 @@ -import fr.acinq.lightning.bin.db.payments.InboundLiquidityLeaseTypeVersion; - -- Stores in a flat row payments standing for an inbound liquidity request (which are done through a splice). --- The lease data are stored in a complex column, as a json-encoded blob. See InboundLiquidityLeaseType file. +-- The purchase data are stored in a complex column, as a json-encoded blob. See InboundLiquidityLeaseType file. +-- +-- Note that these purchase data used to be called "lease" in the old on-the-fly channel mechanism, that's why the +-- colum uses that name, it's a legacy artifact. They now map to a "LiquidityAds.Purchase" object. CREATE TABLE inbound_liquidity_outgoing_payments ( id TEXT NOT NULL PRIMARY KEY, mining_fees_sat INTEGER NOT NULL, channel_id BLOB NOT NULL, tx_id BLOB NOT NULL, - lease_type TEXT AS InboundLiquidityLeaseTypeVersion NOT NULL, + lease_type TEXT NOT NULL, lease_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 @@ -16,8 +18,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, 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=?; @@ -26,9 +28,14 @@ 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, 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_type, created_at, confirmed_at, locked_at +FROM inbound_liquidity_outgoing_payments +WHERE tx_id=?; + delete: DELETE FROM inbound_liquidity_outgoing_payments WHERE id=?; diff --git a/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm b/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm new file mode 100644 index 0000000..f66391e --- /dev/null +++ b/src/commonMain/sqldelight/phoenixdb/migrations/2.sqm @@ -0,0 +1,6 @@ +-- Migration: v2 -> v3 +-- +-- Changes: +-- * Added column [payment_details_type] in table [inbound_liquidity_outgoing_payments] + +ALTER TABLE inbound_liquidity_outgoing_payments ADD COLUMN payment_details_type TEXT DEFAULT NULL; diff --git a/src/jvmMain/kotlin/fr/acinq/lightning/bin/Actuals.kt b/src/jvmMain/kotlin/fr/acinq/lightning/bin/Actuals.kt index 0ad311d..ef57dea 100644 --- a/src/jvmMain/kotlin/fr/acinq/lightning/bin/Actuals.kt +++ b/src/jvmMain/kotlin/fr/acinq/lightning/bin/Actuals.kt @@ -15,7 +15,11 @@ import java.util.* actual val datadir: Path = (System.getenv()[PHOENIX_DATADIR]?.toPath() ?: System.getProperty("user.home").toPath().div(".phoenix")) actual fun createAppDbDriver(dir: Path, chain: Chain, nodeId: PublicKey): SqlDriver { - val path = dir / "phoenix.${chain.name.lowercase()}.${nodeId.toHex().take(6)}.db" + val chainName = when (chain) { + is Chain.Testnet3 -> "testnet" + else -> chain.name.lowercase() + } + val path = dir / "phoenix.$chainName.${nodeId.toHex().take(6)}.db" // Initial schema version wasn't set, so we need to set it manually JdbcSqliteDriver("jdbc:sqlite:$path").let { driver -> diff --git a/src/nativeMain/kotlin/fr/acinq/lightning/bin/Actuals.kt b/src/nativeMain/kotlin/fr/acinq/lightning/bin/Actuals.kt index e37835d..10ab956 100644 --- a/src/nativeMain/kotlin/fr/acinq/lightning/bin/Actuals.kt +++ b/src/nativeMain/kotlin/fr/acinq/lightning/bin/Actuals.kt @@ -19,7 +19,11 @@ actual val datadir: Path = setenv("KTOR_LOG_LEVEL", "WARN", 1) getenv(PHOENIX_DATADIR)?.toKString()?.toPath() ?: getenv("HOME")?.toKString()!!.toPath().div(".phoenix") } actual fun createAppDbDriver(dir: Path, chain: Chain, nodeId: PublicKey): SqlDriver { - return NativeSqliteDriver(PhoenixDatabase.Schema, "phoenix.${chain.name.lowercase()}.${nodeId.toHex().take(6)}.db", + val chainName = when (chain) { + is Chain.Testnet3 -> "testnet" + else -> chain.name.lowercase() + } + return NativeSqliteDriver(PhoenixDatabase.Schema, "phoenix.$chainName.${nodeId.toHex().take(6)}.db", onConfiguration = { it.copy(extendedConfig = it.extendedConfig.copy(basePath = dir.toString())) } ) }