Skip to content

Commit efd1c63

Browse files
committed
Fix computation of recipient path fees
1 parent 874a19d commit efd1c63

File tree

2 files changed

+16
-15
lines changed

2 files changed

+16
-15
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/payment/offer/OfferManager.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ object OfferManager {
6565

6666
case class RequestInvoice(messagePayload: MessageOnion.InvoiceRequestPayload, blindedKey: PrivateKey, postman: ActorRef[Postman.SendMessage]) extends Command
6767

68-
case class ReceivePayment(replyTo: ActorRef[MultiPartHandler.GetIncomingPaymentActor.Command], paymentHash: ByteVector32, payload: FinalPayload.Blinded) extends Command
68+
case class ReceivePayment(replyTo: ActorRef[MultiPartHandler.GetIncomingPaymentActor.Command], paymentHash: ByteVector32, payload: FinalPayload.Blinded, amountReceived: MilliSatoshi) extends Command
6969

7070
/**
7171
* Offer handlers must be implemented in separate plugins and respond to these two `HandlerCommand`.
@@ -117,14 +117,14 @@ object OfferManager {
117117
case _ => context.log.debug("offer {} is not registered or invoice request is invalid", messagePayload.invoiceRequest.offer.offerId)
118118
}
119119
Behaviors.same
120-
case ReceivePayment(replyTo, paymentHash, payload) =>
120+
case ReceivePayment(replyTo, paymentHash, payload, amountReceived) =>
121121
MinimalInvoiceData.decode(payload.pathId) match {
122122
case Some(signed) =>
123123
registeredOffers.get(signed.offerId) match {
124124
case Some(RegisteredOffer(offer, _, _, handler)) =>
125125
MinimalInvoiceData.verify(nodeParams.nodeId, signed) match {
126126
case Some(metadata) if Crypto.sha256(metadata.preimage) == paymentHash =>
127-
val child = context.spawnAnonymous(PaymentActor(nodeParams, replyTo, offer, metadata, payload, paymentTimeout))
127+
val child = context.spawnAnonymous(PaymentActor(nodeParams, replyTo, offer, metadata, amountReceived, paymentTimeout))
128128
handler ! HandlePayment(child, signed.offerId, metadata.pluginData_opt)
129129
case Some(_) => replyTo ! MultiPartHandler.GetIncomingPaymentActor.RejectPayment(s"preimage does not match payment hash for offer ${signed.offerId.toHex}")
130130
case None => replyTo ! MultiPartHandler.GetIncomingPaymentActor.RejectPayment(s"invalid signature for metadata for offer ${signed.offerId.toHex}")
@@ -267,7 +267,7 @@ object OfferManager {
267267
replyTo: ActorRef[MultiPartHandler.GetIncomingPaymentActor.Command],
268268
offer: Offer,
269269
metadata: MinimalInvoiceData,
270-
payload: FinalPayload.Blinded,
270+
amount: MilliSatoshi,
271271
timeout: FiniteDuration): Behavior[Command] = {
272272
Behaviors.setup { context =>
273273
context.scheduleOnce(timeout, context.self, RejectPayment("plugin timeout"))
@@ -276,8 +276,8 @@ object OfferManager {
276276
val minimalInvoice = MinimalBolt12Invoice(offer, nodeParams.chainHash, metadata.amount, metadata.quantity, Crypto.sha256(metadata.preimage), metadata.payerKey, metadata.createdAt, additionalTlvs, customTlvs)
277277
val incomingPayment = IncomingBlindedPayment(minimalInvoice, metadata.preimage, PaymentType.Blinded, TimestampMilli.now(), IncomingPaymentStatus.Pending)
278278
// We may be deducing some of the blinded path fees from the received amount.
279-
val recipientPathFees = nodeFee(metadata.recipientPathFees, payload.amount)
280-
replyTo ! MultiPartHandler.GetIncomingPaymentActor.ProcessPayment(incomingPayment, recipientPathFees)
279+
val maxRecipientPathFees = nodeFee(metadata.recipientPathFees, amount)
280+
replyTo ! MultiPartHandler.GetIncomingPaymentActor.ProcessPayment(incomingPayment, maxRecipientPathFees)
281281
Behaviors.stopped
282282
case RejectPayment(reason) =>
283283
replyTo ! MultiPartHandler.GetIncomingPaymentActor.RejectPayment(reason)

eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,14 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
125125
}
126126
}
127127

128-
case ProcessBlindedPacket(add, payload, payment, recipientPathFees) if doHandle(add.paymentHash) =>
128+
case ProcessBlindedPacket(add, payload, payment, maxRecipientPathFees) if doHandle(add.paymentHash) =>
129129
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(add.paymentHash))) {
130-
validateBlindedPayment(nodeParams, add, payload, payment, recipientPathFees) match {
130+
validateBlindedPayment(nodeParams, add, payload, payment, maxRecipientPathFees) match {
131131
case Some(cmdFail) =>
132132
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, Tags.FailureType(cmdFail)).increment()
133133
PendingCommandsDb.safeSend(register, nodeParams.db.pendingCommands, add.channelId, cmdFail)
134134
case None =>
135+
val recipientPathFees = payload.amount - add.amountMsat
135136
log.debug("received payment for amount={} recipientPathFees={} totalAmount={}", add.amountMsat, recipientPathFees, payload.totalAmount)
136137
addHtlcPart(ctx, add, payload, payment)
137138
if (recipientPathFees > 0.msat) {
@@ -237,7 +238,7 @@ object MultiPartHandler {
237238

238239
// @formatter:off
239240
private case class ProcessPacket(add: UpdateAddHtlc, payload: FinalPayload.Standard, payment_opt: Option[IncomingStandardPayment])
240-
private case class ProcessBlindedPacket(add: UpdateAddHtlc, payload: FinalPayload.Blinded, payment: IncomingBlindedPayment, recipientPathFees: MilliSatoshi)
241+
private case class ProcessBlindedPacket(add: UpdateAddHtlc, payload: FinalPayload.Blinded, payment: IncomingBlindedPayment, maxRecipientPathFees: MilliSatoshi)
241242
private case class RejectPacket(add: UpdateAddHtlc, failure: FailureMessage)
242243
case class DoFulfill(payment: IncomingPayment, success: MultiPartPaymentFSM.MultiPartPaymentSucceeded)
243244

@@ -358,7 +359,7 @@ object MultiPartHandler {
358359
// @formatter:off
359360
sealed trait Command
360361
case class GetIncomingPayment(replyTo: ActorRef) extends Command
361-
case class ProcessPayment(payment: IncomingBlindedPayment, recipientPathFees: MilliSatoshi) extends Command
362+
case class ProcessPayment(payment: IncomingBlindedPayment, maxRecipientPathFees: MilliSatoshi) extends Command
362363
case class RejectPayment(reason: String) extends Command
363364
// @formatter:on
364365

@@ -378,7 +379,7 @@ object MultiPartHandler {
378379
}
379380
Behaviors.stopped
380381
case payload: FinalPayload.Blinded =>
381-
offerManager ! OfferManager.ReceivePayment(context.self, packet.add.paymentHash, payload)
382+
offerManager ! OfferManager.ReceivePayment(context.self, packet.add.paymentHash, payload, packet.add.amountMsat)
382383
waitForPayment(context, nodeParams, replyTo, packet.add, payload)
383384
}
384385
}
@@ -388,8 +389,8 @@ object MultiPartHandler {
388389

389390
private def waitForPayment(context: typed.scaladsl.ActorContext[Command], nodeParams: NodeParams, replyTo: ActorRef, add: UpdateAddHtlc, payload: FinalPayload.Blinded): Behavior[Command] = {
390391
Behaviors.receiveMessagePartial {
391-
case ProcessPayment(payment, recipientPathFees) =>
392-
replyTo ! ProcessBlindedPacket(add, payload, payment, recipientPathFees)
392+
case ProcessPayment(payment, maxRecipientPathFees) =>
393+
replyTo ! ProcessBlindedPacket(add, payload, payment, maxRecipientPathFees)
393394
Behaviors.stopped
394395
case RejectPayment(reason) =>
395396
context.log.info("rejecting blinded htlc #{} from channel {}: {}", add.id, add.channelId, reason)
@@ -473,13 +474,13 @@ object MultiPartHandler {
473474
if (commonOk && secretOk) None else Some(cmdFail)
474475
}
475476

476-
private def validateBlindedPayment(nodeParams: NodeParams, add: UpdateAddHtlc, payload: FinalPayload.Blinded, record: IncomingBlindedPayment, recipientPathFees: MilliSatoshi)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
477+
private def validateBlindedPayment(nodeParams: NodeParams, add: UpdateAddHtlc, payload: FinalPayload.Blinded, record: IncomingBlindedPayment, maxRecipientPathFees: MilliSatoshi)(implicit log: LoggingAdapter): Option[CMD_FAIL_HTLC] = {
477478
// We send the same error regardless of the failure to avoid probing attacks.
478479
val cmdFail = CMD_FAIL_HTLC(add.id, FailureReason.LocalFailure(IncorrectOrUnknownPaymentDetails(payload.totalAmount, nodeParams.currentBlockHeight)), commit = true)
479480
val commonOk = validateCommon(nodeParams, add, payload, record)
480481
// The payer isn't aware of the blinded path fees if we decided to hide them. The HTLC amount will thus be smaller
481482
// than the onion amount, but should match when re-adding the blinded path fees.
482-
val pathFeesOk = add.amountMsat + recipientPathFees >= payload.amount
483+
val pathFeesOk = payload.amount - add.amountMsat <= maxRecipientPathFees
483484
if (commonOk && pathFeesOk) None else Some(cmdFail)
484485
}
485486

0 commit comments

Comments
 (0)