Skip to content

Commit 2ac5cd2

Browse files
dpad85pm47
andauthored
Do not update amount in db for incoming amount of pay-to-open origin (#242)
Setting the payment's amount in the database is the responsibility of the main payment handler. Co-authored-by: Pierre-Marie Padiou <[email protected]>
1 parent 7d29237 commit 2ac5cd2

File tree

4 files changed

+106
-37
lines changed

4 files changed

+106
-37
lines changed

src/commonMain/kotlin/fr/acinq/eclair/db/InMemoryPaymentsDb.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import fr.acinq.eclair.payment.FinalFailure
88
import fr.acinq.eclair.payment.OutgoingPaymentFailure
99
import fr.acinq.eclair.utils.Either
1010
import fr.acinq.eclair.utils.UUID
11+
import fr.acinq.eclair.utils.msat
1112
import fr.acinq.eclair.utils.toByteVector32
1213
import fr.acinq.eclair.wire.FailureMessage
1314

@@ -27,7 +28,10 @@ class InMemoryPaymentsDb : PaymentsDb {
2728
override suspend fun receivePayment(paymentHash: ByteVector32, amount: MilliSatoshi, receivedWith: IncomingPayment.ReceivedWith, receivedAt: Long) {
2829
when (val payment = incoming[paymentHash]) {
2930
null -> Unit // no-op
30-
else -> incoming[paymentHash] = payment.copy(received = IncomingPayment.Received(amount, receivedWith, receivedAt))
31+
else -> incoming[paymentHash] = run {
32+
val alreadyReceived = payment.received?.amount ?: 0.msat
33+
payment.copy(received = IncomingPayment.Received(amount + alreadyReceived, receivedWith, receivedAt))
34+
}
3135
}
3236
}
3337

src/commonMain/kotlin/fr/acinq/eclair/io/Peer.kt

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -370,38 +370,8 @@ class Peer(
370370
action.htlcs.forEach { db.channels.addHtlcInfo(actualChannelId, it.commitmentNumber, it.paymentHash, it.cltvExpiry) }
371371
}
372372
action is ChannelAction.Storage.StoreIncomingAmount -> {
373-
logger.info { "storing incoming amount=${action.amount} with origin=${action.origin}" }
374-
when (action.origin) {
375-
null -> {
376-
logger.warning { "n:$remoteNodeId c:$actualChannelId incoming amount with empty origin, store minimal information" }
377-
val fakePreimage = actualChannelId.sha256()
378-
db.payments.addAndReceivePayment(
379-
preimage = fakePreimage,
380-
origin = IncomingPayment.Origin.SwapIn(address = ""),
381-
amount = action.amount,
382-
receivedWith = IncomingPayment.ReceivedWith.NewChannel(fees = 0.msat, channelId = actualChannelId)
383-
)
384-
}
385-
is ChannelOrigin.PayToOpenOrigin -> {
386-
if (db.payments.getIncomingPayment(action.origin.paymentHash) != null) {
387-
db.payments.receivePayment(paymentHash = action.origin.paymentHash, amount = action.amount, receivedWith = IncomingPayment.ReceivedWith.NewChannel(
388-
fees = action.origin.fee.toMilliSatoshi(),
389-
channelId = actualChannelId
390-
))
391-
} else {
392-
logger.warning { "n:$remoteNodeId c:$actualChannelId ignored pay-to-open storage, no payments in db for hash=${action.origin.paymentHash}" }
393-
}
394-
}
395-
is ChannelOrigin.SwapInOrigin -> {
396-
val fakePreimage = actualChannelId.sha256()
397-
db.payments.addAndReceivePayment(
398-
preimage = fakePreimage,
399-
origin = IncomingPayment.Origin.SwapIn(address = action.origin.bitcoinAddress),
400-
amount = action.amount,
401-
receivedWith = IncomingPayment.ReceivedWith.NewChannel(fees = action.origin.fee.toMilliSatoshi(), channelId = actualChannelId)
402-
)
403-
}
404-
}
373+
logger.info { "n:$remoteNodeId c:$channelId storing incoming amount=${action.amount} with origin=${action.origin}" }
374+
incomingPaymentHandler.process(actualChannelId, action)
405375
}
406376
action is ChannelAction.Storage.GetHtlcInfos -> {
407377
val htlcInfos = db.channels.listHtlcInfos(actualChannelId, action.commitmentNumber).map { ChannelAction.Storage.HtlcInfo(actualChannelId, action.commitmentNumber, it.first, it.second) }

src/commonMain/kotlin/fr/acinq/eclair/payment/IncomingPaymentHandler.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,56 @@ class IncomingPaymentHandler(val nodeParams: NodeParams, val walletParams: Walle
9393
return pr
9494
}
9595

96+
/**
97+
* Save the "received-with" details of an incoming amount.
98+
*
99+
* - for a pay-to-open origin, we only save the id of the channel that was created for this payment.
100+
* - for a swap-in origin, a new incoming payment must be created. We use the channel id to generate the payment's preimage.
101+
* - for unknown origin, the amount is handled as a swap-in coming from an unknown address.
102+
*/
103+
suspend fun process(channelId: ByteVector32, action: ChannelAction.Storage.StoreIncomingAmount) {
104+
when (action.origin) {
105+
null -> {
106+
// TODO: hacky, needs clean-up
107+
logger.warning { "incoming amount with empty origin, we store only minimal information" }
108+
val fakePreimage = channelId.sha256()
109+
db.addAndReceivePayment(
110+
preimage = fakePreimage,
111+
origin = IncomingPayment.Origin.SwapIn(address = ""),
112+
amount = action.amount,
113+
receivedWith = IncomingPayment.ReceivedWith.NewChannel(fees = 0.msat, channelId = channelId)
114+
)
115+
}
116+
is ChannelOrigin.PayToOpenOrigin -> {
117+
if (db.getIncomingPayment(action.origin.paymentHash) != null) {
118+
db.receivePayment(
119+
paymentHash = action.origin.paymentHash,
120+
amount = 0.msat, // do not update the amount, it's already set by the main payment handler.
121+
receivedWith = IncomingPayment.ReceivedWith.NewChannel(
122+
fees = action.origin.fee.toMilliSatoshi(),
123+
channelId = channelId
124+
)
125+
)
126+
} else {
127+
logger.warning { "ignored pay-to-open origin action, there are no payments in db for payment_hash=${action.origin.paymentHash}" }
128+
}
129+
}
130+
is ChannelOrigin.SwapInOrigin -> {
131+
// swap-ins are push payments made with an on-chain tx, there is no related preimage so we make up one so it fits in our model
132+
val fakePreimage = channelId.sha256()
133+
db.addAndReceivePayment(
134+
preimage = fakePreimage,
135+
origin = IncomingPayment.Origin.SwapIn(address = action.origin.bitcoinAddress),
136+
amount = action.amount,
137+
receivedWith = IncomingPayment.ReceivedWith.NewChannel(
138+
fees = action.origin.fee.toMilliSatoshi(),
139+
channelId = channelId
140+
)
141+
)
142+
}
143+
}
144+
}
145+
96146
/**
97147
* Process an incoming htlc.
98148
* Before calling this, the htlc must be committed and ack-ed by both sides.

src/commonTest/kotlin/fr/acinq/eclair/payment/IncomingPaymentHandlerTestsCommon.kt

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ import fr.acinq.eclair.tests.utils.EclairTestSuite
1717
import fr.acinq.eclair.tests.utils.runSuspendTest
1818
import fr.acinq.eclair.utils.*
1919
import fr.acinq.eclair.wire.*
20-
import kotlin.test.Test
21-
import kotlin.test.assertEquals
22-
import kotlin.test.assertNull
23-
import kotlin.test.assertTrue
20+
import kotlin.test.*
2421

2522
class IncomingPaymentHandlerTestsCommon : EclairTestSuite() {
2623

@@ -314,6 +311,54 @@ class IncomingPaymentHandlerTestsCommon : EclairTestSuite() {
314311
assertEquals(setOf(expected), result.actions.toSet())
315312
}
316313

314+
@Test
315+
fun `process incoming amount with unknown origin`() = runSuspendTest {
316+
val channelId = randomBytes32()
317+
val amountOrigin = ChannelAction.Storage.StoreIncomingAmount(amount = 15_000_000.msat, origin = null)
318+
val handler = IncomingPaymentHandler(TestConstants.Bob.nodeParams, TestConstants.Bob.walletParams, InMemoryPaymentsDb())
319+
handler.process(channelId, amountOrigin)
320+
val dbPayment = handler.db.getIncomingPayment(channelId.sha256().sha256())
321+
assertNotNull(dbPayment)
322+
assertTrue { dbPayment.origin is IncomingPayment.Origin.SwapIn }
323+
assertEquals(IncomingPayment.ReceivedWith.NewChannel(fees = 0.msat, channelId = channelId), dbPayment.received?.receivedWith)
324+
assertEquals(15_000_000.msat, dbPayment.received?.amount)
325+
}
326+
327+
@Test
328+
fun `process incoming amount with pay-to-open origin`() = runSuspendTest {
329+
val preimage = randomBytes32()
330+
val channelId = randomBytes32()
331+
val amountOrigin = ChannelAction.Storage.StoreIncomingAmount(
332+
amount = 15_000_000.msat,
333+
origin = ChannelOrigin.PayToOpenOrigin(paymentHash = preimage.sha256(), fee = 1000.sat))
334+
val handler = IncomingPaymentHandler(TestConstants.Bob.nodeParams, TestConstants.Bob.walletParams, InMemoryPaymentsDb())
335+
// simulate payment processed as a pay-to-open
336+
handler.db.addIncomingPayment(preimage, IncomingPayment.Origin.KeySend)
337+
handler.db.receivePayment(preimage.sha256(), 20_000_000.msat, receivedWith = IncomingPayment.ReceivedWith.NewChannel(0.msat, null))
338+
// process the amount origin which must reconcile with the existing line in the database
339+
handler.process(channelId, amountOrigin)
340+
val dbPayment = handler.db.getIncomingPayment(preimage.sha256())
341+
assertNotNull(dbPayment)
342+
assertTrue { dbPayment.origin is IncomingPayment.Origin.KeySend }
343+
assertEquals(IncomingPayment.ReceivedWith.NewChannel(fees = 1_000_000.msat, channelId = channelId), dbPayment.received?.receivedWith)
344+
assertEquals(20_000_000.msat, dbPayment.received?.amount)
345+
}
346+
347+
@Test
348+
fun `process incoming amount with swap-in origin`() = runSuspendTest {
349+
val channelId = randomBytes32()
350+
val amountOrigin = ChannelAction.Storage.StoreIncomingAmount(
351+
amount = 33_000_000.msat,
352+
origin = ChannelOrigin.SwapInOrigin("tb1q97tpc0y4rvdnu9wm7nu354lmmzdm8du228u3g4", 1_200.sat))
353+
val handler = IncomingPaymentHandler(TestConstants.Bob.nodeParams, TestConstants.Bob.walletParams, InMemoryPaymentsDb())
354+
handler.process(channelId, amountOrigin)
355+
val dbPayment = handler.db.getIncomingPayment(channelId.sha256().sha256())
356+
assertNotNull(dbPayment)
357+
assertTrue { dbPayment.origin is IncomingPayment.Origin.SwapIn }
358+
assertEquals(IncomingPayment.ReceivedWith.NewChannel(fees = 1_200_000.msat, channelId = channelId), dbPayment.received?.receivedWith)
359+
assertEquals(33_000_000.msat, dbPayment.received?.amount)
360+
}
361+
317362
@Test
318363
fun `receive multipart payment with multiple HTLCs via same channel`() = runSuspendTest {
319364
val channelId = randomBytes32()

0 commit comments

Comments
 (0)