From a9593875a32a36552b15c7063750f70ecc1b34fa Mon Sep 17 00:00:00 2001 From: pm47 Date: Mon, 9 Sep 2024 18:04:46 +0200 Subject: [PATCH 1/4] integ: enable feature FundingFeeCredit --- src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index aaa75d4da..c1ab71dea 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -205,6 +205,7 @@ data class NodeParams( Feature.ChannelBackupClient to FeatureSupport.Optional, Feature.ExperimentalSplice to FeatureSupport.Optional, Feature.OnTheFlyFunding to FeatureSupport.Optional, + Feature.FundingFeeCredit to FeatureSupport.Optional, ), dustLimit = 546.sat, maxRemoteDustLimit = 600.sat, From 73817213f207be63447735cd56ad43f51219d2a1 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 20 Sep 2024 13:28:49 +0200 Subject: [PATCH 2/4] emit a liquidity event for each liquidity purchase Independently of whether the purchase was triggered by an on-chain or off-chain payment. Also renamed `LiquidityEvents.Accepted` to `LiquidityEvents.Purchased`. --- src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt | 10 +++------- .../kotlin/fr/acinq/lightning/channel/states/Normal.kt | 10 ++++------ .../lightning/channel/states/WaitForFundingSigned.kt | 8 +++----- .../channel/states/WaitForFundingSignedTestsCommon.kt | 5 ++--- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt index f72ab80c8..ff86dd0f5 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt @@ -14,6 +14,7 @@ import fr.acinq.lightning.channel.states.WaitForFundingCreated import fr.acinq.lightning.db.IncomingPayment import fr.acinq.lightning.utils.sum import fr.acinq.lightning.wire.Init +import fr.acinq.lightning.wire.LiquidityAds sealed interface NodeEvents @@ -35,13 +36,8 @@ sealed interface ChannelEvents : NodeEvents { } sealed interface LiquidityEvents : NodeEvents { - /** Amount of liquidity purchased, before fees are paid. */ - val amount: MilliSatoshi - val fee: MilliSatoshi - val source: Source - enum class Source { OnChainWallet, OffChainPayment } - data class Rejected(override val amount: MilliSatoshi, override val fee: MilliSatoshi, override val source: Source, val reason: Reason) : LiquidityEvents { + data class Rejected(val amount: MilliSatoshi, val fee: MilliSatoshi, val source: Source, val reason: Reason) : LiquidityEvents { sealed class Reason { data object PolicySetToDisabled : Reason() sealed class TooExpensive : Reason() { @@ -54,7 +50,7 @@ sealed interface LiquidityEvents : NodeEvents { data class TooManyParts(val parts: Int) : Reason() } } - data class Accepted(override val amount: MilliSatoshi, override val fee: MilliSatoshi, override val source: Source) : LiquidityEvents + data class Purchased(val purchase: LiquidityAds.Purchase) : PaymentEvents } /** This is useful on iOS to ask the OS for time to finish some sensitive tasks. */ diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt index 0a098a244..e3c478ce6 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt @@ -913,13 +913,11 @@ data class Normal( // and what we refunded the remote peer for some of their inputs and outputs via the lease. val miningFees = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi() + purchase.fees.miningFee add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, miningFees = miningFees, purchase = purchase)) + add(ChannelAction.EmitEvent(LiquidityEvents.Purchased(purchase))) + } + origins.filterIsInstance().forEach { origin -> + add(ChannelAction.EmitEvent(SwapInEvents.Accepted(origin.inputs, origin.amountBeforeFees.truncateToSatoshi(), origin.fees))) } - addAll(origins.map { origin -> - when (origin) { - is Origin.OffChainPayment -> ChannelAction.EmitEvent(LiquidityEvents.Accepted(liquidityPurchase?.amount?.toMilliSatoshi() ?: 0.msat, origin.fees.total.toMilliSatoshi(), LiquidityEvents.Source.OffChainPayment)) - is Origin.OnChainWallet -> ChannelAction.EmitEvent(SwapInEvents.Accepted(origin.inputs, origin.amountBeforeFees.truncateToSatoshi(), origin.fees)) - } - }) if (staticParams.useZeroConf) { logger.info { "channel is using 0-conf, sending splice_locked right away" } val spliceLocked = SpliceLocked(channelId, action.fundingTx.txId) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt index 0e32e026c..ef90a7b6c 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSigned.kt @@ -136,13 +136,11 @@ data class WaitForFundingSigned( // and what we refunded the remote peer for some of their inputs and outputs via the lease. val miningFees = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi() + purchase.fees.miningFee add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, miningFees = miningFees, purchase = purchase)) + add(ChannelAction.EmitEvent(LiquidityEvents.Purchased(purchase))) } } - channelOrigin?.let { - when (it) { - is Origin.OffChainPayment -> add(ChannelAction.EmitEvent(LiquidityEvents.Accepted(liquidityPurchase?.amount?.toMilliSatoshi() ?: 0.msat, it.fees.total.toMilliSatoshi(), LiquidityEvents.Source.OffChainPayment))) - is Origin.OnChainWallet -> add(ChannelAction.EmitEvent(SwapInEvents.Accepted(it.inputs, it.amountBeforeFees.truncateToSatoshi(), it.fees))) - } + listOfNotNull(channelOrigin).filterIsInstance().forEach { origin -> + add(ChannelAction.EmitEvent(SwapInEvents.Accepted(origin.inputs, origin.amountBeforeFees.truncateToSatoshi(), origin.fees))) } } return if (staticParams.useZeroConf) { diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt index 8e1faa7a5..3ce3de7f6 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/states/WaitForFundingSignedTestsCommon.kt @@ -99,7 +99,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { val (alice, _, _, commitSigBob) = init(aliceFundingAmount = 0.sat, alicePushAmount = 0.msat, bobPushAmount = 50_000_000.msat, channelOrigin = channelOrigin) val (alice1, actionsAlice1) = alice.process(ChannelCommand.MessageReceived(commitSigBob)) assertIs(alice1.state) - assertEquals(actionsAlice1.size, 6) + assertEquals(5, actionsAlice1.size) actionsAlice1.hasOutgoingMessage() actionsAlice1.has() actionsAlice1.find().also { @@ -110,7 +110,6 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { actionsAlice1.hasWatch() val events = actionsAlice1.filterIsInstance().map { it.event } assertTrue(events.any { it is ChannelEvents.Created }) - assertTrue(events.any { it is LiquidityEvents.Accepted }) } @Test @@ -194,7 +193,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() { assertTrue(actionsAlice1.isEmpty()) val (alice2, actionsAlice2) = alice1.process(ChannelCommand.MessageReceived(txSigsBob)) assertIs(alice2.state) - assertEquals(7, actionsAlice2.size) + assertEquals(8, actionsAlice2.size) assertTrue(actionsAlice2.hasOutgoingMessage().channelData.isEmpty()) actionsAlice2.has() val watchConfirmedAlice = actionsAlice2.findWatch() From 8455d5c4d70e107a1c0d237dc16c4da28e52688a Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 20 Sep 2024 14:32:51 +0200 Subject: [PATCH 3/4] expose serviceFee in InboundLiquidityOutgoingPayment --- src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt b/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt index 3698dfe48..554de25f4 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt @@ -432,7 +432,8 @@ data class InboundLiquidityOutgoingPayment( override val confirmedAt: Long?, override val lockedAt: Long?, ) : OnChainOutgoingPayment() { - override val fees: MilliSatoshi = (miningFees + purchase.fees.serviceFee).toMilliSatoshi() + val serviceFees: Satoshi = purchase.fees.serviceFee + override val fees: MilliSatoshi = (miningFees + serviceFees).toMilliSatoshi() override val amount: MilliSatoshi = fees override val completedAt: Long? = lockedAt val fundingFee: LiquidityAds.FundingFee = LiquidityAds.FundingFee(purchase.fees.total.toMilliSatoshi(), txId) From 4f615be7ea8a5e85383c8c51e84e9ea8c78c0119 Mon Sep 17 00:00:00 2001 From: pm47 Date: Fri, 20 Sep 2024 15:25:53 +0200 Subject: [PATCH 4/4] add events PaymentEvents.PaymentSent --- src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt | 4 +++- src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt index ff86dd0f5..6c5b7e045 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt @@ -12,6 +12,7 @@ import fr.acinq.lightning.channel.states.ChannelStateWithCommitments import fr.acinq.lightning.channel.states.Normal import fr.acinq.lightning.channel.states.WaitForFundingCreated import fr.acinq.lightning.db.IncomingPayment +import fr.acinq.lightning.db.OutgoingPayment import fr.acinq.lightning.utils.sum import fr.acinq.lightning.wire.Init import fr.acinq.lightning.wire.LiquidityAds @@ -50,7 +51,7 @@ sealed interface LiquidityEvents : NodeEvents { data class TooManyParts(val parts: Int) : Reason() } } - data class Purchased(val purchase: LiquidityAds.Purchase) : PaymentEvents + data class Purchased(val purchase: LiquidityAds.Purchase) : LiquidityEvents } /** This is useful on iOS to ask the OS for time to finish some sensitive tasks. */ @@ -73,4 +74,5 @@ sealed interface PaymentEvents : NodeEvents { val amount: MilliSatoshi = receivedWith.map { it.amountReceived }.sum() val fees: MilliSatoshi = receivedWith.map { it.fees }.sum() } + data class PaymentSent(val payment: OutgoingPayment) : PaymentEvents } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index 5954447a4..1ea977b7b 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -910,6 +910,7 @@ class Peer( ) } } + nodeParams._nodeEvents.emit(PaymentEvents.PaymentSent(payment)) db.payments.addOutgoingPayment(payment) } is ChannelAction.Storage.SetLocked -> {