Skip to content

Commit 6aab996

Browse files
authored
Rework IncomingPayment model (#722)
We move from a flat model with a single `IncomingPayment` class and a combination of `Origin` + `ReceivedWith` parts to a hierarchical model. This new model: - makes incoming/outgoing databases symmetrical - removes impossible combinations allowed in the previous model (e.g. an on-chain origin with a fee-credit part) - removes hacks required for the handling of on-chain deposits, which were shoe-horned in what was originally a lightning-only model Before: ``` Origin | `--- Invoice | `--- Offer | `--- SwapIn | `--- OnChain ReceivedWith | `--- LightningPayment | `--- AddedToFeeCredit | `--- OnChainIncomingPayment | `--- NewChannel | `--- SpliceIn ``` After: ``` IncomingPayment | `--- LightningIncomingPayment | | | `--- Bolt11IncomingPayment | | | `--- Bolt12IncomingPayment | `--- OnChainIncomingPayment | `--- NewChannelIncomingPayment | `--- SpliceInIncomingPayment | `--- LegacySwapInIncomingPayment | `--- LegacyPayToOpenIncomingPayment LightningIncomingPayment.Part | `--- Htlc | `--- FeeCredit ``` The handling of backward compatible data is tricky, especially for legacy pay-to-open/pay-to-splice, which can be a mix of lightning parts and on-chain parts, and either Bolt11 or Bolt12. Note that `Legacy*` classes are not used at all within `lightning-kmp`, they are just meant to handle pre-existing data in the database. payment | old model | new model -------------------------------|-----------------------------------------------|------------------------ Plain Lightning Bolt11 | Origin=Invoice, ReceivedWith=LightningPayment | Bolt11IncomingPayment Plain Lightning Bolt12 | Origin=Offer, ReceivedWith=LightningPayment | Bolt12IncomingPayment Pre-otf pay-to-open Bolt11 | Origin=Invoice, ReceivedWith=NewChannel | LegacyPayToOpenIncomingPayment Pre-otf pay-to-splice Bolt11 | Origin=Invoice, ReceivedWith=SpliceIn | LegacyPayToOpenIncomingPayment Pre-otf pay-to-open Bolt12 | Origin=Offer, ReceivedWith=NewChannel | LegacyPayToOpenIncomingPayment Pre-otf pay-to-splice Bolt12 | Origin=Offer, ReceivedWith=SpliceIn | LegacyPayToOpenIncomingPayment Legacy trusted swap-in | Origin=SwapIn, ReceivedWith=NewChannel | LegacySwapInIncomingPayment Swap-in potentiam open | Origin=OnChain, ReceivedWith=NewChannel | NewChannelIncomingPayment Swap-in potentiam splice | Origin=OnChain, ReceivedWith=SpliceIn | SpliceInIncomingPayment
1 parent 7cfdb96 commit 6aab996

File tree

10 files changed

+458
-429
lines changed

10 files changed

+458
-429
lines changed

modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import fr.acinq.lightning.channel.states.ChannelStateWithCommitments
1212
import fr.acinq.lightning.channel.states.Normal
1313
import fr.acinq.lightning.channel.states.WaitForFundingCreated
1414
import fr.acinq.lightning.db.IncomingPayment
15+
import fr.acinq.lightning.db.LightningIncomingPayment
1516
import fr.acinq.lightning.db.OutgoingPayment
1617
import fr.acinq.lightning.utils.sum
1718
import fr.acinq.lightning.wire.Init
@@ -70,9 +71,6 @@ sealed interface SensitiveTaskEvents : NodeEvents {
7071
data object UpgradeRequired : NodeEvents
7172

7273
sealed interface PaymentEvents : NodeEvents {
73-
data class PaymentReceived(val paymentHash: ByteVector32, val receivedWith: List<IncomingPayment.ReceivedWith>) : PaymentEvents {
74-
val amount: MilliSatoshi = receivedWith.map { it.amountReceived }.sum()
75-
val fees: MilliSatoshi = receivedWith.map { it.fees }.sum()
76-
}
74+
data class PaymentReceived(val payment: IncomingPayment) : PaymentEvents
7775
data class PaymentSent(val payment: OutgoingPayment) : PaymentEvents
7876
}

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelAction.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import fr.acinq.lightning.channel.states.PersistedChannelState
99
import fr.acinq.lightning.db.ChannelClosingType
1010
import fr.acinq.lightning.transactions.Transactions
1111
import fr.acinq.lightning.utils.UUID
12-
import fr.acinq.lightning.utils.toMilliSatoshi
1312
import fr.acinq.lightning.wire.*
1413

1514
/** Channel Actions (outputs produced by the state machine). */
@@ -80,7 +79,7 @@ sealed class ChannelAction {
8079
abstract val txId: TxId
8180
abstract val localInputs: Set<OutPoint>
8281
data class ViaNewChannel(val amountReceived: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
83-
data class ViaSpliceIn(val amountReceived: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
82+
data class ViaSpliceIn(val amountReceived: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
8483
}
8584
/** Payment sent through on-chain operations (channel close or splice-out) */
8685
sealed class StoreOutgoingPayment : Storage() {

modules/core/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt

-1
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,6 @@ data class Normal(
883883
if (action.fundingTx.sharedTx.tx.localInputs.isNotEmpty()) add(
884884
ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn(
885885
amountReceived = action.fundingTx.sharedTx.tx.localInputs.map { i -> i.txOut.amount }.sum().toMilliSatoshi() - action.fundingTx.sharedTx.tx.localFees,
886-
serviceFee = 0.msat,
887886
miningFee = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi(),
888887
localInputs = action.fundingTx.sharedTx.tx.localInputs.map { it.outPoint }.toSet(),
889888
txId = action.fundingTx.txId,

modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt

+159-94
Large diffs are not rendered by default.

modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt

+31-7
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ data class SendOnTheFlyFundingMessage(val message: OnTheFlyFundingMessage) : Pee
138138

139139
sealed class PeerEvent
140140

141-
@Deprecated("Replaced by NodeEvents", replaceWith = ReplaceWith("PaymentEvents.PaymentReceived", "fr.acinq.lightning.PaymentEvents"))
142-
data class PaymentReceived(val incomingPayment: IncomingPayment, val received: IncomingPayment.Received) : PeerEvent()
143141
data class PaymentProgress(val request: SendPayment, val fees: MilliSatoshi) : PeerEvent()
144142
sealed class SendPaymentResult : PeerEvent() {
145143
abstract val request: SendPayment
@@ -847,8 +845,36 @@ class Peer(
847845
action.htlcs.forEach { db.channels.addHtlcInfo(actualChannelId, it.commitmentNumber, it.paymentHash, it.cltvExpiry) }
848846
}
849847
is ChannelAction.Storage.StoreIncomingPayment -> {
850-
logger.info { "storing incoming payment $action" }
851-
incomingPaymentHandler.process(actualChannelId, action)
848+
logger.info { "storing $action" }
849+
val payment = when (action) {
850+
is ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel ->
851+
NewChannelIncomingPayment(
852+
id = UUID.randomUUID(),
853+
amountReceived = action.amountReceived,
854+
serviceFee = action.serviceFee,
855+
miningFee = action.miningFee,
856+
channelId = channelId,
857+
txId = action.txId,
858+
localInputs = action.localInputs,
859+
createdAt = currentTimestampMillis(),
860+
confirmedAt = null,
861+
lockedAt = null,
862+
)
863+
is ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn ->
864+
SpliceInIncomingPayment(
865+
id = UUID.randomUUID(),
866+
amountReceived = action.amountReceived,
867+
miningFee = action.miningFee,
868+
channelId = channelId,
869+
txId = action.txId,
870+
localInputs = action.localInputs,
871+
createdAt = currentTimestampMillis(),
872+
confirmedAt = null,
873+
lockedAt = null,
874+
)
875+
}
876+
nodeParams._nodeEvents.emit(PaymentEvents.PaymentReceived(payment))
877+
db.payments.addIncomingPayment(payment)
852878
}
853879
is ChannelAction.Storage.StoreOutgoingPayment -> {
854880
logger.info { "storing $action" }
@@ -938,12 +964,10 @@ class Peer(
938964
}
939965
when (result) {
940966
is IncomingPaymentHandler.ProcessAddResult.Accepted -> {
941-
if ((result.incomingPayment.received?.receivedWith?.size ?: 0) > 1) {
967+
if (result.incomingPayment.parts.size > 1) {
942968
// this was a multi-part payment, we signal that the task is finished
943969
nodeParams._nodeEvents.tryEmit(SensitiveTaskEvents.TaskEnded(SensitiveTaskEvents.TaskIdentifier.IncomingMultiPartPayment(result.incomingPayment.paymentHash)))
944970
}
945-
@Suppress("DEPRECATION")
946-
_eventsFlow.emit(PaymentReceived(result.incomingPayment, result.received))
947971
}
948972
is IncomingPaymentHandler.ProcessAddResult.Pending -> if (result.pendingPayment.parts.size == 1) {
949973
// this is the first part of a multi-part payment, we request to keep the app alive to receive subsequent parts

0 commit comments

Comments
 (0)