diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index 65b1573e6..e588e75e3 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -49,7 +49,7 @@ interface IncomingPaymentsDb { * * @param parts Is a list containing the payment parts holding the incoming amount. */ - suspend fun receiveLightningPayment(paymentHash: ByteVector32, parts: List, receivedAt: Long = currentTimestampMillis()) + suspend fun receiveLightningPayment(paymentHash: ByteVector32, parts: List) /** List expired unpaid normal payments created within specified time range (with the most recent payments first). */ suspend fun listLightningExpiredPayments(fromCreatedAt: Long = 0, toCreatedAt: Long = currentTimestampMillis()): List @@ -129,46 +129,40 @@ sealed class LightningIncomingPayment(val paymentPreimage: ByteVector32) : Incom override val id: UUID = UUID.fromBytes(paymentHash.toByteArray().copyOf(16)) - /** Funds received for this payment, null if no funds have been received yet. */ - abstract val received: Received? + /** Funds received for this payment, empty if no funds have been received yet. */ + abstract val parts: List /** This timestamp will be defined when the received amount is usable for spending. */ - override val completedAt: Long? get() = received?.receivedAt + override val completedAt: Long? get() = parts.maxByOrNull { it.receivedAt }?.receivedAt /** Total fees paid to receive this payment. */ - override val fees: MilliSatoshi get() = received?.fees ?: 0.msat + override val fees: MilliSatoshi get() = parts.map { it.fees }.sum() /** Total amount actually received for this payment after applying the fees. If someone sent you 500 and the fee was 10, this amount will be 490. */ - override val amountReceived: MilliSatoshi get() = received?.amount ?: 0.msat + override val amountReceived: MilliSatoshi get() = parts.map { it.amountReceived }.sum() - data class Received(val parts: List, val receivedAt: Long = currentTimestampMillis()) { - /** Total amount received after applying the fees. */ - val amount: MilliSatoshi = parts.map { it.amountReceived }.sum() - - /** Fees applied to receive this payment. */ - val fees: MilliSatoshi = parts.map { it.fees }.sum() + sealed class Part { + /** Amount received for this part after applying the fees. This is the final amount we can use. */ + abstract val amountReceived: MilliSatoshi - sealed class Part { - /** Amount received for this part after applying the fees. This is the final amount we can use. */ - abstract val amountReceived: MilliSatoshi + /** Fees applied to receive this part.*/ + abstract val fees: MilliSatoshi - /** Fees applied to receive this part.*/ - abstract val fees: MilliSatoshi + abstract val receivedAt: Long - /** Payment was received via existing lightning channels. */ - data class Htlc(override val amountReceived: MilliSatoshi, val channelId: ByteVector32, val htlcId: Long, val fundingFee: LiquidityAds.FundingFee?) : Part() { - // If there is no funding fee, the fees are paid by the sender for lightning payments. - override val fees: MilliSatoshi = fundingFee?.amount ?: 0.msat - } + /** Payment was received via existing lightning channels. */ + data class Htlc(override val amountReceived: MilliSatoshi, val channelId: ByteVector32, val htlcId: Long, val fundingFee: LiquidityAds.FundingFee?, override val receivedAt: Long = currentTimestampMillis()) : Part() { + // If there is no funding fee, the fees are paid by the sender for lightning payments. + override val fees: MilliSatoshi = fundingFee?.amount ?: 0.msat + } - /** - * Payment was added to our fee credit for future on-chain operations (see [fr.acinq.lightning.Feature.FundingFeeCredit]). - * We didn't really receive this amount yet, but we trust our peer to use it for future on-chain operations. - */ - data class FeeCredit(override val amountReceived: MilliSatoshi) : Part() { - // Adding to the fee credit doesn't cost any fees. - override val fees: MilliSatoshi = 0.msat - } + /** + * Payment was added to our fee credit for future on-chain operations (see [fr.acinq.lightning.Feature.FundingFeeCredit]). + * We didn't really receive this amount yet, but we trust our peer to use it for future on-chain operations. + */ + data class FeeCredit(override val amountReceived: MilliSatoshi, override val receivedAt: Long = currentTimestampMillis()) : Part() { + // Adding to the fee credit doesn't cost any fees. + override val fees: MilliSatoshi = 0.msat } } @@ -177,14 +171,10 @@ sealed class LightningIncomingPayment(val paymentPreimage: ByteVector32) : Incom companion object { /** Helper method to facilitate updating child classes */ - fun LightningIncomingPayment.addReceivedParts(parts: List, receivedAt: Long): LightningIncomingPayment { - val newReceived = when (val received = this.received) { - null -> Received(parts, receivedAt) - else -> received.copy(parts = received.parts + parts) - } + fun LightningIncomingPayment.addReceivedParts(parts: List): LightningIncomingPayment { return when (this) { - is Bolt11IncomingPayment -> copy(received = newReceived) - is Bolt12IncomingPayment -> copy(received = newReceived) + is Bolt11IncomingPayment -> copy(parts = this.parts + parts) + is Bolt12IncomingPayment -> copy(parts = this.parts + parts) } } } @@ -194,7 +184,7 @@ sealed class LightningIncomingPayment(val paymentPreimage: ByteVector32) : Incom data class Bolt11IncomingPayment( private val preimage: ByteVector32, val paymentRequest: Bolt11Invoice, - override val received: Received?, + override val parts: List = emptyList(), override val createdAt: Long = currentTimestampMillis() ) : LightningIncomingPayment(preimage) @@ -202,7 +192,7 @@ data class Bolt11IncomingPayment( data class Bolt12IncomingPayment( private val preimage: ByteVector32, val metadata: OfferPaymentMetadata, - override val received: Received?, + override val parts: List = emptyList(), override val createdAt: Long = currentTimestampMillis() ) : LightningIncomingPayment(preimage) diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index 14f085488..6e4e9e932 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -964,7 +964,7 @@ class Peer( } when (result) { is IncomingPaymentHandler.ProcessAddResult.Accepted -> { - if ((result.incomingPayment.received?.parts?.size ?: 0) > 1) { + if (result.incomingPayment.parts.size > 1) { // this was a multi-part payment, we signal that the task is finished nodeParams._nodeEvents.tryEmit(SensitiveTaskEvents.TaskEnded(SensitiveTaskEvents.TaskIdentifier.IncomingMultiPartPayment(result.incomingPayment.paymentHash))) } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt index 40932e2bd..d3b19e086 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandler.kt @@ -8,6 +8,7 @@ import fr.acinq.lightning.blockchain.fee.FeeratePerKw import fr.acinq.lightning.channel.* import fr.acinq.lightning.crypto.sphinx.Sphinx import fr.acinq.lightning.db.* +import fr.acinq.lightning.db.LightningIncomingPayment.Companion.addReceivedParts import fr.acinq.lightning.io.AddLiquidityForIncomingPayment import fr.acinq.lightning.io.PeerCommand import fr.acinq.lightning.io.SendOnTheFlyFundingMessage @@ -47,7 +48,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { sealed class ProcessAddResult { abstract val actions: List - data class Accepted(override val actions: List, val incomingPayment: LightningIncomingPayment, val received: LightningIncomingPayment.Received) : ProcessAddResult() + data class Accepted(override val actions: List, val incomingPayment: LightningIncomingPayment, val parts: List) : ProcessAddResult() data class Rejected(override val actions: List, val incomingPayment: LightningIncomingPayment?) : ProcessAddResult() data class Pending(val incomingPayment: LightningIncomingPayment, val pendingPayment: PendingPayment, override val actions: List = listOf()) : ProcessAddResult() } @@ -100,7 +101,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { timestampSeconds ) logger.info(mapOf("paymentHash" to paymentHash)) { "generated payment request ${pr.write()}" } - val incomingPayment = Bolt11IncomingPayment(paymentPreimage, pr, received = null) + val incomingPayment = Bolt11IncomingPayment(paymentPreimage, pr) db.addIncomingPayment(incomingPayment) return pr } @@ -149,8 +150,8 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { is Either.Left -> validationResult.value is Either.Right -> { val incomingPayment = validationResult.value - val received = incomingPayment.received - if (received != null) { + val receivedParts = incomingPayment.parts + if (receivedParts.isNotEmpty()) { return when (paymentPart) { is HtlcPart -> { // The invoice for this payment hash has already been paid. Two possible scenarios: @@ -162,11 +163,11 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { // // 2) This is a new htlc. This can happen when a sender pays an already paid invoice. In that case the // htlc can be safely rejected. - val htlcsMapInDb = received.parts.filterIsInstance().map { it.channelId to it.htlcId } + val htlcsMapInDb = receivedParts.filterIsInstance().map { it.channelId to it.htlcId } if (htlcsMapInDb.contains(paymentPart.htlc.channelId to paymentPart.htlc.id)) { logger.info { "accepting local replay of htlc=${paymentPart.htlc.id} on channel=${paymentPart.htlc.channelId}" } val action = WrappedChannelCommand(paymentPart.htlc.channelId, ChannelCommand.Htlc.Settlement.Fulfill(paymentPart.htlc.id, incomingPayment.paymentPreimage, true)) - ProcessAddResult.Accepted(listOf(action), incomingPayment, received) + ProcessAddResult.Accepted(listOf(action), incomingPayment, receivedParts) } else { logger.info { "rejecting htlc part for an invoice that has already been paid" } val action = actionForFailureMessage(IncorrectOrUnknownPaymentDetails(paymentPart.totalAmount, currentBlockHeight.toLong()), paymentPart.htlc) @@ -233,8 +234,8 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { addToFeeCredit -> { logger.info { "adding on-the-fly funding to fee credit (amount=${willAddHtlcParts.map { it.amount }.sum()})" } val parts = buildList { - htlcParts.forEach { add(LightningIncomingPayment.Received.Part.Htlc(it.amount, it.htlc.channelId, it.htlc.id, it.htlc.fundingFee)) } - willAddHtlcParts.forEach { add(LightningIncomingPayment.Received.Part.FeeCredit(it.amount)) } + htlcParts.forEach { add(LightningIncomingPayment.Part.Htlc(it.amount, it.htlc.channelId, it.htlc.id, it.htlc.fundingFee)) } + willAddHtlcParts.forEach { add(LightningIncomingPayment.Part.FeeCredit(it.amount)) } } val actions = buildList { // We send a single add_fee_credit for the will_add_htlc set. @@ -280,7 +281,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { rejectPayment(payment, incomingPayment, failure) } is Either.Right -> { - val parts = htlcParts.map { part -> LightningIncomingPayment.Received.Part.Htlc(part.amount, part.htlc.channelId, part.htlc.id, part.htlc.fundingFee) } + val parts = htlcParts.map { part -> LightningIncomingPayment.Part.Htlc(part.amount, part.htlc.channelId, part.htlc.id, part.htlc.fundingFee) } val actions = htlcParts.map { part -> val cmd = ChannelCommand.Htlc.Settlement.Fulfill(part.htlc.id, incomingPayment.paymentPreimage, true) WrappedChannelCommand(part.htlc.channelId, cmd) @@ -296,7 +297,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { } } - private suspend fun acceptPayment(incomingPayment: LightningIncomingPayment, parts: List, actions: List): ProcessAddResult.Accepted { + private suspend fun acceptPayment(incomingPayment: LightningIncomingPayment, parts: List, actions: List): ProcessAddResult.Accepted { pending.remove(incomingPayment.paymentHash) if (incomingPayment is Bolt12IncomingPayment) { // We didn't store the Bolt 12 invoice in our DB when receiving the invoice_request (to protect against DoS). @@ -304,13 +305,9 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { db.addIncomingPayment(incomingPayment) } db.receiveLightningPayment(incomingPayment.paymentHash, parts) - val received = LightningIncomingPayment.Received(parts) - val incomingPayment1 = when (incomingPayment) { - is Bolt11IncomingPayment -> incomingPayment.copy(received = received) - is Bolt12IncomingPayment -> incomingPayment.copy(received = received) - } + val incomingPayment1 = incomingPayment.addReceivedParts(parts) nodeParams._nodeEvents.emit(PaymentEvents.PaymentReceived(incomingPayment1)) - return ProcessAddResult.Accepted(actions, incomingPayment1, received) + return ProcessAddResult.Accepted(actions, incomingPayment1, parts) } private fun rejectPayment(payment: PendingPayment, incomingPayment: LightningIncomingPayment, failure: FailureMessage): ProcessAddResult.Rejected { @@ -410,7 +407,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { } // Payments are rejected for expired invoices UNLESS invoice has already been paid // We must accept payments for already paid invoices, because it could be the channel replaying HTLCs that we already fulfilled - incomingPayment.isExpired() && incomingPayment.received == null -> { + incomingPayment.isExpired() && incomingPayment.parts.isEmpty() -> { logger.warning { "the invoice is expired" } Either.Left(rejectPaymentPart(privateKey, paymentPart, incomingPayment, currentBlockHeight)) } @@ -465,7 +462,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { Either.Left(rejectPaymentPart(privateKey, paymentPart, null, currentBlockHeight)) } else -> { - val incomingPayment = db.getLightningIncomingPayment(paymentPart.paymentHash) ?: Bolt12IncomingPayment(metadata.preimage, metadata, received = null) + val incomingPayment = db.getLightningIncomingPayment(paymentPart.paymentHash) ?: Bolt12IncomingPayment(metadata.preimage, metadata) when { incomingPayment !is Bolt12IncomingPayment -> { logger.warning { "unsupported payment type: ${incomingPayment::class}" } @@ -483,7 +480,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb) { logger.warning { "payment with expiry too small: ${paymentPart.htlc.cltvExpiry}, min is ${minFinalCltvExpiry(nodeParams, paymentPart, incomingPayment, currentBlockHeight)}" } Either.Left(rejectPaymentPart(privateKey, paymentPart, incomingPayment, currentBlockHeight)) } - metadata.createdAtMillis + nodeParams.bolt12InvoiceExpiry.inWholeMilliseconds < currentTimestampMillis() && incomingPayment.received == null -> { + metadata.createdAtMillis + nodeParams.bolt12InvoiceExpiry.inWholeMilliseconds < currentTimestampMillis() && incomingPayment.parts.isEmpty() -> { logger.warning { "the invoice is expired" } Either.Left(rejectPaymentPart(privateKey, paymentPart, incomingPayment, currentBlockHeight)) } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt index 9a26e7ae9..6ccb65d45 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/InMemoryPaymentsDb.kt @@ -2,6 +2,7 @@ package fr.acinq.lightning.db import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.TxId +import fr.acinq.lightning.db.LightningIncomingPayment.Companion.addReceivedParts import fr.acinq.lightning.payment.FinalFailure import fr.acinq.lightning.utils.UUID @@ -29,22 +30,10 @@ class InMemoryPaymentsDb : PaymentsDb { override suspend fun getLightningIncomingPayment(paymentHash: ByteVector32): LightningIncomingPayment? = incoming[paymentHash] - override suspend fun receiveLightningPayment(paymentHash: ByteVector32, parts: List, receivedAt: Long) { + override suspend fun receiveLightningPayment(paymentHash: ByteVector32, parts: List) { when (val payment = incoming[paymentHash]) { null -> Unit // no-op - is Bolt11IncomingPayment -> - incoming[paymentHash] = payment.copy( - received = LightningIncomingPayment.Received( - parts = payment.received?.parts.orEmpty() + parts, - receivedAt = receivedAt - ) - ) - is Bolt12IncomingPayment -> incoming[paymentHash] = payment.copy( - received = LightningIncomingPayment.Received( - parts = payment.received?.parts.orEmpty() + parts, - receivedAt = receivedAt - ) - ) + else -> incoming[paymentHash] = payment.addReceivedParts(parts) } } @@ -61,14 +50,14 @@ class InMemoryPaymentsDb : PaymentsDb { .asSequence() .filter { it.createdAt in fromCreatedAt until toCreatedAt } .filter { it.isExpired() } - .filter { it.received == null } + .filter { it.parts.isEmpty() } .sortedByDescending { it.createdAt } .toList() override suspend fun removeLightningIncomingPayment(paymentHash: ByteVector32): Boolean { val payment = getLightningIncomingPayment(paymentHash) - return when (payment?.received) { - null -> incoming.remove(paymentHash) != null + return when (payment?.parts?.isEmpty()) { + true -> incoming.remove(paymentHash) != null else -> false // do nothing if payment already partially paid } } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt index 5fd9e4d00..541182eea 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/db/PaymentsDbTestsCommon.kt @@ -5,6 +5,7 @@ import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.* import fr.acinq.lightning.Lightning.randomBytes32 import fr.acinq.lightning.Lightning.randomKey +import fr.acinq.lightning.db.LightningIncomingPayment.Companion.addReceivedParts import fr.acinq.lightning.payment.Bolt11Invoice import fr.acinq.lightning.payment.FinalFailure import fr.acinq.lightning.tests.utils.LightningTestSuite @@ -21,17 +22,17 @@ class PaymentsDbTestsCommon : LightningTestSuite() { assertNull(db.getLightningIncomingPayment(pr.paymentHash)) val channelId = randomBytes32() - val incoming = Bolt11IncomingPayment(preimage, pr, null, 100) + val incoming = Bolt11IncomingPayment(preimage, pr, createdAt = 100) db.addIncomingPayment(incoming) val pending = db.getLightningIncomingPayment(pr.paymentHash) assertIs(pending) assertEquals(incoming, pending) - val parts = LightningIncomingPayment.Received.Part.Htlc(200_000.msat, channelId, 1, fundingFee = null) - db.receiveLightningPayment(pr.paymentHash, listOf(parts), 110) + val parts = LightningIncomingPayment.Part.Htlc(200_000.msat, channelId, 1, fundingFee = null, receivedAt = 110) + db.receiveLightningPayment(pr.paymentHash, listOf(parts)) val received = db.getLightningIncomingPayment(pr.paymentHash) assertNotNull(received) - assertEquals(pending.copy(received = LightningIncomingPayment.Received(listOf(parts), 110)), received) + assertEquals(pending.addReceivedParts(listOf(parts)), received) } @Test @@ -40,7 +41,7 @@ class PaymentsDbTestsCommon : LightningTestSuite() { assertNull(db.getLightningIncomingPayment(pr.paymentHash)) val (channelId1, channelId2) = listOf(randomBytes32(), randomBytes32()) - val incoming = Bolt11IncomingPayment(preimage, pr, null, 200) + val incoming = Bolt11IncomingPayment(preimage, pr, createdAt = 200) db.addIncomingPayment(incoming) val pending = db.getLightningIncomingPayment(pr.paymentHash) assertIs(pending) @@ -48,19 +49,19 @@ class PaymentsDbTestsCommon : LightningTestSuite() { db.receiveLightningPayment( pr.paymentHash, listOf( - LightningIncomingPayment.Received.Part.Htlc(57_000.msat, channelId1, 1, fundingFee = null), - LightningIncomingPayment.Received.Part.Htlc(43_000.msat, channelId2, 54, fundingFee = null), - ), 110 + LightningIncomingPayment.Part.Htlc(57_000.msat, channelId1, 1, fundingFee = null, receivedAt = 110), + LightningIncomingPayment.Part.Htlc(43_000.msat, channelId2, 54, fundingFee = null, receivedAt = 110), + ) ) val received = db.getLightningIncomingPayment(pr.paymentHash) assertNotNull(received) assertEquals(100_000.msat, received.amount) assertEquals(0.msat, received.fees) - assertEquals(2, received.received!!.parts.size) - assertEquals(57_000.msat, received.received!!.parts.elementAt(0).amountReceived) - assertEquals(0.msat, received.received!!.parts.elementAt(0).fees) - assertEquals(channelId1, (received.received!!.parts.elementAt(0) as LightningIncomingPayment.Received.Part.Htlc).channelId) - assertEquals(54, (received.received!!.parts.elementAt(1) as LightningIncomingPayment.Received.Part.Htlc).htlcId) + assertEquals(2, received.parts.size) + assertEquals(57_000.msat, received.parts.elementAt(0).amountReceived) + assertEquals(0.msat, received.parts.elementAt(0).fees) + assertEquals(channelId1, (received.parts.elementAt(0) as LightningIncomingPayment.Part.Htlc).channelId) + assertEquals(54, (received.parts.elementAt(1) as LightningIncomingPayment.Part.Htlc).htlcId) } @Test @@ -68,36 +69,33 @@ class PaymentsDbTestsCommon : LightningTestSuite() { val (db, preimage, pr) = createFixture() val channelId = randomBytes32() - val incoming = Bolt11IncomingPayment(preimage, pr, null, 200) + val incoming = Bolt11IncomingPayment(preimage, pr, createdAt = 200) db.addIncomingPayment(incoming) val parts = listOf( - LightningIncomingPayment.Received.Part.Htlc(200_000.msat, channelId, 1, fundingFee = null), - LightningIncomingPayment.Received.Part.Htlc(100_000.msat, channelId, 2, fundingFee = null) + LightningIncomingPayment.Part.Htlc(200_000.msat, channelId, 1, fundingFee = null, receivedAt = 110), + LightningIncomingPayment.Part.Htlc(100_000.msat, channelId, 2, fundingFee = null, receivedAt = 150) ) - db.receiveLightningPayment(pr.paymentHash, listOf(parts.first()), 110) + db.receiveLightningPayment(pr.paymentHash, listOf(parts.first())) val received1 = db.getLightningIncomingPayment(pr.paymentHash) assertNotNull(received1) - assertNotNull(received1.received) assertEquals(200_000.msat, received1.amount) - db.receiveLightningPayment(pr.paymentHash, listOf(parts.last()), 150) + db.receiveLightningPayment(pr.paymentHash, listOf(parts.last())) val received2 = db.getLightningIncomingPayment(pr.paymentHash) assertNotNull(received2) - assertNotNull(received2.received) assertEquals(300_000.msat, received2.amount) - assertEquals(150, received2.received!!.receivedAt) - assertEquals(parts, received2.received!!.parts) + assertEquals(150, received2.completedAt) + assertEquals(parts, received2.parts) } @Test fun `receive lightning payment with funding fee`() = runSuspendTest { val (db, preimage, pr) = createFixture() - val incoming = Bolt11IncomingPayment(preimage, pr, null, 200) + val incoming = Bolt11IncomingPayment(preimage, pr, createdAt = 200) db.addIncomingPayment(incoming) - val parts = LightningIncomingPayment.Received.Part.Htlc(40_000_000.msat, randomBytes32(), 3, LiquidityAds.FundingFee(10_000_000.msat, TxId(randomBytes32()))) - db.receiveLightningPayment(pr.paymentHash, listOf(parts), 110) + val parts = LightningIncomingPayment.Part.Htlc(40_000_000.msat, randomBytes32(), 3, LiquidityAds.FundingFee(10_000_000.msat, TxId(randomBytes32())), receivedAt = 110) + db.receiveLightningPayment(pr.paymentHash, listOf(parts)) val received = db.getLightningIncomingPayment(pr.paymentHash) - assertNotNull(received?.received) assertEquals(40_000_000.msat, received!!.amount) assertEquals(10_000_000.msat, received.fees) } @@ -105,15 +103,15 @@ class PaymentsDbTestsCommon : LightningTestSuite() { @Test fun `reject duplicate payment hash`() = runSuspendTest { val (db, preimage, pr) = createFixture() - db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr, null)) - assertFails { db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr, null)) } + db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr)) + assertFails { db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr)) } } @Test fun `set expired invoices`() = runSuspendTest { val (db, preimage, _) = createFixture() val pr = createExpiredInvoice(preimage) - db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr, null)) + db.addIncomingPayment(Bolt11IncomingPayment(preimage, pr)) val expired = db.getLightningIncomingPayment(pr.paymentHash) assertIs(expired) diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt index 1a290a7b4..c1d5a27b4 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/io/peer/PeerTest.kt @@ -533,7 +533,7 @@ class PeerTest : LightningTestSuite() { alice.forward(bob2alice3.expect(), connectionId = 2) bob.forward(alice2bob3.expect(), connectionId = 2) - assertEquals(invoice.amount, alice.db.payments.getLightningIncomingPayment(invoice.paymentHash)?.received?.amount) + assertEquals(invoice.amount, alice.db.payments.getLightningIncomingPayment(invoice.paymentHash)?.amount) } @Test diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt index a7168b48d..7c17796df 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/IncomingPaymentHandlerTestsCommon.kt @@ -143,9 +143,9 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val expected = ChannelCommand.Htlc.Settlement.Fulfill(add.id, incomingPayment.paymentPreimage, commit = true) assertEquals(setOf(WrappedChannelCommand(add.channelId, expected)), result.actions.toSet()) - assertEquals(result.incomingPayment.received, result.received) - assertEquals(defaultAmount, result.received.amount) - assertEquals(listOf(LightningIncomingPayment.Received.Part.Htlc(defaultAmount, channelId, 12, null)), result.received.parts) + assertEqualsIgnoreTimestamps(result.incomingPayment.parts, result.parts) + assertEquals(defaultAmount, result.amount) + assertEqualsIgnoreTimestamps(listOf(LightningIncomingPayment.Part.Htlc(defaultAmount, channelId, 12, null)), result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } @@ -173,15 +173,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(5, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount2, totalAmount, paymentSecret)) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, defaultPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, fundingFee = null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(5, defaultPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2, channelId, 5, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, defaultPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(5, defaultPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2, channelId, 5, fundingFee = null), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -198,7 +198,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(0, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount1, totalAmount, paymentSecret)) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - assertNull(result.incomingPayment.received) + assertTrue(result.incomingPayment.parts.isEmpty()) assertTrue(result.actions.isEmpty()) add } @@ -210,7 +210,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { run { val result = paymentHandler.process(add1, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - assertNull(result.incomingPayment.received) + assertTrue(result.incomingPayment.parts.isEmpty()) assertTrue(result.actions.isEmpty()) } @@ -219,15 +219,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(1, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount2, totalAmount, paymentSecret)) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, defaultPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, fundingFee = null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, defaultPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2, channelId, 1, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, defaultPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, defaultPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2, channelId, 1, fundingFee = null), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -248,7 +248,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(TestConstants.fundingRates.fundingRates.first(), addLiquidity.fundingRate) assertEquals(listOf(willAddHtlc), addLiquidity.willAddHtlcs) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -265,7 +265,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertIs(addLiquidity) assertEquals(555_556.sat, addLiquidity.requestedAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -296,7 +296,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(incomingPayment.paymentPreimage, addLiquidity.preimage) assertEquals(amount * 2, addLiquidity.paymentAmount) assertEquals(2, addLiquidity.willAddHtlcs.size) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } } @@ -330,7 +330,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(incomingPayment.paymentPreimage, addLiquidity.preimage) assertEquals(totalAmount, addLiquidity.paymentAmount) assertEquals(2, addLiquidity.willAddHtlcs.size) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } } @@ -359,7 +359,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(defaultAmount, addLiquidity.paymentAmount) assertEquals(defaultAmount, addLiquidity.requestedAmount.toMilliSatoshi()) assertEquals(TestConstants.fundingRates.fundingRates.first(), addLiquidity.fundingRate) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -490,7 +490,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(incomingPayment.paymentPreimage, addLiquidity.preimage) assertEquals(amount2.truncateToSatoshi(), addLiquidity.requestedAmount) assertEquals(totalAmount, addLiquidity.paymentAmount) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } // Step 3 of 3: @@ -500,15 +500,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val htlc = makeUpdateAddHtlc(1, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount2, totalAmount, paymentSecret)) val result = paymentHandler.process(htlc, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, fundingFee = null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2, channelId, 1, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2, channelId, 1, fundingFee = null), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -544,7 +544,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertIs(willFailHtlc).also { assertEquals(willAddHtlc.id, it.id) } val failHtlc = ChannelCommand.Htlc.Settlement.Fail(0, ChannelCommand.Htlc.Settlement.Fail.Reason.Failure(TemporaryNodeFailure), commit = true) assertTrue(result.actions.contains(WrappedChannelCommand(channelId, failHtlc))) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } // Step 3 of 4: @@ -564,15 +564,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val htlc = makeUpdateAddHtlc(2, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount2, totalAmount, paymentSecret)) val result = paymentHandler.process(htlc, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 1, fundingFee = null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(2, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2, channelId, 2, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 1, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(2, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2, channelId, 2, fundingFee = null), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -610,7 +610,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(willAddHtlcs.map { it.id }.toSet(), willFailHtlcs.map { it.id }.toSet()) val failHtlc = ChannelCommand.Htlc.Settlement.Fail(htlc.id, ChannelCommand.Htlc.Settlement.Fail.Reason.Failure(TemporaryNodeFailure), commit = true) assertTrue(result.actions.contains(WrappedChannelCommand(channelId, failHtlc))) - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -633,8 +633,8 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { null -> { assertIs(result) assertEquals(listOf(SendOnTheFlyFundingMessage(AddFeeCredit(paymentHandler.nodeParams.chainHash, incomingPayment.paymentPreimage))), result.actions) - assertEquals(totalAmount, result.received.amount) - assertEquals(listOf(LightningIncomingPayment.Received.Part.FeeCredit(totalAmount)), result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(listOf(LightningIncomingPayment.Part.FeeCredit(totalAmount)), result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } else -> { @@ -675,15 +675,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val willAddHtlc = makeWillAddHtlc(paymentHandler, incomingPayment.paymentHash, makeMppPayload(amount2, totalAmount, paymentSecret)) val result = paymentHandler.process(willAddHtlc, feeCreditFeatures, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates, currentFeeCredit = 0.msat) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, fundingFee = null), - SendOnTheFlyFundingMessage(AddFeeCredit(paymentHandler.nodeParams.chainHash, incomingPayment.paymentPreimage)) to LightningIncomingPayment.Received.Part.FeeCredit(amount2), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, fundingFee = null), + SendOnTheFlyFundingMessage(AddFeeCredit(paymentHandler.nodeParams.chainHash, incomingPayment.paymentPreimage)) to LightningIncomingPayment.Part.FeeCredit(amount2), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -734,7 +734,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(totalAmount, addLiquidity.paymentAmount) assertEquals(105_000.sat, addLiquidity.requestedAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } } @@ -753,7 +753,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(totalAmount, addLiquidity.paymentAmount) assertEquals(100_001.sat, addLiquidity.requestedAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -775,7 +775,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val addFeeCredit = result.actions.first() assertIs(addFeeCredit) assertIs(addFeeCredit.message) - assertEquals(amount, result.received.amount) + assertEquals(amount, result.amount) checkDbPayment(result.incomingPayment, paymentHandler.db) } @@ -794,7 +794,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(amount, addLiquidity.paymentAmount) assertEquals(104_000.sat, addLiquidity.requestedAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } } @@ -812,7 +812,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(totalAmount, addLiquidity.paymentAmount) assertEquals(110_000.sat, addLiquidity.requestedAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.received) + assertEquals(emptyList(), paymentHandler.db.getLightningIncomingPayment(incomingPayment.paymentHash)?.parts) } @Test @@ -875,15 +875,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertTrue(htlc.amountMsat < amount2) val result = paymentHandler.process(htlc, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, fundingFee = null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2 - purchase.fundingFee.amount, channelId, 1, purchase.fundingFee), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, fundingFee = null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2 - purchase.fundingFee.amount, channelId, 1, purchase.fundingFee), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount - purchase.fundingFee.amount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount - purchase.fundingFee.amount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -927,14 +927,14 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val currentBlockHeight = TestConstants.defaultBlockHeight + 24 val result = paymentHandler.process(htlc, Features.empty, currentBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(7, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount, channelId, 7, fundingFee), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(7, incomingPayment.paymentPreimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount, channelId, 7, fundingFee), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(amount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(amount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -983,8 +983,8 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, TestConstants.fundingRates) assertIs(result) assertEquals(listOf(WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, incomingPayment.paymentPreimage, commit = true))), result.actions) - assertEquals(defaultAmount - payment.fundingFee.amount, result.received.amount) - assertEquals(listOf(LightningIncomingPayment.Received.Part.Htlc(defaultAmount - payment.fundingFee.amount, channelId, 1, payment.fundingFee)), result.received.parts) + assertEquals(defaultAmount - payment.fundingFee.amount, result.amount) + assertEqualsIgnoreTimestamps(listOf(LightningIncomingPayment.Part.Htlc(defaultAmount - payment.fundingFee.amount, channelId, 1, payment.fundingFee)), result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -1135,7 +1135,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add2 = makeUpdateAddHtlc(5, randomBytes32(), paymentHandler, incomingPayment.paymentHash, makeMppPayload(defaultAmount / 2, defaultAmount, paymentSecret)) val result2 = paymentHandler.process(add2, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result2) - assertEquals(defaultAmount, result2.received.amount) + assertEquals(defaultAmount, result2.amount) val expected = setOf( WrappedChannelCommand(add1.channelId, ChannelCommand.Htlc.Settlement.Fulfill(add1.id, incomingPayment.paymentPreimage, commit = true)), WrappedChannelCommand(add2.channelId, ChannelCommand.Htlc.Settlement.Fulfill(add2.id, incomingPayment.paymentPreimage, commit = true)) @@ -1145,7 +1145,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { // The second htlc is reprocessed (e.g. because our peer disconnected before we could send them the preimage). val result2b = paymentHandler.process(add2, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result2b) - assertEquals(defaultAmount, result2b.received.amount) + assertEquals(defaultAmount, result2b.amount) assertEquals(listOf(WrappedChannelCommand(add2.channelId, ChannelCommand.Htlc.Settlement.Fulfill(add2.id, incomingPayment.paymentPreimage, commit = true))), result2b.actions) } @@ -1533,14 +1533,14 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { paymentHandler.db.receiveLightningPayment( paidInvoice.paymentHash, parts = listOf( - LightningIncomingPayment.Received.Part.Htlc( + LightningIncomingPayment.Part.Htlc( amountReceived = 15_000_000.msat, channelId = randomBytes32(), htlcId = 42, - fundingFee = null + fundingFee = null, + receivedAt = 101 // simulate incoming payment being paid before it expired ) ), - receivedAt = 101 // simulate incoming payment being paid before it expired ) // create unexpired payment @@ -1574,9 +1574,9 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val expected = ChannelCommand.Htlc.Settlement.Fulfill(add.id, preimage, commit = true) assertEquals(setOf(WrappedChannelCommand(add.channelId, expected)), result.actions.toSet()) - assertEquals(result.incomingPayment.received, result.received) - assertEquals(defaultAmount, result.received.amount) - assertEquals(listOf(LightningIncomingPayment.Received.Part.Htlc(defaultAmount, add.channelId, 8, null)), result.received.parts) + assertEqualsIgnoreTimestamps(result.incomingPayment.parts, result.parts) + assertEquals(defaultAmount, result.amount) + assertEqualsIgnoreTimestamps(listOf(LightningIncomingPayment.Part.Htlc(defaultAmount, add.channelId, 8, null)), result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } @@ -1599,7 +1599,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(0, channelId, paymentHandler, paymentHash, finalPayload, route.blindingKey) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - assertNull(result.incomingPayment.received) + assertTrue(result.incomingPayment.parts.isEmpty()) assertTrue(result.actions.isEmpty()) } @@ -1611,15 +1611,15 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(1, channelId, paymentHandler, paymentHash, finalPayload, route.blindingKey) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - val (expectedActions, expectedReceivedWith) = setOf( + val (expectedActions, parts) = setOf( // @formatter:off - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, preimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount1, channelId, 0, null), - WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, preimage, commit = true)) to LightningIncomingPayment.Received.Part.Htlc(amount2, channelId, 1, null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(0, preimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount1, channelId, 0, null), + WrappedChannelCommand(channelId, ChannelCommand.Htlc.Settlement.Fulfill(1, preimage, commit = true)) to LightningIncomingPayment.Part.Htlc(amount2, channelId, 1, null), // @formatter:on ).unzip() assertEquals(expectedActions.toSet(), result.actions.toSet()) - assertEquals(totalAmount, result.received.amount) - assertEquals(expectedReceivedWith, result.received.parts) + assertEquals(totalAmount, result.amount) + assertEqualsIgnoreTimestamps(parts, result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -1640,7 +1640,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertEquals(preimage, addLiquidity.preimage) assertEquals(defaultAmount, addLiquidity.paymentAmount) // We don't update the payments DB: we're waiting to receive HTLCs after the open/splice. - assertNull(paymentHandler.db.getLightningIncomingPayment(paymentHash)?.received) + assertNull(paymentHandler.db.getLightningIncomingPayment(paymentHash)) } @Test @@ -1679,10 +1679,10 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { assertIs(result) val fulfill = ChannelCommand.Htlc.Settlement.Fulfill(add.id, preimage, commit = true) assertEquals(setOf(WrappedChannelCommand(add.channelId, fulfill)), result.actions.toSet()) - assertEquals(result.incomingPayment.received, result.received) - assertEquals(defaultAmount - payment.fundingFee.amount, result.received.amount) - val parts = LightningIncomingPayment.Received.Part.Htlc(defaultAmount - payment.fundingFee.amount, add.channelId, 0, payment.fundingFee) - assertEquals(listOf(parts), result.received.parts) + assertEqualsIgnoreTimestamps(result.incomingPayment.parts, result.parts) + assertEquals(defaultAmount - payment.fundingFee.amount, result.amount) + val parts = LightningIncomingPayment.Part.Htlc(defaultAmount - payment.fundingFee.amount, add.channelId, 0, payment.fundingFee) + assertEqualsIgnoreTimestamps(listOf(parts), result.parts) checkDbPayment(result.incomingPayment, paymentHandler.db) } } @@ -1719,7 +1719,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val add = makeUpdateAddHtlc(0, channelId, paymentHandler, paymentHash, finalPayload, route.blindingKey) val result = paymentHandler.process(add, Features.empty, TestConstants.defaultBlockHeight, TestConstants.feeratePerKw, remoteFundingRates = null) assertIs(result) - assertNull(result.incomingPayment.received) + assertTrue(result.incomingPayment.parts.isEmpty()) assertTrue(result.actions.isEmpty()) } @@ -1774,6 +1774,18 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { val defaultAmount = 150_000_000.msat val feeCreditFeatures = Features(Feature.ExperimentalSplice to FeatureSupport.Optional, Feature.OnTheFlyFunding to FeatureSupport.Optional, Feature.FundingFeeCredit to FeatureSupport.Optional) + val IncomingPaymentHandler.ProcessAddResult.Accepted.amount: MilliSatoshi get() = this.parts.map { it.amountReceived }.sum() + + fun LightningIncomingPayment.Part.resetTimestamp() = when (this) { + is LightningIncomingPayment.Part.Htlc -> copy(receivedAt = 0) + is LightningIncomingPayment.Part.FeeCredit -> copy(receivedAt = 0) + } + + fun List.resetTimestamp() = this.map { it.resetTimestamp() } + + fun assertEqualsIgnoreTimestamps(expected: List, actual: List) = + assertEquals(expected.resetTimestamp(), actual.resetTimestamp()) + private fun makeCmdAddHtlc(destination: PublicKey, paymentHash: ByteVector32, finalPayload: PaymentOnion.FinalPayload): ChannelCommand.Htlc.Add { val onion = OutgoingPaymentPacket.buildOnion(listOf(destination), listOf(finalPayload), paymentHash, OnionRoutingPacket.PaymentPacketLength).packet return ChannelCommand.Htlc.Add(finalPayload.amount, paymentHash, finalPayload.expiry, onion, UUID.randomUUID(), commit = true) @@ -1882,7 +1894,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() { } ) assertEquals(incomingPayment.amount, dbPayment.amount) - assertEquals(incomingPayment.received?.parts, dbPayment.received?.parts) + assertEqualsIgnoreTimestamps(incomingPayment.parts, dbPayment.parts) } private suspend fun createFixture(invoiceAmount: MilliSatoshi?): Triple {