Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include local mining fees for pure liquidity purchases #710

Merged
merged 2 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ sealed class ChannelAction {
abstract val txId: TxId
data class ViaSpliceOut(val amount: Satoshi, override val miningFees: Satoshi, val address: String, override val txId: TxId) : StoreOutgoingPayment()
data class ViaSpliceCpfp(override val miningFees: Satoshi, override val txId: TxId) : StoreOutgoingPayment()
data class ViaInboundLiquidityRequest(override val txId: TxId, val purchase: LiquidityAds.Purchase) : StoreOutgoingPayment() { override val miningFees: Satoshi = purchase.fees.miningFee }
data class ViaInboundLiquidityRequest(override val txId: TxId, val localMiningFees: Satoshi, val purchase: LiquidityAds.Purchase) : StoreOutgoingPayment() { override val miningFees: Satoshi = localMiningFees + purchase.fees.miningFee }
data class ViaClose(val amount: Satoshi, override val miningFees: Satoshi, val address: String, override val txId: TxId, val isSentToDefaultAddress: Boolean, val closingType: ChannelClosingType) : StoreOutgoingPayment()
}
data class SetLocked(val txId: TxId) : Storage()
Expand Down
20 changes: 14 additions & 6 deletions src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -874,14 +874,22 @@ data class Normal(
action.fundingTx.signedTx?.let { add(ChannelAction.Blockchain.PublishTx(it, ChannelAction.Blockchain.PublishTx.Type.FundingTx)) }
add(ChannelAction.Blockchain.SendWatch(watchConfirmed))
add(ChannelAction.Message.Send(action.localSigs))
// If we purchased liquidity as part of the channel creation, we will add it to our payments db
// If we purchased liquidity as part of the splice, we will add it to our payments db.
liquidityPurchase?.let { purchase ->
// We only count fees corresponding to the liquidity purchase (i.e. their inputs).
add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, purchase = purchase))
// If we are purchasing liquidity without any other operation (splice-in, splice-out or splice-cpfp),
// we must include the mining fees we're paying for the shared input and shared output.
// Otherwise, we only count the mining fees that we must refund to our peer as part of the liquidity
// purchase: the mining fees we pay for our inputs/outputs and the shared input/output will be recorded
// in the dedicated splice entry below.
val isPurchaseOnly = action.fundingTx.sharedTx.tx.let {
action.fundingTx.fundingParams.isInitiator && it.localInputs.isEmpty() && it.localOutputs.isEmpty() && it.remoteInputs.isNotEmpty()
}
val localMiningFees = if (isPurchaseOnly) action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi() else 0.sat
add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, localMiningFees = localMiningFees, purchase = purchase))
add(ChannelAction.EmitEvent(LiquidityEvents.Purchased(purchase)))
}
// NB: the following assume that there can't be a splice-in and a splice-out simultaneously, because we attribute
// all local mining fees to both.
// NB: the following assumes that there can't be a splice-in and a splice-out simultaneously,
// or more than one splice-out, because we attribute all local mining fees to each payment entry.
if (action.fundingTx.sharedTx.tx.localInputs.isNotEmpty()) add(
ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn(
amountReceived = action.fundingTx.sharedTx.tx.localInputs.map { i -> i.txOut.amount }.sum().toMilliSatoshi() - action.fundingTx.sharedTx.tx.localFees,
Expand All @@ -900,7 +908,7 @@ data class Normal(
txId = action.fundingTx.txId
)
})
// If we initiated the splice but there are no new inputs on either side and no new output on our side, it's a cpfp
// If we initiated the splice but there are no new inputs on either side and no new output on our side, it's a cpfp.
if (action.fundingTx.fundingParams.isInitiator && action.fundingTx.sharedTx.tx.localInputs.isEmpty() && action.fundingTx.sharedTx.tx.remoteInputs.isEmpty() && action.fundingTx.fundingParams.localOutputs.isEmpty()) {
add(ChannelAction.Storage.StoreOutgoingPayment.ViaSpliceCpfp(miningFees = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi(), txId = action.fundingTx.txId))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import fr.acinq.lightning.blockchain.WatchConfirmed
import fr.acinq.lightning.channel.*
import fr.acinq.lightning.crypto.ShaChain
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.toMilliSatoshi
import fr.acinq.lightning.utils.sat
import fr.acinq.lightning.wire.*
import kotlin.math.absoluteValue

Expand Down Expand Up @@ -119,21 +119,23 @@ data class WaitForFundingSigned(
action.fundingTx.signedTx?.let { add(ChannelAction.Blockchain.PublishTx(it, ChannelAction.Blockchain.PublishTx.Type.FundingTx)) }
add(ChannelAction.Blockchain.SendWatch(watchConfirmed))
add(ChannelAction.Message.Send(action.localSigs))
// If we purchased liquidity as part of the channel creation, we will add it to our payments db
// If we purchased liquidity as part of the channel creation, we will add it to our payments db.
liquidityPurchase?.let { purchase ->
if (channelParams.localParams.isChannelOpener) {
// We only count fees corresponding to the liquidity purchase (i.e. their inputs).
add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, purchase = purchase))
// We only count the mining fees that we must refund to our peer as part of the liquidity purchase.
// If we're also contributing to the funding transaction, the mining fees we pay for our inputs and
// outputs will be recorded in the ViaNewChannel incoming payment entry below.
add(ChannelAction.Storage.StoreOutgoingPayment.ViaInboundLiquidityRequest(txId = action.fundingTx.txId, localMiningFees = 0.sat, purchase = purchase))
add(ChannelAction.EmitEvent(LiquidityEvents.Purchased(purchase)))
}
}
// If we receive funds as part of the channel creation, we will add it to our payments db
// If we receive funds as part of the channel creation, we will add it to our payments db.
if (action.commitment.localCommit.spec.toLocal > 0.msat) add(
// We only count the mining fees corresponding to the inputs and outputs we paid for in the interactive-tx transaction
// (i.e. our wallet inputs and the shared output).
ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel(
amountReceived = action.commitment.localCommit.spec.toLocal,
serviceFee = 0.msat,
// We only count the mining fees we're paying for our inputs and outputs.
// The mining fees for the remote inputs and outputs are paid by the remote node.
miningFee = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi(),
localInputs = action.fundingTx.sharedTx.tx.localInputs.map { it.outPoint }.toSet(),
txId = action.fundingTx.txId,
Expand Down
7 changes: 4 additions & 3 deletions src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ data class IncomingPayment(val preimage: ByteVector32, val origin: Origin, val r
}

/**
* Payment was added to our fee credit for future on-chain operations (see [Feature.FundingFeeCredit]).
* 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 AddedToFeeCredit(override val amountReceived: MilliSatoshi) : ReceivedWith() {
Expand Down Expand Up @@ -427,14 +427,15 @@ data class InboundLiquidityOutgoingPayment(
override val id: UUID,
override val channelId: ByteVector32,
override val txId: TxId,
val localMiningFees: Satoshi,
val purchase: LiquidityAds.Purchase,
override val createdAt: Long,
override val confirmedAt: Long?,
override val lockedAt: Long?,
) : OnChainOutgoingPayment() {
override val miningFees: Satoshi = purchase.fees.miningFee
override val miningFees: Satoshi = localMiningFees + purchase.fees.miningFee
val serviceFees: Satoshi = purchase.fees.serviceFee
override val fees: MilliSatoshi = purchase.fees.total.toMilliSatoshi()
override val fees: MilliSatoshi = (localMiningFees + purchase.fees.total).toMilliSatoshi()
override val amount: MilliSatoshi = fees
override val completedAt: Long? = lockedAt
val fundingFee: LiquidityAds.FundingFee = LiquidityAds.FundingFee(purchase.fees.total.toMilliSatoshi(), txId)
Expand Down
1 change: 1 addition & 0 deletions src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ class Peer(
id = UUID.randomUUID(),
channelId = channelId,
txId = action.txId,
localMiningFees = action.localMiningFees,
purchase = action.purchase,
createdAt = currentTimestampMillis(),
confirmedAt = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
splice.fees(TestConstants.feeratePerKw, isChannelCreation = false),
LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(incomingPayment.paymentHash)),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 0.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)
payment
}
Expand Down Expand Up @@ -911,7 +911,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
splice.fees(TestConstants.feeratePerKw, isChannelCreation = false),
LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(listOf(incomingPayment.paymentHash)),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 500.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)
payment
}
Expand Down Expand Up @@ -963,7 +963,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
LiquidityAds.Fees(2000.sat, 3000.sat),
LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(incomingPayment.paymentHash)),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 100.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)

run {
Expand Down Expand Up @@ -999,7 +999,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
LiquidityAds.Fees(2000.sat, 3000.sat),
LiquidityAds.PaymentDetails.FromChannelBalanceForFutureHtlc(listOf(incomingPayment.paymentHash)),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 0.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)

val add = makeUpdateAddHtlc(0, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(defaultAmount, defaultAmount, paymentSecret), fundingFee = payment.fundingFee)
Expand All @@ -1022,7 +1022,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
250_000.msat,
LiquidityAds.PaymentDetails.FromFutureHtlc(listOf(randomBytes32())),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 0.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)

val add = makeUpdateAddHtlc(0, channelId, paymentHandler, incomingPayment.paymentHash, makeMppPayload(defaultAmount, defaultAmount, paymentSecret), fundingFee = payment.fundingFee)
Expand Down Expand Up @@ -1658,7 +1658,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
500.msat,
LiquidityAds.PaymentDetails.FromFutureHtlcWithPreimage(listOf(preimage)),
)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), purchase, 0, null, null)
val payment = InboundLiquidityOutgoingPayment(UUID.randomUUID(), channelId, TxId(randomBytes32()), 500.sat, purchase, 0, null, null)
paymentHandler.db.addOutgoingPayment(payment)

val cltvExpiry = TestConstants.Bob.nodeParams.minFinalCltvExpiryDelta.toCltvExpiry(TestConstants.defaultBlockHeight.toLong())
Expand Down