Skip to content

Commit

Permalink
Clarify received amount before or after fees
Browse files Browse the repository at this point in the history
We clarify some of our event types that previously had an `amount`
field to detail whether this amount includes fees or not.

This impacts:

- SwapInEvents.Accepted
- StoreIncomingPayment.ViaNewChannel
- StoreIncomingPayment.ViaSpliceIn
- Origin.OnChainWallet
- Origin.OffChainPayment

There was an inconsistency in the `ViaSpliceIn` event, where in some
cases we used the received amount, and in others the amount with fees.
  • Loading branch information
t-bast committed Sep 9, 2024
1 parent 40d3c8a commit d0665d1
Show file tree
Hide file tree
Showing 12 changed files with 39 additions and 35 deletions.
8 changes: 4 additions & 4 deletions src/commonMain/kotlin/fr/acinq/lightning/NodeEvents.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ sealed interface SwapInEvents : NodeEvents {
data class Requested(val walletInputs: List<WalletState.Utxo>) : SwapInEvents {
val totalAmount: Satoshi = walletInputs.map { it.amount }.sum()
}
data class Accepted(val inputs: Set<OutPoint>, val amount: Satoshi, val fees: ChannelManagementFees) : SwapInEvents {
val receivedAmount: Satoshi = amount - fees.total
data class Accepted(val inputs: Set<OutPoint>, val amountBeforeFees: Satoshi, val fees: ChannelManagementFees) : SwapInEvents {
val receivedAmount: Satoshi = amountBeforeFees - fees.total
}
}

Expand All @@ -35,7 +35,7 @@ sealed interface ChannelEvents : NodeEvents {
}

sealed interface LiquidityEvents : NodeEvents {
/** Amount of the liquidity event, before fees are paid. */
/** Amount of liquidity purchased, before fees are paid. */
val amount: MilliSatoshi
val fee: MilliSatoshi
val source: Source
Expand Down Expand Up @@ -74,7 +74,7 @@ data object UpgradeRequired : NodeEvents

sealed interface PaymentEvents : NodeEvents {
data class PaymentReceived(val paymentHash: ByteVector32, val receivedWith: List<IncomingPayment.ReceivedWith>) : PaymentEvents {
val amount: MilliSatoshi = receivedWith.map { it.amount }.sum()
val amount: MilliSatoshi = receivedWith.map { it.amountReceived }.sum()
val fees: MilliSatoshi = receivedWith.map { it.fees }.sum()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import fr.acinq.lightning.channel.states.PersistedChannelState
import fr.acinq.lightning.db.ChannelClosingType
import fr.acinq.lightning.transactions.Transactions
import fr.acinq.lightning.utils.UUID
import fr.acinq.lightning.utils.toMilliSatoshi
import fr.acinq.lightning.wire.*

/** Channel Actions (outputs produced by the state machine). */
Expand Down Expand Up @@ -78,8 +79,8 @@ sealed class ChannelAction {
abstract val origin: Origin?
abstract val txId: TxId
abstract val localInputs: Set<OutPoint>
data class ViaNewChannel(val amount: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
data class ViaSpliceIn(val amount: MilliSatoshi, val serviceFee: MilliSatoshi, val miningFee: Satoshi, override val localInputs: Set<OutPoint>, override val txId: TxId, override val origin: Origin?) : StoreIncomingPayment()
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()
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()
}
/** Payment sent through on-chain operations (channel close or splice-out) */
sealed class StoreOutgoingPayment : Storage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import fr.acinq.lightning.crypto.KeyManager
import fr.acinq.lightning.logging.LoggingContext
import fr.acinq.lightning.transactions.Scripts
import fr.acinq.lightning.transactions.Transactions.TransactionWithInputInfo.*
import fr.acinq.lightning.utils.toMilliSatoshi
import fr.acinq.lightning.wire.ClosingSigned

/**
Expand Down Expand Up @@ -410,12 +411,14 @@ data class ChannelManagementFees(val miningFee: Satoshi, val serviceFee: Satoshi
// @formatter:off
sealed class Origin {
/** Amount of the origin payment, before fees are paid. */
abstract val amount: MilliSatoshi
abstract val amountBeforeFees: MilliSatoshi
/** Fees applied for the channel funding transaction. */
abstract val fees: ChannelManagementFees

data class OnChainWallet(val inputs: Set<OutPoint>, override val amount: MilliSatoshi, override val fees: ChannelManagementFees) : Origin()
data class OffChainPayment(val paymentPreimage: ByteVector32, override val amount: MilliSatoshi, override val fees: ChannelManagementFees) : Origin() {
fun amountReceived(): MilliSatoshi = amountBeforeFees - fees.total.toMilliSatoshi()

data class OnChainWallet(val inputs: Set<OutPoint>, override val amountBeforeFees: MilliSatoshi, override val fees: ChannelManagementFees) : Origin()
data class OffChainPayment(val paymentPreimage: ByteVector32, override val amountBeforeFees: MilliSatoshi, override val fees: ChannelManagementFees) : Origin() {
val paymentHash: ByteVector32 = Crypto.sha256(paymentPreimage).byteVector32()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ data class Normal(
// If we received or sent funds as part of the splice, we will add a corresponding entry to our incoming/outgoing payments db
addAll(origins.map { origin ->
ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn(
amount = origin.amount,
amountReceived = origin.amountReceived(),
serviceFee = origin.fees.serviceFee.toMilliSatoshi(),
miningFee = origin.fees.miningFee,
localInputs = action.fundingTx.sharedTx.tx.localInputs.map { it.outPoint }.toSet(),
Expand All @@ -899,7 +899,7 @@ data class Normal(
// If we added some funds ourselves it's a swap-in
if (action.fundingTx.sharedTx.tx.localInputs.isNotEmpty()) add(
ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn(
amount = action.fundingTx.sharedTx.tx.localInputs.map { i -> i.txOut.amount }.sum().toMilliSatoshi() - action.fundingTx.sharedTx.tx.localFees,
amountReceived = action.fundingTx.sharedTx.tx.localInputs.map { i -> i.txOut.amount }.sum().toMilliSatoshi() - action.fundingTx.sharedTx.tx.localFees,
serviceFee = 0.msat,
miningFee = action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi(),
localInputs = action.fundingTx.sharedTx.tx.localInputs.map { it.outPoint }.toSet(),
Expand Down Expand Up @@ -927,8 +927,8 @@ data class Normal(
}
addAll(origins.map { origin ->
when (origin) {
is Origin.OffChainPayment -> ChannelAction.EmitEvent(LiquidityEvents.Accepted(origin.amount, origin.fees.total.toMilliSatoshi(), LiquidityEvents.Source.OffChainPayment))
is Origin.OnChainWallet -> ChannelAction.EmitEvent(SwapInEvents.Accepted(origin.inputs, origin.amount.truncateToSatoshi(), origin.fees))
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ data class WaitForFundingSigned(
// 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(
ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel(
amount = action.commitment.localCommit.spec.toLocal,
amountReceived = action.commitment.localCommit.spec.toLocal,
serviceFee = channelOrigin?.fees?.serviceFee?.toMilliSatoshi() ?: 0.msat,
miningFee = channelOrigin?.fees?.miningFee ?: action.fundingTx.sharedTx.tx.localFees.truncateToSatoshi(),
localInputs = action.fundingTx.sharedTx.tx.localInputs.map { it.outPoint }.toSet(),
Expand All @@ -140,8 +140,8 @@ data class WaitForFundingSigned(
}
channelOrigin?.let {
when (it) {
is Origin.OffChainPayment -> add(ChannelAction.EmitEvent(LiquidityEvents.Accepted(it.amount, it.fees.total.toMilliSatoshi(), LiquidityEvents.Source.OffChainPayment)))
is Origin.OnChainWallet -> add(ChannelAction.EmitEvent(SwapInEvents.Accepted(it.inputs, it.amount.truncateToSatoshi(), it.fees)))
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)))
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/commonMain/kotlin/fr/acinq/lightning/db/PaymentsDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,21 +156,21 @@ data class IncomingPayment(val preimage: ByteVector32, val origin: Origin, val r

data class Received(val receivedWith: List<ReceivedWith>, val receivedAt: Long = currentTimestampMillis()) {
/** Total amount received after applying the fees. */
val amount: MilliSatoshi = receivedWith.map { it.amount }.sum()
val amount: MilliSatoshi = receivedWith.map { it.amountReceived }.sum()

/** Fees applied to receive this payment. */
val fees: MilliSatoshi = receivedWith.map { it.fees }.sum()
}

sealed class ReceivedWith {
/** Amount received for this part after applying the fees. This is the final amount we can use. */
abstract val amount: MilliSatoshi
abstract val amountReceived: MilliSatoshi

/** Fees applied to receive this part. Is zero for Lightning payments. */
abstract val fees: MilliSatoshi

/** Payment was received via existing lightning channels. */
data class LightningPayment(override val amount: MilliSatoshi, val channelId: ByteVector32, val htlcId: Long, val fundingFee: LiquidityAds.FundingFee?) : ReceivedWith() {
data class LightningPayment(override val amountReceived: MilliSatoshi, val channelId: ByteVector32, val htlcId: Long, val fundingFee: LiquidityAds.FundingFee?) : ReceivedWith() {
// If there is no funding fee, the fees are paid by the sender for lightning payments.
override val fees: MilliSatoshi = fundingFee?.amount ?: 0.msat
}
Expand All @@ -188,13 +188,13 @@ data class IncomingPayment(val preimage: ByteVector32, val origin: Origin, val r
/**
* Payment was received via a new channel opened to us.
*
* @param amount Our side of the balance of this channel when it's created. This is the amount pushed to us once the creation fees are applied.
* @param amountReceived Our side of the balance of this channel when it's created. This is the amount received after the creation fees are applied.
* @param serviceFee Fees paid to Lightning Service Provider to open this channel.
* @param miningFee Feed paid to bitcoin miners for processing the L1 transaction.
* @param channelId The long id of the channel created to receive this payment. May be null if the channel id is not known.
*/
data class NewChannel(
override val amount: MilliSatoshi,
override val amountReceived: MilliSatoshi,
override val serviceFee: MilliSatoshi,
override val miningFee: Satoshi,
override val channelId: ByteVector32,
Expand All @@ -204,7 +204,7 @@ data class IncomingPayment(val preimage: ByteVector32, val origin: Origin, val r
) : OnChainIncomingPayment()

data class SpliceIn(
override val amount: MilliSatoshi,
override val amountReceived: MilliSatoshi,
override val serviceFee: MilliSatoshi,
override val miningFee: Satoshi,
override val channelId: ByteVector32,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb, pri
val receivedWith = when (action) {
is ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel ->
IncomingPayment.ReceivedWith.NewChannel(
amount = action.amount,
amountReceived = action.amountReceived,
serviceFee = action.serviceFee,
miningFee = action.miningFee,
channelId = channelId,
Expand All @@ -119,7 +119,7 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val db: PaymentsDb, pri
)
is ChannelAction.Storage.StoreIncomingPayment.ViaSpliceIn ->
IncomingPayment.ReceivedWith.SpliceIn(
amount = action.amount,
amountReceived = action.amountReceived,
serviceFee = action.serviceFee,
miningFee = action.miningFee,
channelId = channelId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,12 @@ object Deserialization {
}
0x03 -> Origin.OffChainPayment(
paymentPreimage = readByteVector32(),
amount = readNumber().msat,
amountBeforeFees = readNumber().msat,
fees = ChannelManagementFees(miningFee = readNumber().sat, serviceFee = readNumber().sat),
)
0x04 -> Origin.OnChainWallet(
inputs = readCollection { readOutPoint() }.toSet(),
amount = readNumber().msat,
amountBeforeFees = readNumber().msat,
fees = ChannelManagementFees(miningFee = readNumber().sat, serviceFee = readNumber().sat),
)
else -> error("unknown discriminator $discriminator for class ${Origin::class}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,14 +455,14 @@ object Serialization {
is Origin.OffChainPayment -> {
write(0x03)
writeByteVector32(o.paymentPreimage)
writeNumber(o.amount.toLong())
writeNumber(o.amountBeforeFees.toLong())
writeNumber(o.fees.miningFee.toLong())
writeNumber(o.fees.serviceFee.toLong())
}
is Origin.OnChainWallet -> {
write(0x04)
writeCollection(o.inputs) { writeBtcObject(it) }
writeNumber(o.amount.toLong())
writeNumber(o.amountBeforeFees.toLong())
writeNumber(o.fees.miningFee.toLong())
writeNumber(o.fees.serviceFee.toLong())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() {
assertEquals(actions.size, 5)
actions.hasOutgoingMessage<TxSignatures>().also { assertFalse(it.channelData.isEmpty()) }
actions.findWatch<WatchConfirmed>().also { assertEquals(WatchConfirmed(state.channelId, commitInput.outPoint.txid, commitInput.txOut.publicKeyScript, 3, BITCOIN_FUNDING_DEPTHOK), it) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals(TestConstants.bobFundingAmount.toMilliSatoshi() + TestConstants.alicePushAmount - TestConstants.bobPushAmount, it.amount) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals(TestConstants.bobFundingAmount.toMilliSatoshi() + TestConstants.alicePushAmount - TestConstants.bobPushAmount, it.amountReceived) }
actions.has<ChannelAction.Storage.StoreState>()
actions.find<ChannelAction.EmitEvent>().also { assertEquals(ChannelEvents.Created(state.state), it.event) }
}
Expand All @@ -59,7 +59,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() {
actions.hasOutgoingMessage<TxSignatures>().also { assertFalse(it.channelData.isEmpty()) }
actions.hasOutgoingMessage<ChannelReady>().also { assertEquals(ShortChannelId.peerId(bob.staticParams.nodeParams.nodeId), it.alias) }
actions.findWatch<WatchConfirmed>().also { assertEquals(state.commitments.latest.fundingTxId, it.txId) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals(TestConstants.bobFundingAmount.toMilliSatoshi() + TestConstants.alicePushAmount - TestConstants.bobPushAmount, it.amount) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals(TestConstants.bobFundingAmount.toMilliSatoshi() + TestConstants.alicePushAmount - TestConstants.bobPushAmount, it.amountReceived) }
actions.has<ChannelAction.Storage.StoreState>()
actions.find<ChannelAction.EmitEvent>().also { assertEquals(ChannelEvents.Created(state.state), it.event) }
}
Expand Down Expand Up @@ -87,7 +87,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() {
assertEquals(TestConstants.aliceFundingAmount - purchase.fees.total, state.commitments.latest.localCommit.spec.toRemote.truncateToSatoshi())
actions.hasOutgoingMessage<TxSignatures>().also { assertFalse(it.channelData.isEmpty()) }
actions.findWatch<WatchConfirmed>().also { assertEquals(BITCOIN_FUNDING_DEPTHOK, it.event) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals((TestConstants.bobFundingAmount + purchase.fees.total).toMilliSatoshi(), it.amount) }
actions.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also { assertEquals((TestConstants.bobFundingAmount + purchase.fees.total).toMilliSatoshi(), it.amountReceived) }
actions.has<ChannelAction.Storage.StoreState>()
actions.find<ChannelAction.EmitEvent>().also { assertEquals(ChannelEvents.Created(state.state), it.event) }
}
Expand All @@ -103,7 +103,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() {
actionsAlice1.hasOutgoingMessage<TxSignatures>()
actionsAlice1.has<ChannelAction.Storage.StoreState>()
actionsAlice1.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also {
assertEquals(50_000_000.msat, it.amount)
assertEquals(50_000_000.msat, it.amountReceived)
assertEquals(channelOrigin, it.origin)
assertEquals(alice1.commitments.latest.fundingTxId, it.txId)
}
Expand All @@ -123,7 +123,7 @@ class WaitForFundingSignedTestsCommon : LightningTestSuite() {
actionsAlice1.hasOutgoingMessage<TxSignatures>()
actionsAlice1.has<ChannelAction.Storage.StoreState>()
actionsAlice1.find<ChannelAction.Storage.StoreIncomingPayment.ViaNewChannel>().also {
assertEquals(it.amount, 200_000_000.msat)
assertEquals(it.amountReceived, 200_000_000.msat)
assertEquals(it.origin, channelOrigin)
assertTrue(it.localInputs.isNotEmpty())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class PaymentsDbTestsCommon : LightningTestSuite() {
assertEquals(100_000.msat, received.amount)
assertEquals(0.msat, received.fees)
assertEquals(2, received.received!!.receivedWith.size)
assertEquals(57_000.msat, received.received!!.receivedWith.elementAt(0).amount)
assertEquals(57_000.msat, received.received!!.receivedWith.elementAt(0).amountReceived)
assertEquals(0.msat, received.received!!.receivedWith.elementAt(0).fees)
assertEquals(channelId1, (received.received!!.receivedWith.elementAt(0) as IncomingPayment.ReceivedWith.LightningPayment).channelId)
assertEquals(54, (received.received!!.receivedWith.elementAt(1) as IncomingPayment.ReceivedWith.LightningPayment).htlcId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ class IncomingPaymentHandlerTestsCommon : LightningTestSuite() {
paidInvoice.paymentHash,
receivedWith = listOf(
IncomingPayment.ReceivedWith.NewChannel(
amount = 15_000_000.msat,
amountReceived = 15_000_000.msat,
serviceFee = 1_000_000.msat,
miningFee = 0.sat,
channelId = randomBytes32(),
Expand Down

0 comments on commit d0665d1

Please sign in to comment.