Merge channels and payments database

There is now only one database file, phoenix.db. This
makes database backup easier.

Legacy channels closing parts have been removed (we
use a dedicated table to store closings).

The tables storing lightning outgoing payments have
been renamed for consistency by addingd a "lightning"
prefix. The related kotlin classes have been renamed
as well.
This commit is contained in:
Dominique Padiou 2024-03-18 17:21:03 +01:00
parent 9d984591bc
commit 64775d2fd5
No known key found for this signature in database
GPG Key ID: 574C8C6A1673E987
28 changed files with 185 additions and 287 deletions

View File

@ -107,13 +107,9 @@ tasks.withType<JavaExec> {
sqldelight { sqldelight {
databases { databases {
create("ChannelsDatabase") { create("PhoenixDatabase") {
packageName.set("fr.acinq.phoenix.db") packageName.set("fr.acinq.phoenix.db")
srcDirs.from("src/commonMain/sqldelight/channelsdb") srcDirs.from("src/commonMain/sqldelight/phoenixdb")
}
create("PaymentsDatabase") {
packageName.set("fr.acinq.phoenix.db")
srcDirs.from("src/commonMain/sqldelight/paymentsdb")
} }
} }
} }

View File

@ -6,4 +6,3 @@ import okio.Path
expect val homeDirectory: Path expect val homeDirectory: Path
expect fun createAppDbDriver(dir: Path): SqlDriver expect fun createAppDbDriver(dir: Path): SqlDriver
expect fun createPaymentsDbDriver(dir: Path): SqlDriver

View File

@ -1,5 +1,6 @@
package fr.acinq.lightning.bin package fr.acinq.lightning.bin
import app.cash.sqldelight.EnumColumnAdapter
import co.touchlab.kermit.CommonWriter import co.touchlab.kermit.CommonWriter
import co.touchlab.kermit.Severity import co.touchlab.kermit.Severity
import co.touchlab.kermit.StaticConfig import co.touchlab.kermit.StaticConfig
@ -27,6 +28,7 @@ import fr.acinq.lightning.bin.conf.getOrGenerateSeed
import fr.acinq.lightning.bin.conf.readConfFile import fr.acinq.lightning.bin.conf.readConfFile
import fr.acinq.lightning.bin.db.SqliteChannelsDb import fr.acinq.lightning.bin.db.SqliteChannelsDb
import fr.acinq.lightning.bin.db.SqlitePaymentsDb import fr.acinq.lightning.bin.db.SqlitePaymentsDb
import fr.acinq.lightning.bin.db.payments.LightningOutgoingQueries
import fr.acinq.lightning.bin.json.ApiType import fr.acinq.lightning.bin.json.ApiType
import fr.acinq.lightning.bin.logs.FileLogWriter import fr.acinq.lightning.bin.logs.FileLogWriter
import fr.acinq.lightning.blockchain.electrum.ElectrumClient import fr.acinq.lightning.blockchain.electrum.ElectrumClient
@ -44,6 +46,7 @@ import fr.acinq.lightning.utils.Connection
import fr.acinq.lightning.utils.ServerAddress import fr.acinq.lightning.utils.ServerAddress
import fr.acinq.lightning.utils.msat import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sat
import fr.acinq.phoenix.db.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.cio.* import io.ktor.server.cio.*
@ -210,14 +213,34 @@ class Phoenixd : CliktCommand() {
) )
echo(cyan("nodeid: ${nodeParams.nodeId}")) echo(cyan("nodeid: ${nodeParams.nodeId}"))
val driver = createAppDbDriver(datadir)
val database = PhoenixDatabase(
driver = driver,
lightning_outgoing_payment_partsAdapter = Lightning_outgoing_payment_parts.Adapter(
part_routeAdapter = LightningOutgoingQueries.hopDescAdapter,
part_status_typeAdapter = EnumColumnAdapter()
),
lightning_outgoing_paymentsAdapter = Lightning_outgoing_payments.Adapter(
status_typeAdapter = EnumColumnAdapter(),
details_typeAdapter = EnumColumnAdapter()
),
incoming_paymentsAdapter = Incoming_payments.Adapter(
origin_typeAdapter = EnumColumnAdapter(),
received_with_typeAdapter = EnumColumnAdapter()
),
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 electrum = ElectrumClient(scope, loggerFactory) val electrum = ElectrumClient(scope, loggerFactory)
val paymentsDb = SqlitePaymentsDb(loggerFactory, createPaymentsDbDriver(datadir))
val peer = Peer( val peer = Peer(
nodeParams = nodeParams, walletParams = lsp.walletParams, watcher = ElectrumWatcher(electrum, scope, loggerFactory), db = object : Databases { nodeParams = nodeParams, walletParams = lsp.walletParams, watcher = ElectrumWatcher(electrum, scope, loggerFactory), db = object : Databases {
override val channels: ChannelsDb override val channels: ChannelsDb get() = SqliteChannelsDb(driver, database)
get() = SqliteChannelsDb(createAppDbDriver(datadir)) override val payments: PaymentsDb get() = SqlitePaymentsDb(database)
override val payments: PaymentsDb
get() = paymentsDb
}, socketBuilder = TcpSocket.Builder(), scope }, socketBuilder = TcpSocket.Builder(), scope
) )

View File

@ -1,3 +1,19 @@
/*
* Copyright 2020 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 package fr.acinq.lightning.bin.db
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
@ -6,14 +22,13 @@ import fr.acinq.lightning.CltvExpiry
import fr.acinq.lightning.channel.states.PersistedChannelState import fr.acinq.lightning.channel.states.PersistedChannelState
import fr.acinq.lightning.db.ChannelsDb import fr.acinq.lightning.db.ChannelsDb
import fr.acinq.lightning.serialization.Serialization import fr.acinq.lightning.serialization.Serialization
import fr.acinq.phoenix.db.ChannelsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
internal class SqliteChannelsDb(private val driver: SqlDriver) : ChannelsDb { internal class SqliteChannelsDb(val driver: SqlDriver, val database: PhoenixDatabase) : ChannelsDb {
private val database = ChannelsDatabase(driver) private val queries = database.channelsQueries
private val queries = database.channelsDatabaseQueries
override suspend fun addOrUpdateChannel(state: PersistedChannelState) { override suspend fun addOrUpdateChannel(state: PersistedChannelState) {
val channelId = state.channelId.toByteArray() val channelId = state.channelId.toByteArray()

View File

@ -16,8 +16,6 @@
package fr.acinq.lightning.bin.db package fr.acinq.lightning.bin.db
import app.cash.sqldelight.EnumColumnAdapter
import app.cash.sqldelight.db.SqlDriver
import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto import fr.acinq.bitcoin.Crypto
import fr.acinq.bitcoin.TxId import fr.acinq.bitcoin.TxId
@ -27,8 +25,6 @@ import fr.acinq.lightning.bin.db.payments.LinkTxToPaymentQueries
import fr.acinq.lightning.bin.db.payments.PaymentsMetadataQueries import fr.acinq.lightning.bin.db.payments.PaymentsMetadataQueries
import fr.acinq.lightning.channel.ChannelException import fr.acinq.lightning.channel.ChannelException
import fr.acinq.lightning.db.* import fr.acinq.lightning.db.*
import fr.acinq.lightning.logging.LoggerFactory
import fr.acinq.lightning.logging.info
import fr.acinq.lightning.payment.FinalFailure import fr.acinq.lightning.payment.FinalFailure
import fr.acinq.lightning.utils.* import fr.acinq.lightning.utils.*
import fr.acinq.lightning.wire.FailureMessage import fr.acinq.lightning.wire.FailureMessage
@ -36,40 +32,10 @@ import fr.acinq.phoenix.db.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SqlitePaymentsDb( class SqlitePaymentsDb(val database: PhoenixDatabase) : PaymentsDb {
loggerFactory: LoggerFactory,
private val driver: SqlDriver,
) : PaymentsDb {
private val log = loggerFactory.newLogger(this::class)
private val database = PaymentsDatabase(
driver = driver,
outgoing_payment_partsAdapter = Outgoing_payment_parts.Adapter(
part_routeAdapter = OutgoingQueries.hopDescAdapter,
part_status_typeAdapter = EnumColumnAdapter()
),
outgoing_paymentsAdapter = Outgoing_payments.Adapter(
status_typeAdapter = EnumColumnAdapter(),
details_typeAdapter = EnumColumnAdapter()
),
incoming_paymentsAdapter = Incoming_payments.Adapter(
origin_typeAdapter = EnumColumnAdapter(),
received_with_typeAdapter = EnumColumnAdapter()
),
outgoing_payment_closing_tx_partsAdapter = Outgoing_payment_closing_tx_parts.Adapter(
part_closing_info_typeAdapter = EnumColumnAdapter()
),
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()
)
)
private val inQueries = IncomingQueries(database) private val inQueries = IncomingQueries(database)
private val outQueries = OutgoingQueries(database) private val lightningOutgoingQueries = LightningOutgoingQueries(database)
private val spliceOutQueries = SpliceOutgoingQueries(database) private val spliceOutQueries = SpliceOutgoingQueries(database)
private val channelCloseQueries = ChannelCloseOutgoingQueries(database) private val channelCloseQueries = ChannelCloseOutgoingQueries(database)
private val cpfpQueries = SpliceCpfpOutgoingQueries(database) private val cpfpQueries = SpliceCpfpOutgoingQueries(database)
@ -82,7 +48,7 @@ class SqlitePaymentsDb(
parts: List<LightningOutgoingPayment.Part> parts: List<LightningOutgoingPayment.Part>
) { ) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
outQueries.addLightningParts(parentId, parts) lightningOutgoingQueries.addLightningParts(parentId, parts)
} }
} }
@ -93,7 +59,7 @@ class SqlitePaymentsDb(
database.transaction { database.transaction {
when (outgoingPayment) { when (outgoingPayment) {
is LightningOutgoingPayment -> { is LightningOutgoingPayment -> {
outQueries.addLightningOutgoingPayment(outgoingPayment) lightningOutgoingQueries.addLightningOutgoingPayment(outgoingPayment)
} }
is SpliceOutgoingPayment -> { is SpliceOutgoingPayment -> {
spliceOutQueries.addSpliceOutgoingPayment(outgoingPayment) spliceOutQueries.addSpliceOutgoingPayment(outgoingPayment)
@ -128,7 +94,7 @@ class SqlitePaymentsDb(
completedAt: Long completedAt: Long
) { ) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
outQueries.completePayment(id, LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(preimage, completedAt)) lightningOutgoingQueries.completePayment(id, LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(preimage, completedAt))
} }
} }
@ -138,7 +104,7 @@ class SqlitePaymentsDb(
completedAt: Long completedAt: Long
) { ) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
outQueries.completePayment(id, LightningOutgoingPayment.Status.Completed.Failed(finalFailure, completedAt)) lightningOutgoingQueries.completePayment(id, LightningOutgoingPayment.Status.Completed.Failed(finalFailure, completedAt))
} }
} }
@ -148,7 +114,7 @@ class SqlitePaymentsDb(
completedAt: Long completedAt: Long
) { ) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
outQueries.updateLightningPart(partId, preimage, completedAt) lightningOutgoingQueries.updateLightningPart(partId, preimage, completedAt)
} }
} }
@ -158,16 +124,16 @@ class SqlitePaymentsDb(
completedAt: Long completedAt: Long
) { ) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
outQueries.updateLightningPart(partId, failure, completedAt) lightningOutgoingQueries.updateLightningPart(partId, failure, completedAt)
} }
} }
override suspend fun getLightningOutgoingPayment(id: UUID): LightningOutgoingPayment? = withContext(Dispatchers.Default) { override suspend fun getLightningOutgoingPayment(id: UUID): LightningOutgoingPayment? = withContext(Dispatchers.Default) {
outQueries.getPayment(id) lightningOutgoingQueries.getPayment(id)
} }
override suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? = withContext(Dispatchers.Default) { override suspend fun getLightningOutgoingPaymentFromPartId(partId: UUID): LightningOutgoingPayment? = withContext(Dispatchers.Default) {
outQueries.getPaymentFromPartId(partId) lightningOutgoingQueries.getPaymentFromPartId(partId)
} }
// ---- list outgoing // ---- list outgoing
@ -175,7 +141,7 @@ class SqlitePaymentsDb(
override suspend fun listLightningOutgoingPayments( override suspend fun listLightningOutgoingPayments(
paymentHash: ByteVector32 paymentHash: ByteVector32
): List<LightningOutgoingPayment> = withContext(Dispatchers.Default) { ): List<LightningOutgoingPayment> = withContext(Dispatchers.Default) {
outQueries.listLightningOutgoingPayments(paymentHash) lightningOutgoingQueries.listLightningOutgoingPayments(paymentHash)
} }
// ---- incoming payments // ---- incoming payments

View File

@ -21,9 +21,9 @@ import fr.acinq.lightning.db.ChannelCloseOutgoingPayment
import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.utils.toByteVector32 import fr.acinq.lightning.utils.toByteVector32
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
class ChannelCloseOutgoingQueries(val database: PaymentsDatabase) { class ChannelCloseOutgoingQueries(val database: PhoenixDatabase) {
private val channelCloseQueries = database.channelCloseOutgoingPaymentsQueries private val channelCloseQueries = database.channelCloseOutgoingPaymentsQueries
fun getChannelCloseOutgoingPayment(id: UUID): ChannelCloseOutgoingPayment? { fun getChannelCloseOutgoingPayment(id: UUID): ChannelCloseOutgoingPayment? {
@ -74,7 +74,7 @@ class ChannelCloseOutgoingQueries(val database: PaymentsDatabase) {
confirmed_at: Long?, confirmed_at: Long?,
locked_at: Long?, locked_at: Long?,
channel_id: ByteArray, channel_id: ByteArray,
closing_info_type: OutgoingPartClosingInfoTypeVersion, closing_info_type: ClosingInfoTypeVersion,
closing_info_blob: ByteArray closing_info_blob: ByteArray
): ChannelCloseOutgoingPayment { ): ChannelCloseOutgoingPayment {
return ChannelCloseOutgoingPayment( return ChannelCloseOutgoingPayment(
@ -88,7 +88,7 @@ class ChannelCloseOutgoingQueries(val database: PaymentsDatabase) {
confirmedAt = confirmed_at, confirmedAt = confirmed_at,
lockedAt = locked_at, lockedAt = locked_at,
channelId = channel_id.toByteVector32(), channelId = channel_id.toByteVector32(),
closingType = OutgoingPartClosingInfoData.deserialize(closing_info_type, closing_info_blob), closingType = ClosingInfoData.deserialize(closing_info_type, closing_info_blob),
) )
} }
} }

View File

@ -25,24 +25,24 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
enum class OutgoingPartClosingInfoTypeVersion { enum class ClosingInfoTypeVersion {
// basic type, containing only a [ChannelClosingType] field // basic type, containing only a [ChannelClosingType] field
CLOSING_INFO_V0, CLOSING_INFO_V0,
} }
sealed class OutgoingPartClosingInfoData { sealed class ClosingInfoData {
@Serializable @Serializable
data class V0(val closingType: ChannelClosingType) data class V0(val closingType: ChannelClosingType)
companion object { companion object {
fun deserialize(typeVersion: OutgoingPartClosingInfoTypeVersion, blob: ByteArray): ChannelClosingType = DbTypesHelper.decodeBlob(blob) { json, format -> fun deserialize(typeVersion: ClosingInfoTypeVersion, blob: ByteArray): ChannelClosingType = DbTypesHelper.decodeBlob(blob) { json, format ->
when (typeVersion) { when (typeVersion) {
OutgoingPartClosingInfoTypeVersion.CLOSING_INFO_V0 -> format.decodeFromString<V0>(json).closingType ClosingInfoTypeVersion.CLOSING_INFO_V0 -> format.decodeFromString<V0>(json).closingType
} }
} }
} }
} }
fun ChannelCloseOutgoingPayment.mapClosingTypeToDb() = OutgoingPartClosingInfoTypeVersion.CLOSING_INFO_V0 to fun ChannelCloseOutgoingPayment.mapClosingTypeToDb() = ClosingInfoTypeVersion.CLOSING_INFO_V0 to
Json.encodeToString(OutgoingPartClosingInfoData.V0(this.closingType)).toByteArray(Charsets.UTF_8) Json.encodeToString(ClosingInfoData.V0(this.closingType)).toByteArray(Charsets.UTF_8)

View File

@ -21,10 +21,10 @@ import fr.acinq.lightning.db.InboundLiquidityOutgoingPayment
import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.utils.toByteVector32 import fr.acinq.lightning.utils.toByteVector32
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
class InboundLiquidityQueries(val database: PaymentsDatabase) { class InboundLiquidityQueries(val database: PhoenixDatabase) {
private val queries = database.inboundLiquidityOutgoingQueries private val queries = database.inboundLiquidityOutgoingPaymentsQueries
fun add(payment: InboundLiquidityOutgoingPayment) { fun add(payment: InboundLiquidityOutgoingPayment) {
database.transaction { database.transaction {

View File

@ -21,13 +21,12 @@ import app.cash.sqldelight.coroutines.mapToList
import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.byteVector32 import fr.acinq.bitcoin.byteVector32
import fr.acinq.lightning.db.IncomingPayment import fr.acinq.lightning.db.IncomingPayment
import fr.acinq.lightning.utils.msat import fr.acinq.phoenix.db.PhoenixDatabase
import fr.acinq.phoenix.db.PaymentsDatabase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
class IncomingQueries(private val database: PaymentsDatabase) { class IncomingQueries(private val database: PhoenixDatabase) {
private val queries = database.incomingPaymentsQueries private val queries = database.incomingPaymentsQueries

View File

@ -35,46 +35,46 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
enum class OutgoingDetailsTypeVersion { enum class LightningOutgoingDetailsTypeVersion {
NORMAL_V0, NORMAL_V0,
KEYSEND_V0, KEYSEND_V0,
SWAPOUT_V0, SWAPOUT_V0,
} }
sealed class OutgoingDetailsData { sealed class LightningOutgoingDetailsData {
sealed class Normal : OutgoingDetailsData() { sealed class Normal : LightningOutgoingDetailsData() {
@Serializable @Serializable
data class V0(val paymentRequest: String) : Normal() data class V0(val paymentRequest: String) : Normal()
} }
sealed class KeySend : OutgoingDetailsData() { sealed class KeySend : LightningOutgoingDetailsData() {
@Serializable @Serializable
data class V0(@Serializable val preimage: ByteVector32) : KeySend() data class V0(@Serializable val preimage: ByteVector32) : KeySend()
} }
sealed class SwapOut : OutgoingDetailsData() { sealed class SwapOut : LightningOutgoingDetailsData() {
@Serializable @Serializable
data class V0(val address: String, val paymentRequest: String, @Serializable val swapOutFee: Satoshi) : SwapOut() data class V0(val address: String, val paymentRequest: String, @Serializable val swapOutFee: Satoshi) : SwapOut()
} }
companion object { companion object {
/** Deserialize the details of an outgoing payment. Return null if the details is for a legacy channel closing payment (see [deserializeLegacyClosingDetails]). */ /** Deserialize the details of an outgoing payment. Return null if the details is for a legacy channel closing payment (see [deserializeLegacyClosingDetails]). */
fun deserialize(typeVersion: OutgoingDetailsTypeVersion, blob: ByteArray): LightningOutgoingPayment.Details? = DbTypesHelper.decodeBlob(blob) { json, format -> fun deserialize(typeVersion: LightningOutgoingDetailsTypeVersion, blob: ByteArray): LightningOutgoingPayment.Details = DbTypesHelper.decodeBlob(blob) { json, format ->
when (typeVersion) { when (typeVersion) {
OutgoingDetailsTypeVersion.NORMAL_V0 -> format.decodeFromString<Normal.V0>(json).let { LightningOutgoingPayment.Details.Normal(Bolt11Invoice.read(it.paymentRequest).get()) } LightningOutgoingDetailsTypeVersion.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) } LightningOutgoingDetailsTypeVersion.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) } LightningOutgoingDetailsTypeVersion.SWAPOUT_V0 -> format.decodeFromString<SwapOut.V0>(json).let { LightningOutgoingPayment.Details.SwapOut(it.address, Bolt11Invoice.read(it.paymentRequest).get(), it.swapOutFee) }
} }
} }
} }
} }
fun LightningOutgoingPayment.Details.mapToDb(): Pair<OutgoingDetailsTypeVersion, ByteArray> = when (this) { fun LightningOutgoingPayment.Details.mapToDb(): Pair<LightningOutgoingDetailsTypeVersion, ByteArray> = when (this) {
is LightningOutgoingPayment.Details.Normal -> OutgoingDetailsTypeVersion.NORMAL_V0 to is LightningOutgoingPayment.Details.Normal -> LightningOutgoingDetailsTypeVersion.NORMAL_V0 to
Json.encodeToString(OutgoingDetailsData.Normal.V0(paymentRequest.write())).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingDetailsData.Normal.V0(paymentRequest.write())).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Details.KeySend -> OutgoingDetailsTypeVersion.KEYSEND_V0 to is LightningOutgoingPayment.Details.KeySend -> LightningOutgoingDetailsTypeVersion.KEYSEND_V0 to
Json.encodeToString(OutgoingDetailsData.KeySend.V0(preimage)).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingDetailsData.KeySend.V0(preimage)).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Details.SwapOut -> OutgoingDetailsTypeVersion.SWAPOUT_V0 to is LightningOutgoingPayment.Details.SwapOut -> LightningOutgoingDetailsTypeVersion.SWAPOUT_V0 to
Json.encodeToString(OutgoingDetailsData.SwapOut.V0(address, paymentRequest.write(), swapOutFee)).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingDetailsData.SwapOut.V0(address, paymentRequest.write(), swapOutFee)).toByteArray(Charsets.UTF_8)
} }

