Skip to content

Commit 3bb7ee8

Browse files
authored
Parallel payment request generation (#1878)
Delegate the payment request generation, signature and db write to a short-lived child actor. There is small (~5%) gain in performance in `PerformanceIntegrationSpec` because what matters is the db write, and it is not parallelized in WAL mode.
1 parent d02760d commit 3bb7ee8

File tree

3 files changed

+55
-30
lines changed

3 files changed

+55
-30
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
245245

246246
override def receive(description: String, amount_opt: Option[MilliSatoshi], expire_opt: Option[Long], fallbackAddress_opt: Option[String], paymentPreimage_opt: Option[ByteVector32])(implicit timeout: Timeout): Future[PaymentRequest] = {
247247
fallbackAddress_opt.map { fa => fr.acinq.eclair.addressToPublicKeyScript(fa, appKit.nodeParams.chainHash) } // if it's not a bitcoin address throws an exception
248-
(appKit.paymentHandler ? ReceivePayment(amount_opt, description, expire_opt, fallbackAddress = fallbackAddress_opt, paymentPreimage = paymentPreimage_opt)).mapTo[PaymentRequest]
248+
(appKit.paymentHandler ? ReceivePayment(amount_opt, description, expire_opt, fallbackAddress_opt = fallbackAddress_opt, paymentPreimage_opt = paymentPreimage_opt)).mapTo[PaymentRequest]
249249
}
250250

251251
override def newAddress(): Future[String] = {

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

+53-28
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package fr.acinq.eclair.payment.receive
1818

1919
import akka.actor.Actor.Receive
20+
import akka.actor.typed.Behavior
21+
import akka.actor.typed.scaladsl.Behaviors
22+
import akka.actor.typed.scaladsl.adapter.ClassicActorContextOps
2023
import akka.actor.{ActorContext, ActorRef, PoisonPill, Status}
2124
import akka.event.{DiagnosticLoggingAdapter, LoggingAdapter}
2225
import fr.acinq.bitcoin.{ByteVector32, Crypto}
@@ -28,6 +31,7 @@ import fr.acinq.eclair.payment.{IncomingPacket, PaymentReceived, PaymentRequest}
2831
import fr.acinq.eclair.wire.protocol._
2932
import fr.acinq.eclair.{Features, Logs, MilliSatoshi, NodeParams, randomBytes32}
3033

34+
import java.util.UUID
3135
import scala.util.{Failure, Success, Try}
3236

3337
/**
@@ -52,26 +56,9 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
5256
def postFulfill(paymentReceived: PaymentReceived)(implicit log: LoggingAdapter): Unit = ()
5357

5458
override def handle(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Receive = {
55-
case ReceivePayment(amount_opt, desc, expirySeconds_opt, extraHops, fallbackAddress_opt, paymentPreimage_opt, paymentType) =>
56-
Try {
57-
val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32())
58-
val paymentHash = Crypto.sha256(paymentPreimage)
59-
val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
60-
val features = {
61-
val f1 = Seq(Features.PaymentSecret.mandatory, Features.VariableLengthOnion.mandatory)
62-
val allowMultiPart = nodeParams.features.hasFeature(Features.BasicMultiPartPayment)
63-
val f2 = if (allowMultiPart) Seq(Features.BasicMultiPartPayment.optional) else Nil
64-
val f3 = if (nodeParams.enableTrampolinePayment) Seq(Features.TrampolinePayment.optional) else Nil
65-
PaymentRequest.PaymentRequestFeatures(f1 ++ f2 ++ f3: _*)
66-
}
67-
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, desc, nodeParams.minFinalExpiryDelta, fallbackAddress_opt, expirySeconds = Some(expirySeconds), extraHops = extraHops, features = features)
68-
log.debug("generated payment request={} from amount={}", PaymentRequest.write(paymentRequest), amount_opt)
69-
db.addIncomingPayment(paymentRequest, paymentPreimage, paymentType)
70-
paymentRequest
71-
} match {
72-
case Success(paymentRequest) => ctx.sender ! paymentRequest
73-
case Failure(exception) => ctx.sender ! Status.Failure(exception)
74-
}
59+
case receivePayment: ReceivePayment =>
60+
val child = ctx.spawn(CreateInvoiceActor(nodeParams), name = UUID.randomUUID().toString)
61+
child ! CreateInvoiceActor.CreatePaymentRequest(ctx.sender(), receivePayment)
7562

7663
case p: IncomingPacket.FinalPacket if doHandle(p.add.paymentHash) =>
7764
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(p.add.paymentHash))) {
@@ -188,21 +175,59 @@ object MultiPartHandler {
188175
/**
189176
* Use this message to create a Bolt 11 invoice to receive a payment.
190177
*
191-
* @param amount_opt amount to receive in milli-satoshis.
192-
* @param description payment description.
193-
* @param expirySeconds_opt number of seconds before the invoice expires (relative to the invoice creation time).
194-
* @param extraHops routing hints to help the payer.
195-
* @param fallbackAddress fallback Bitcoin address.
196-
* @param paymentPreimage payment preimage.
178+
* @param amount_opt amount to receive in milli-satoshis.
179+
* @param description payment description.
180+
* @param expirySeconds_opt number of seconds before the invoice expires (relative to the invoice creation time).
181+
* @param extraHops routing hints to help the payer.
182+
* @param fallbackAddress_opt fallback Bitcoin address.
183+
* @param paymentPreimage_opt payment preimage.
197184
*/
198185
case class ReceivePayment(amount_opt: Option[MilliSatoshi],
199186
description: String,
200187
expirySeconds_opt: Option[Long] = None,
201188
extraHops: List[List[ExtraHop]] = Nil,
202-
fallbackAddress: Option[String] = None,
203-
paymentPreimage: Option[ByteVector32] = None,
189+
fallbackAddress_opt: Option[String] = None,
190+
paymentPreimage_opt: Option[ByteVector32] = None,
204191
paymentType: String = PaymentType.Standard)
205192

193+
194+
object CreateInvoiceActor {
195+
196+
// @formatter:off
197+
sealed trait Command
198+
case class CreatePaymentRequest(replyTo: ActorRef, receivePayment: ReceivePayment) extends Command
199+
// @formatter:on
200+
201+
def apply(nodeParams: NodeParams): Behavior[Command] = {
202+
Behaviors.setup { context =>
203+
Behaviors.receiveMessage {
204+
case CreatePaymentRequest(replyTo, receivePayment) =>
205+
Try {
206+
import receivePayment._
207+
val paymentPreimage = paymentPreimage_opt.getOrElse(randomBytes32())
208+
val paymentHash = Crypto.sha256(paymentPreimage)
209+
val expirySeconds = expirySeconds_opt.getOrElse(nodeParams.paymentRequestExpiry.toSeconds)
210+
val features = {
211+
val f1 = Seq(Features.PaymentSecret.mandatory, Features.VariableLengthOnion.mandatory)
212+
val allowMultiPart = nodeParams.features.hasFeature(Features.BasicMultiPartPayment)
213+
val f2 = if (allowMultiPart) Seq(Features.BasicMultiPartPayment.optional) else Nil
214+
val f3 = if (nodeParams.enableTrampolinePayment) Seq(Features.TrampolinePayment.optional) else Nil
215+
PaymentRequest.PaymentRequestFeatures(f1 ++ f2 ++ f3: _*)
216+
}
217+
val paymentRequest = PaymentRequest(nodeParams.chainHash, amount_opt, paymentHash, nodeParams.privateKey, description, nodeParams.minFinalExpiryDelta, fallbackAddress_opt, expirySeconds = Some(expirySeconds), extraHops = extraHops, features = features)
218+
context.log.debug("generated payment request={} from amount={}", PaymentRequest.write(paymentRequest), amount_opt)
219+
nodeParams.db.payments.addIncomingPayment(paymentRequest, paymentPreimage, paymentType)
220+
paymentRequest
221+
} match {
222+
case Success(paymentRequest) => replyTo ! paymentRequest
223+
case Failure(exception) => replyTo ! Status.Failure(exception)
224+
}
225+
Behaviors.stopped
226+
}
227+
}
228+
}
229+
}
230+
206231
private def validatePaymentStatus(payment: IncomingPacket.FinalPacket, record: IncomingPayment)(implicit log: LoggingAdapter): Boolean = {
207232
if (record.status.isInstanceOf[IncomingPaymentStatus.Received]) {
208233
log.warning("ignoring incoming payment for which has already been paid")

eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
326326

327327
assert(receive.amount_opt === Some(123 msat))
328328
assert(receive.expirySeconds_opt === Some(456))
329-
assert(receive.fallbackAddress === Some(fallBackAddressRaw))
329+
assert(receive.fallbackAddress_opt === Some(fallBackAddressRaw))
330330

331331
// try with wrong address format
332332
assertThrows[IllegalArgumentException](eclair.receive("some desc", Some(123 msat), Some(456), Some("wassa wassa"), None))

0 commit comments

Comments
 (0)