View File

@ -31,33 +31,33 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
enum class OutgoingPartStatusTypeVersion { enum class LightningOutgoingPartStatusTypeVersion {
SUCCEEDED_V0, SUCCEEDED_V0,
FAILED_V0, FAILED_V0,
} }
sealed class OutgoingPartStatusData { sealed class LightningOutgoingPartStatusData {
sealed class Succeeded : OutgoingPartStatusData() { sealed class Succeeded : LightningOutgoingPartStatusData() {
@Serializable @Serializable
data class V0(@Serializable val preimage: ByteVector32) : Succeeded() data class V0(@Serializable val preimage: ByteVector32) : Succeeded()
} }
sealed class Failed : OutgoingPartStatusData() { sealed class Failed : LightningOutgoingPartStatusData() {
@Serializable @Serializable
data class V0(val remoteFailureCode: Int?, val details: String) : Failed() data class V0(val remoteFailureCode: Int?, val details: String) : Failed()
} }
companion object { companion object {
fun deserialize( fun deserialize(
typeVersion: OutgoingPartStatusTypeVersion, typeVersion: LightningOutgoingPartStatusTypeVersion,
blob: ByteArray, completedAt: Long blob: ByteArray, completedAt: Long
): LightningOutgoingPayment.Part.Status = DbTypesHelper.decodeBlob(blob) { json, format -> ): LightningOutgoingPayment.Part.Status = DbTypesHelper.decodeBlob(blob) { json, format ->
when (typeVersion) { when (typeVersion) {
OutgoingPartStatusTypeVersion.SUCCEEDED_V0 -> format.decodeFromString<Succeeded.V0>(json).let { LightningOutgoingPartStatusTypeVersion.SUCCEEDED_V0 -> format.decodeFromString<Succeeded.V0>(json).let {
LightningOutgoingPayment.Part.Status.Succeeded(it.preimage, completedAt) LightningOutgoingPayment.Part.Status.Succeeded(it.preimage, completedAt)
} }
OutgoingPartStatusTypeVersion.FAILED_V0 -> format.decodeFromString<Failed.V0>(json).let { LightningOutgoingPartStatusTypeVersion.FAILED_V0 -> format.decodeFromString<Failed.V0>(json).let {
LightningOutgoingPayment.Part.Status.Failed(it.remoteFailureCode, it.details, completedAt) LightningOutgoingPayment.Part.Status.Failed(it.remoteFailureCode, it.details, completedAt)
} }
} }
@ -65,8 +65,8 @@ sealed class OutgoingPartStatusData {
} }
} }
fun LightningOutgoingPayment.Part.Status.Succeeded.mapToDb() = OutgoingPartStatusTypeVersion.SUCCEEDED_V0 to fun LightningOutgoingPayment.Part.Status.Succeeded.mapToDb() = LightningOutgoingPartStatusTypeVersion.SUCCEEDED_V0 to
Json.encodeToString(OutgoingPartStatusData.Succeeded.V0(preimage)).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingPartStatusData.Succeeded.V0(preimage)).toByteArray(Charsets.UTF_8)
fun LightningOutgoingPayment.Part.Status.Failed.mapToDb() = OutgoingPartStatusTypeVersion.FAILED_V0 to fun LightningOutgoingPayment.Part.Status.Failed.mapToDb() = LightningOutgoingPartStatusTypeVersion.FAILED_V0 to
Json.encodeToString(OutgoingPartStatusData.Failed.V0(remoteFailureCode, details)).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingPartStatusData.Failed.V0(remoteFailureCode, details)).toByteArray(Charsets.UTF_8)

View File

@ -25,16 +25,15 @@ import fr.acinq.lightning.ShortChannelId
import fr.acinq.lightning.channel.ChannelException import fr.acinq.lightning.channel.ChannelException
import fr.acinq.lightning.db.HopDesc import fr.acinq.lightning.db.HopDesc
import fr.acinq.lightning.db.LightningOutgoingPayment import fr.acinq.lightning.db.LightningOutgoingPayment
import fr.acinq.lightning.db.OutgoingPayment
import fr.acinq.lightning.payment.OutgoingPaymentFailure import fr.acinq.lightning.payment.OutgoingPaymentFailure
import fr.acinq.lightning.utils.* import fr.acinq.lightning.utils.*
import fr.acinq.lightning.wire.FailureMessage import fr.acinq.lightning.wire.FailureMessage
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
class OutgoingQueries(val database: PaymentsDatabase) { class LightningOutgoingQueries(val database: PhoenixDatabase) {
private val queries = database.outgoingPaymentsQueries private val queries = database.lightningOutgoingPaymentsQueries
fun addLightningParts(parentId: UUID, parts: List<LightningOutgoingPayment.Part>) { fun addLightningParts(parentId: UUID, parts: List<LightningOutgoingPayment.Part>) {
if (parts.isEmpty()) return if (parts.isEmpty()) return
@ -135,12 +134,10 @@ class OutgoingQueries(val database: PaymentsDatabase) {
return result return result
} }
/** This method will ignore any parts that are not proper [LightningOutgoingPayment]. */
fun getPaymentFromPartId(partId: UUID): LightningOutgoingPayment? { fun getPaymentFromPartId(partId: UUID): LightningOutgoingPayment? {
return queries.getLightningPart(part_id = partId.toString()).executeAsOneOrNull()?.let { part -> return queries.getLightningPart(part_id = partId.toString()).executeAsOneOrNull()?.let { part ->
queries.getPayment(id = part.part_parent_id, Companion::mapLightningOutgoingPayment).executeAsList() queries.getPayment(id = part.part_parent_id, Companion::mapLightningOutgoingPayment).executeAsList()
}?.filterIsInstance<LightningOutgoingPayment>()?.let { }?.let {
// first ignore any legacy channel closing, then group by parent id
groupByRawLightningOutgoing(it).firstOrNull() groupByRawLightningOutgoing(it).firstOrNull()
}?.let { }?.let {
filterUselessParts(it) filterUselessParts(it)
@ -156,17 +153,13 @@ class OutgoingQueries(val database: PaymentsDatabase) {
id = id.toString(), id = id.toString(),
mapper = Companion::mapLightningOutgoingPayment mapper = Companion::mapLightningOutgoingPayment
).executeAsList().let { parts -> ).executeAsList().let { parts ->
// only take regular LN payments parts, and group them groupByRawLightningOutgoing(parts).firstOrNull()?.let {
parts.filterIsInstance<LightningOutgoingPayment>().let {
groupByRawLightningOutgoing(it).firstOrNull()
}?.let {
filterUselessParts(it) filterUselessParts(it)
} }
} }
fun listLightningOutgoingPayments(paymentHash: ByteVector32): List<LightningOutgoingPayment> { fun listLightningOutgoingPayments(paymentHash: ByteVector32): List<LightningOutgoingPayment> {
return queries.listPaymentsForPaymentHash(paymentHash.toByteArray(), Companion::mapLightningOutgoingPayment).executeAsList() return queries.listPaymentsForPaymentHash(paymentHash.toByteArray(), Companion::mapLightningOutgoingPayment).executeAsList()
.filterIsInstance<LightningOutgoingPayment>()
.let { groupByRawLightningOutgoing(it) } .let { groupByRawLightningOutgoing(it) }
} }
@ -195,16 +188,15 @@ class OutgoingQueries(val database: PaymentsDatabase) {
recipient_amount_msat: Long, recipient_amount_msat: Long,
recipient_node_id: String, recipient_node_id: String,
payment_hash: ByteArray, payment_hash: ByteArray,
details_type: OutgoingDetailsTypeVersion, details_type: LightningOutgoingDetailsTypeVersion,
details_blob: ByteArray, details_blob: ByteArray,
created_at: Long, created_at: Long,
completed_at: Long?, completed_at: Long?,
status_type: OutgoingStatusTypeVersion?, status_type: LightningOutgoingStatusTypeVersion?,
status_blob: ByteArray? status_blob: ByteArray?
): LightningOutgoingPayment { ): LightningOutgoingPayment {
val details = OutgoingDetailsData.deserialize(details_type, details_blob) val details = LightningOutgoingDetailsData.deserialize(details_type, details_blob)
return if (details != null) { return LightningOutgoingPayment(
LightningOutgoingPayment(
id = UUID.fromString(id), id = UUID.fromString(id),
recipientAmount = MilliSatoshi(recipient_amount_msat), recipientAmount = MilliSatoshi(recipient_amount_msat),
recipient = PublicKey.parse(Hex.decode(recipient_node_id)), recipient = PublicKey.parse(Hex.decode(recipient_node_id)),
@ -213,7 +205,6 @@ class OutgoingQueries(val database: PaymentsDatabase) {
status = mapPaymentStatus(status_type, status_blob, completed_at), status = mapPaymentStatus(status_type, status_blob, completed_at),
createdAt = created_at createdAt = created_at
) )
} else throw IllegalArgumentException("cannot handle closing payment at this stage, use LegacyChannelCloseHelper")
} }
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
@ -222,11 +213,11 @@ class OutgoingQueries(val database: PaymentsDatabase) {
recipient_amount_msat: Long, recipient_amount_msat: Long,
recipient_node_id: String, recipient_node_id: String,
payment_hash: ByteArray, payment_hash: ByteArray,
details_type: OutgoingDetailsTypeVersion, details_type: LightningOutgoingDetailsTypeVersion,
details_blob: ByteArray, details_blob: ByteArray,
created_at: Long, created_at: Long,
completed_at: Long?, completed_at: Long?,
status_type: OutgoingStatusTypeVersion?, status_type: LightningOutgoingStatusTypeVersion?,
status_blob: ByteArray?, status_blob: ByteArray?,
// lightning parts data, may be null // lightning parts data, may be null
lightning_part_id: String?, lightning_part_id: String?,
@ -234,16 +225,9 @@ class OutgoingQueries(val database: PaymentsDatabase) {
lightning_part_route: List<HopDesc>?, lightning_part_route: List<HopDesc>?,
lightning_part_created_at: Long?, lightning_part_created_at: Long?,
lightning_part_completed_at: Long?, lightning_part_completed_at: Long?,
lightning_part_status_type: OutgoingPartStatusTypeVersion?, lightning_part_status_type: LightningOutgoingPartStatusTypeVersion?,
lightning_part_status_blob: ByteArray?, lightning_part_status_blob: ByteArray?,
// closing tx parts data, may be null ): LightningOutgoingPayment {
closingtx_part_id: String?,
closingtx_part_tx_id: ByteArray?,
closingtx_part_amount_sat: Long?,
closingtx_part_closing_info_type: OutgoingPartClosingInfoTypeVersion?,
closingtx_part_closing_info_blob: ByteArray?,
closingtx_part_created_at: Long?
): OutgoingPayment {
val parts = if (lightning_part_id != null && lightning_part_amount_msat != null && lightning_part_route != null && lightning_part_created_at != null) { val parts = if (lightning_part_id != null && lightning_part_amount_msat != null && lightning_part_route != null && lightning_part_created_at != null) {
listOf( listOf(
@ -281,7 +265,7 @@ class OutgoingQueries(val database: PaymentsDatabase) {
route: List<HopDesc>, route: List<HopDesc>,
createdAt: Long, createdAt: Long,
completedAt: Long?, completedAt: Long?,
statusType: OutgoingPartStatusTypeVersion?, statusType: LightningOutgoingPartStatusTypeVersion?,
statusBlob: ByteArray? statusBlob: ByteArray?
): LightningOutgoingPayment.Part { ): LightningOutgoingPayment.Part {
return LightningOutgoingPayment.Part( return LightningOutgoingPayment.Part(
@ -298,22 +282,22 @@ class OutgoingQueries(val database: PaymentsDatabase) {
} }
private fun mapPaymentStatus( private fun mapPaymentStatus(
statusType: OutgoingStatusTypeVersion?, statusType: LightningOutgoingStatusTypeVersion?,
statusBlob: ByteArray?, statusBlob: ByteArray?,
completedAt: Long?, completedAt: Long?,
): LightningOutgoingPayment.Status = when { ): LightningOutgoingPayment.Status = when {
completedAt == null && statusType == null && statusBlob == null -> LightningOutgoingPayment.Status.Pending completedAt == null && statusType == null && statusBlob == null -> LightningOutgoingPayment.Status.Pending
completedAt != null && statusType != null && statusBlob != null -> OutgoingStatusData.deserialize(statusType, statusBlob, completedAt) completedAt != null && statusType != null && statusBlob != null -> LightningOutgoingStatusData.deserialize(statusType, statusBlob, completedAt)
else -> throw UnhandledOutgoingStatus(completedAt, statusType, statusBlob) else -> throw UnhandledOutgoingStatus(completedAt, statusType, statusBlob)
} }
private fun mapLightningPartStatus( private fun mapLightningPartStatus(
statusType: OutgoingPartStatusTypeVersion?, statusType: LightningOutgoingPartStatusTypeVersion?,
statusBlob: ByteArray?, statusBlob: ByteArray?,
completedAt: Long?, completedAt: Long?,
): LightningOutgoingPayment.Part.Status = when { ): LightningOutgoingPayment.Part.Status = when {
completedAt == null && statusType == null && statusBlob == null -> LightningOutgoingPayment.Part.Status.Pending completedAt == null && statusType == null && statusBlob == null -> LightningOutgoingPayment.Part.Status.Pending
completedAt != null && statusType != null && statusBlob != null -> OutgoingPartStatusData.deserialize(statusType, statusBlob, completedAt) completedAt != null && statusType != null && statusBlob != null -> LightningOutgoingPartStatusData.deserialize(statusType, statusBlob, completedAt)
else -> throw UnhandledOutgoingPartStatus(statusType, statusBlob, completedAt) else -> throw UnhandledOutgoingPartStatus(statusType, statusBlob, completedAt)
} }
@ -336,8 +320,8 @@ class OutgoingQueries(val database: PaymentsDatabase) {
} }
} }
data class UnhandledOutgoingStatus(val completedAt: Long?, val statusTypeVersion: OutgoingStatusTypeVersion?, val statusData: ByteArray?) : data class UnhandledOutgoingStatus(val completedAt: Long?, val statusTypeVersion: LightningOutgoingStatusTypeVersion?, val statusData: ByteArray?) :
RuntimeException("cannot map outgoing payment status data with completed_at=$completedAt status_type=$statusTypeVersion status=$statusData") RuntimeException("cannot map outgoing payment status data with completed_at=$completedAt status_type=$statusTypeVersion status=$statusData")
data class UnhandledOutgoingPartStatus(val status_type: OutgoingPartStatusTypeVersion?, val status_blob: ByteArray?, val completedAt: Long?) : data class UnhandledOutgoingPartStatus(val status_type: LightningOutgoingPartStatusTypeVersion?, val status_blob: ByteArray?, val completedAt: Long?) :
RuntimeException("cannot map outgoing part status data [ completed_at=$completedAt status_type=$status_type status_blob=$status_blob]") RuntimeException("cannot map outgoing part status data [ completed_at=$completedAt status_type=$status_type status_blob=$status_blob]")

View File

@ -22,7 +22,6 @@
package fr.acinq.lightning.bin.db.payments package fr.acinq.lightning.bin.db.payments
import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Satoshi
import fr.acinq.lightning.bin.db.payments.DbTypesHelper.decodeBlob import fr.acinq.lightning.bin.db.payments.DbTypesHelper.decodeBlob
import fr.acinq.lightning.bin.db.serializers.v1.ByteVector32Serializer import fr.acinq.lightning.bin.db.serializers.v1.ByteVector32Serializer
import fr.acinq.lightning.bin.db.serializers.v1.SatoshiSerializer import fr.acinq.lightning.bin.db.serializers.v1.SatoshiSerializer
@ -32,54 +31,35 @@ import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
enum class OutgoingStatusTypeVersion { enum class LightningOutgoingStatusTypeVersion {
SUCCEEDED_OFFCHAIN_V0, SUCCEEDED_OFFCHAIN_V0,
FAILED_V0, FAILED_V0,
} }
sealed class OutgoingStatusData { sealed class LightningOutgoingStatusData {
sealed class SucceededOffChain : OutgoingStatusData() { sealed class SucceededOffChain : LightningOutgoingStatusData() {
@Serializable @Serializable
data class V0(@Serializable val preimage: ByteVector32) : SucceededOffChain() data class V0(@Serializable val preimage: ByteVector32) : SucceededOffChain()
} }
sealed class SucceededOnChain : OutgoingStatusData() { sealed class Failed : LightningOutgoingStatusData() {
@Serializable
data class V0(
val txIds: List<@Serializable ByteVector32>,
@Serializable val claimed: Satoshi,
val closingType: String
) : SucceededOnChain()
@Serializable
object V1 : SucceededOnChain()
}
sealed class Failed : OutgoingStatusData() {
@Serializable @Serializable
data class V0(val reason: String) : Failed() data class V0(val reason: String) : Failed()
} }
companion object { companion object {
/** Extract valuable data from old outgoing payments status that represent closing transactions. */ fun deserialize(typeVersion: LightningOutgoingStatusTypeVersion, blob: ByteArray, completedAt: Long): LightningOutgoingPayment.Status = decodeBlob(blob) { json, format ->
fun deserializeLegacyClosingStatus(blob: ByteArray): SucceededOnChain.V0 = decodeBlob(blob) { json, format ->
val data = format.decodeFromString<SucceededOnChain.V0>(json)
data
}
fun deserialize(typeVersion: OutgoingStatusTypeVersion, blob: ByteArray, completedAt: Long): LightningOutgoingPayment.Status = decodeBlob(blob) { json, format ->
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
when (typeVersion) { when (typeVersion) {
OutgoingStatusTypeVersion.SUCCEEDED_OFFCHAIN_V0 -> format.decodeFromString<SucceededOffChain.V0>(json).let { LightningOutgoingStatusTypeVersion.SUCCEEDED_OFFCHAIN_V0 -> format.decodeFromString<SucceededOffChain.V0>(json).let {
LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(it.preimage, completedAt) LightningOutgoingPayment.Status.Completed.Succeeded.OffChain(it.preimage, completedAt)
} }
OutgoingStatusTypeVersion.FAILED_V0 -> format.decodeFromString<Failed.V0>(json).let { LightningOutgoingStatusTypeVersion.FAILED_V0 -> format.decodeFromString<Failed.V0>(json).let {
LightningOutgoingPayment.Status.Completed.Failed(deserializeFinalFailure(it.reason), completedAt) LightningOutgoingPayment.Status.Completed.Failed(deserializeFinalFailure(it.reason), completedAt)
} }
} }
@ -101,10 +81,9 @@ sealed class OutgoingStatusData {
} }
} }
fun LightningOutgoingPayment.Status.Completed.mapToDb(): Pair<OutgoingStatusTypeVersion, ByteArray> = when (this) { fun LightningOutgoingPayment.Status.Completed.mapToDb(): Pair<LightningOutgoingStatusTypeVersion, ByteArray> = when (this) {
is LightningOutgoingPayment.Status.Completed.Succeeded.OffChain -> OutgoingStatusTypeVersion.SUCCEEDED_OFFCHAIN_V0 to is LightningOutgoingPayment.Status.Completed.Succeeded.OffChain -> LightningOutgoingStatusTypeVersion.SUCCEEDED_OFFCHAIN_V0 to
Json.encodeToString(OutgoingStatusData.SucceededOffChain.V0(preimage)).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingStatusData.SucceededOffChain.V0(preimage)).toByteArray(Charsets.UTF_8)
is LightningOutgoingPayment.Status.Completed.Failed -> OutgoingStatusTypeVersion.FAILED_V0 to is LightningOutgoingPayment.Status.Completed.Failed -> LightningOutgoingStatusTypeVersion.FAILED_V0 to
Json.encodeToString(OutgoingStatusData.Failed.V0(OutgoingStatusData.serializeFinalFailure(reason))).toByteArray(Charsets.UTF_8) Json.encodeToString(LightningOutgoingStatusData.Failed.V0(LightningOutgoingStatusData.serializeFinalFailure(reason))).toByteArray(Charsets.UTF_8)
} }

View File

@ -20,12 +20,12 @@ import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToList import app.cash.sqldelight.coroutines.mapToList
import fr.acinq.bitcoin.TxId import fr.acinq.bitcoin.TxId
import fr.acinq.lightning.bin.db.WalletPaymentId import fr.acinq.lightning.bin.db.WalletPaymentId
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
class LinkTxToPaymentQueries(val database: PaymentsDatabase) { class LinkTxToPaymentQueries(val database: PhoenixDatabase) {
private val linkTxQueries = database.linkTxToPaymentQueries private val linkTxQueries = database.linkTxToPaymentQueries
fun listUnconfirmedTxs(): Flow<List<ByteArray>> { fun listUnconfirmedTxs(): Flow<List<ByteArray>> {

View File

@ -3,9 +3,9 @@ package fr.acinq.lightning.bin.db.payments
import fr.acinq.lightning.bin.db.PaymentMetadata import fr.acinq.lightning.bin.db.PaymentMetadata
import fr.acinq.lightning.bin.db.WalletPaymentId import fr.acinq.lightning.bin.db.WalletPaymentId
import fr.acinq.lightning.utils.currentTimestampMillis import fr.acinq.lightning.utils.currentTimestampMillis
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
class PaymentsMetadataQueries(private val database: PaymentsDatabase) { class PaymentsMetadataQueries(private val database: PhoenixDatabase) {
private val queries = database.paymentsMetadataQueries private val queries = database.paymentsMetadataQueries

View File

@ -21,9 +21,9 @@ import fr.acinq.lightning.db.SpliceCpfpOutgoingPayment
import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.utils.toByteVector32 import fr.acinq.lightning.utils.toByteVector32
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
class SpliceCpfpOutgoingQueries(val database: PaymentsDatabase) { class SpliceCpfpOutgoingQueries(val database: PhoenixDatabase) {
private val cpfpQueries = database.spliceCpfpOutgoingPaymentsQueries private val cpfpQueries = database.spliceCpfpOutgoingPaymentsQueries
fun addCpfpPayment(payment: SpliceCpfpOutgoingPayment) { fun addCpfpPayment(payment: SpliceCpfpOutgoingPayment) {

View File

@ -21,9 +21,9 @@ import fr.acinq.lightning.db.SpliceOutgoingPayment
import fr.acinq.lightning.utils.UUID import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.sat import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.utils.toByteVector32 import fr.acinq.lightning.utils.toByteVector32
import fr.acinq.phoenix.db.PaymentsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
class SpliceOutgoingQueries(val database: PaymentsDatabase) { class SpliceOutgoingQueries(val database: PhoenixDatabase) {
private val spliceOutQueries = database.spliceOutgoingPaymentsQueries private val spliceOutQueries = database.spliceOutgoingPaymentsQueries
fun addSpliceOutgoingPayment(payment: SpliceOutgoingPayment) { fun addSpliceOutgoingPayment(payment: SpliceOutgoingPayment) {

View File

@ -1,4 +1,4 @@
import fr.acinq.lightning.bin.db.payments.OutgoingPartClosingInfoTypeVersion; import fr.acinq.lightning.bin.db.payments.ClosingInfoTypeVersion;
-- Store in a flat row outgoing payments standing for channel-closing. -- Store in a flat row outgoing payments standing for channel-closing.
-- There are no complex json columns like in the outgoing_payments table. -- There are no complex json columns like in the outgoing_payments table.
@ -14,7 +14,7 @@ CREATE TABLE IF NOT EXISTS channel_close_outgoing_payments (
confirmed_at INTEGER DEFAULT NULL, confirmed_at INTEGER DEFAULT NULL,
locked_at INTEGER DEFAULT NULL, locked_at INTEGER DEFAULT NULL,
channel_id BLOB NOT NULL, channel_id BLOB NOT NULL,
closing_info_type TEXT AS OutgoingPartClosingInfoTypeVersion NOT NULL, closing_info_type TEXT AS ClosingInfoTypeVersion NOT NULL,
closing_info_blob BLOB NOT NULL closing_info_blob BLOB NOT NULL
); );

View File

@ -1,36 +1,35 @@
import fr.acinq.lightning.db.HopDesc; import fr.acinq.lightning.db.HopDesc;
import fr.acinq.lightning.bin.db.payments.OutgoingDetailsTypeVersion; import fr.acinq.lightning.bin.db.payments.LightningOutgoingDetailsTypeVersion;
import fr.acinq.lightning.bin.db.payments.OutgoingPartClosingInfoTypeVersion; import fr.acinq.lightning.bin.db.payments.LightningOutgoingPartStatusTypeVersion;
import fr.acinq.lightning.bin.db.payments.OutgoingPartStatusTypeVersion; import fr.acinq.lightning.bin.db.payments.LightningOutgoingStatusTypeVersion;
import fr.acinq.lightning.bin.db.payments.OutgoingStatusTypeVersion;
import kotlin.collections.List; import kotlin.collections.List;
PRAGMA foreign_keys = 1; PRAGMA foreign_keys = 1;
-- outgoing payments -- outgoing payments
-- Stores an outgoing payment in a flat row. Some columns can be null. -- Stores an outgoing payment in a flat row. Some columns can be null.
CREATE TABLE IF NOT EXISTS outgoing_payments ( CREATE TABLE IF NOT EXISTS lightning_outgoing_payments (
id TEXT NOT NULL PRIMARY KEY, id TEXT NOT NULL PRIMARY KEY,
recipient_amount_msat INTEGER NOT NULL, recipient_amount_msat INTEGER NOT NULL,
recipient_node_id TEXT NOT NULL, recipient_node_id TEXT NOT NULL,
payment_hash BLOB NOT NULL, payment_hash BLOB NOT NULL,
created_at INTEGER NOT NULL, created_at INTEGER NOT NULL,
-- details -- details
details_type TEXT AS OutgoingDetailsTypeVersion NOT NULL, details_type TEXT AS LightningOutgoingDetailsTypeVersion NOT NULL,
details_blob BLOB NOT NULL, details_blob BLOB NOT NULL,
-- status -- status
completed_at INTEGER DEFAULT NULL, completed_at INTEGER DEFAULT NULL,
status_type TEXT AS OutgoingStatusTypeVersion DEFAULT NULL, status_type TEXT AS LightningOutgoingStatusTypeVersion DEFAULT NULL,
status_blob BLOB DEFAULT NULL status_blob BLOB DEFAULT NULL
); );
-- Create indexes to optimize the queries in AggregatedQueries. -- Create indexes to optimize the queries in AggregatedQueries.
-- Tip: Use "explain query plan" to ensure they're actually being used. -- Tip: Use "explain query plan" to ensure they're actually being used.
CREATE INDEX IF NOT EXISTS outgoing_payments_filter_idx CREATE INDEX IF NOT EXISTS outgoing_payments_filter_idx
ON outgoing_payments(completed_at); ON lightning_outgoing_payments(completed_at);
-- Stores the lightning parts that make up a lightning payment -- Stores the lightning parts that make up a lightning payment
CREATE TABLE IF NOT EXISTS outgoing_payment_parts ( CREATE TABLE IF NOT EXISTS lightning_outgoing_payment_parts (
part_id TEXT NOT NULL PRIMARY KEY, part_id TEXT NOT NULL PRIMARY KEY,
part_parent_id TEXT NOT NULL, part_parent_id TEXT NOT NULL,
part_amount_msat INTEGER NOT NULL, part_amount_msat INTEGER NOT NULL,
@ -38,24 +37,10 @@ CREATE TABLE IF NOT EXISTS outgoing_payment_parts (
part_created_at INTEGER NOT NULL, part_created_at INTEGER NOT NULL,
-- status -- status
part_completed_at INTEGER DEFAULT NULL, part_completed_at INTEGER DEFAULT NULL,
part_status_type TEXT AS OutgoingPartStatusTypeVersion DEFAULT NULL, part_status_type TEXT AS LightningOutgoingPartStatusTypeVersion DEFAULT NULL,
part_status_blob BLOB DEFAULT NULL, part_status_blob BLOB DEFAULT NULL,
FOREIGN KEY(part_parent_id) REFERENCES outgoing_payments(id) FOREIGN KEY(part_parent_id) REFERENCES lightning_outgoing_payments(id)
);
-- !! This table is legacy, and will only contain old payments. See ChannelCloseOutgoingPayment.sq for the new table.
-- Stores the transactions that close a channel
CREATE TABLE IF NOT EXISTS outgoing_payment_closing_tx_parts (
part_id TEXT NOT NULL PRIMARY KEY,
part_parent_id TEXT NOT NULL,
part_tx_id BLOB NOT NULL,
part_amount_sat INTEGER NOT NULL,
part_closing_info_type TEXT AS OutgoingPartClosingInfoTypeVersion NOT NULL,
part_closing_info_blob BLOB NOT NULL,
part_created_at INTEGER NOT NULL,
FOREIGN KEY(part_parent_id) REFERENCES outgoing_payments(id)
); );
-- A FOREIGN KEY does NOT create an implicit index. -- A FOREIGN KEY does NOT create an implicit index.
@ -64,17 +49,16 @@ CREATE TABLE IF NOT EXISTS outgoing_payment_closing_tx_parts (
-- > Indices are not required for child key columns but they are almost always beneficial. -- > Indices are not required for child key columns but they are almost always beneficial.
-- > [...] So, in most real systems, an index should be created on the child key columns -- > [...] So, in most real systems, an index should be created on the child key columns
-- > of each foreign key constraint. -- > of each foreign key constraint.
CREATE INDEX IF NOT EXISTS parent_id_idx ON outgoing_payment_parts(part_parent_id); CREATE INDEX IF NOT EXISTS parent_id_idx ON lightning_outgoing_payment_parts(part_parent_id);
CREATE INDEX IF NOT EXISTS parent_id_idx ON outgoing_payment_closing_tx_parts(part_parent_id);
-- queries for outgoing payments -- queries for outgoing payments
hasPayment: hasPayment:
SELECT COUNT(*) FROM outgoing_payments SELECT COUNT(*) FROM lightning_outgoing_payments
WHERE id = ?; WHERE id = ?;
insertPayment: insertPayment:
INSERT INTO outgoing_payments ( INSERT INTO lightning_outgoing_payments (
id, id,
recipient_amount_msat, recipient_amount_msat,
recipient_node_id, recipient_node_id,
@ -85,23 +69,23 @@ INSERT INTO outgoing_payments (
VALUES (?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?);
updatePayment: updatePayment:
UPDATE outgoing_payments SET completed_at=?, status_type=?, status_blob=? WHERE id=?; UPDATE lightning_outgoing_payments SET completed_at=?, status_type=?, status_blob=? WHERE id=?;
scanCompleted: scanCompleted:
SELECT id, completed_at SELECT id, completed_at
FROM outgoing_payments FROM lightning_outgoing_payments
WHERE completed_at IS NOT NULL; WHERE completed_at IS NOT NULL;
deletePayment: deletePayment:
DELETE FROM outgoing_payments WHERE id = ?; DELETE FROM lightning_outgoing_payments WHERE id = ?;
-- queries for lightning parts -- queries for lightning parts
countLightningPart: countLightningPart:
SELECT COUNT(*) FROM outgoing_payment_parts WHERE part_id = ?; SELECT COUNT(*) FROM lightning_outgoing_payment_parts WHERE part_id = ?;
insertLightningPart: insertLightningPart:
INSERT INTO outgoing_payment_parts ( INSERT INTO lightning_outgoing_payment_parts (
part_id, part_id,
part_parent_id, part_parent_id,
part_amount_msat, part_amount_msat,
@ -110,33 +94,17 @@ INSERT INTO outgoing_payment_parts (
VALUES (?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?);
updateLightningPart: updateLightningPart:
UPDATE outgoing_payment_parts UPDATE lightning_outgoing_payment_parts
SET part_status_type=?, SET part_status_type=?,
part_status_blob=?, part_status_blob=?,
part_completed_at=? part_completed_at=?
WHERE part_id=?; WHERE part_id=?;
getLightningPart: getLightningPart:
SELECT * FROM outgoing_payment_parts WHERE part_id=?; SELECT * FROM lightning_outgoing_payment_parts WHERE part_id=?;
deleteLightningPartsForParentId: deleteLightningPartsForParentId:
DELETE FROM outgoing_payment_parts WHERE part_parent_id = ?; DELETE FROM lightning_outgoing_payment_parts WHERE part_parent_id = ?;
-- queries for closing tx parts
countClosingTxPart:
SELECT COUNT(*) FROM outgoing_payment_closing_tx_parts WHERE part_id = ?;
insertClosingTxPart:
INSERT INTO outgoing_payment_closing_tx_parts (
part_id,
part_parent_id,
part_tx_id,
part_amount_sat,
part_closing_info_type,
part_closing_info_blob,
part_created_at
) VALUES (:id, :parent_id, :tx_id, :amount_msat, :closing_info_type, :closing_info_blob, :created_at);
-- queries mixing outgoing payments and parts -- queries mixing outgoing payments and parts
@ -151,12 +119,12 @@ SELECT id,
completed_at, completed_at,
status_type, status_type,
status_blob status_blob
FROM outgoing_payments FROM lightning_outgoing_payments
WHERE id=?; WHERE id=?;
getOldestCompletedDate: getOldestCompletedDate:
SELECT completed_at SELECT completed_at
FROM outgoing_payments AS o FROM lightning_outgoing_payments AS o
WHERE completed_at IS NOT NULL WHERE completed_at IS NOT NULL
ORDER BY o.completed_at ASC ORDER BY o.completed_at ASC
LIMIT 1; LIMIT 1;
@ -179,17 +147,9 @@ SELECT parent.id,
lightning_parts.part_created_at AS lightning_part_created_at, lightning_parts.part_created_at AS lightning_part_created_at,
lightning_parts.part_completed_at AS lightning_part_completed_at, lightning_parts.part_completed_at AS lightning_part_completed_at,
lightning_parts.part_status_type AS lightning_part_status_type, lightning_parts.part_status_type AS lightning_part_status_type,
lightning_parts.part_status_blob AS lightning_part_status_blob, lightning_parts.part_status_blob AS lightning_part_status_blob
-- closing tx parts FROM lightning_outgoing_payments AS parent
closing_parts.part_id AS closingtx_part_id, LEFT OUTER JOIN lightning_outgoing_payment_parts AS lightning_parts ON lightning_parts.part_parent_id = parent.id
closing_parts.part_tx_id AS closingtx_tx_id,
closing_parts.part_amount_sat AS closingtx_amount_sat,
closing_parts.part_closing_info_type AS closingtx_info_type,
closing_parts.part_closing_info_blob AS closingtx_info_blob,
closing_parts.part_created_at AS closingtx_created_at
FROM outgoing_payments AS parent
LEFT OUTER JOIN outgoing_payment_parts AS lightning_parts ON lightning_parts.part_parent_id = parent.id
LEFT OUTER JOIN outgoing_payment_closing_tx_parts AS closing_parts ON closing_parts.part_parent_id = parent.id
WHERE parent.id=?; WHERE parent.id=?;
listPaymentsForPaymentHash: listPaymentsForPaymentHash:
@ -210,17 +170,9 @@ SELECT parent.id,
lightning_parts.part_created_at AS lightning_part_created_at, lightning_parts.part_created_at AS lightning_part_created_at,
lightning_parts.part_completed_at AS lightning_part_completed_at, lightning_parts.part_completed_at AS lightning_part_completed_at,
lightning_parts.part_status_type AS lightning_part_status_type, lightning_parts.part_status_type AS lightning_part_status_type,
lightning_parts.part_status_blob AS lightning_part_status_blob, lightning_parts.part_status_blob AS lightning_part_status_blob
-- closing tx parts FROM lightning_outgoing_payments AS parent
closing_parts.part_id AS closingtx_part_id, LEFT OUTER JOIN lightning_outgoing_payment_parts AS lightning_parts ON lightning_parts.part_parent_id = parent.id
closing_parts.part_tx_id AS closingtx_tx_id,
closing_parts.part_amount_sat AS closingtx_amount_sat,
closing_parts.part_closing_info_type AS closingtx_info_type,
closing_parts.part_closing_info_blob AS closingtx_info_blob,
closing_parts.part_created_at AS closingtx_created_at
FROM outgoing_payments AS parent
LEFT OUTER JOIN outgoing_payment_parts AS lightning_parts ON lightning_parts.part_parent_id = parent.id
LEFT OUTER JOIN outgoing_payment_closing_tx_parts AS closing_parts ON closing_parts.part_parent_id = parent.id
WHERE payment_hash=?; WHERE payment_hash=?;
-- use this in a `transaction` block to know how many rows were changed after an UPDATE -- use this in a `transaction` block to know how many rows were changed after an UPDATE

View File

@ -2,8 +2,7 @@ package fr.acinq.lightning.bin
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
import fr.acinq.phoenix.db.ChannelsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
import fr.acinq.phoenix.db.PaymentsDatabase
import okio.Path import okio.Path
import okio.Path.Companion.toPath import okio.Path.Companion.toPath
@ -12,13 +11,6 @@ actual val homeDirectory: Path = System.getProperty("user.home").toPath()
actual fun createAppDbDriver(dir: Path): SqlDriver { actual fun createAppDbDriver(dir: Path): SqlDriver {
val path = dir / "phoenix.db" val path = dir / "phoenix.db"
val driver = JdbcSqliteDriver("jdbc:sqlite:$path") val driver = JdbcSqliteDriver("jdbc:sqlite:$path")
ChannelsDatabase.Schema.create(driver) PhoenixDatabase.Schema.create(driver)
return driver
}
actual fun createPaymentsDbDriver(dir: Path): SqlDriver {
val path = dir / "payments.db"
val driver = JdbcSqliteDriver("jdbc:sqlite:$path")
PaymentsDatabase.Schema.create(driver)
return driver return driver
} }

View File

@ -2,8 +2,7 @@ package fr.acinq.lightning.bin
import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver import app.cash.sqldelight.driver.native.NativeSqliteDriver
import fr.acinq.phoenix.db.ChannelsDatabase import fr.acinq.phoenix.db.PhoenixDatabase
import fr.acinq.phoenix.db.PaymentsDatabase
import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString import kotlinx.cinterop.toKString
import okio.Path import okio.Path
@ -15,13 +14,7 @@ import platform.posix.setenv
actual val homeDirectory: Path = setenv("KTOR_LOG_LEVEL", "WARN", 1).let { getenv("HOME")?.toKString()!!.toPath() } actual val homeDirectory: Path = setenv("KTOR_LOG_LEVEL", "WARN", 1).let { getenv("HOME")?.toKString()!!.toPath() }
actual fun createAppDbDriver(dir: Path): SqlDriver { actual fun createAppDbDriver(dir: Path): SqlDriver {
return NativeSqliteDriver(ChannelsDatabase.Schema, "phoenix.db", return NativeSqliteDriver(PhoenixDatabase.Schema, "phoenix.db",
onConfiguration = { it.copy(extendedConfig = it.extendedConfig.copy(basePath = dir.toString())) }
)
}
actual fun createPaymentsDbDriver(dir: Path): SqlDriver {
return NativeSqliteDriver(PaymentsDatabase.Schema, "payments.db",
onConfiguration = { it.copy(extendedConfig = it.extendedConfig.copy(basePath = dir.toString())) } onConfiguration = { it.copy(extendedConfig = it.extendedConfig.copy(basePath = dir.toString())) }
) )
} }