Skip to content

Commit b4183ed

Browse files
authored
Fetch incoming payments in parallel (#1880)
This is a simpler approach to completely parallelizing the handling of payments, where we simply parallelize the fetch from the database. This brings a ~30% performance improvement in performance in `PerformanceIntegrationSpec`.
1 parent 5182402 commit b4183ed

File tree

2 files changed

+33
-10
lines changed

2 files changed

+33
-10
lines changed

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import fr.acinq.eclair.payment.{IncomingPacket, PaymentReceived, PaymentRequest}
3131
import fr.acinq.eclair.wire.protocol._
3232
import fr.acinq.eclair.{Features, Logs, MilliSatoshi, NodeParams, randomBytes32}
3333

34-
import java.util.UUID
3534
import scala.util.{Failure, Success, Try}
3635

3736
/**
@@ -57,12 +56,16 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
5756

5857
override def handle(implicit ctx: ActorContext, log: DiagnosticLoggingAdapter): Receive = {
5958
case receivePayment: ReceivePayment =>
60-
val child = ctx.spawn(CreateInvoiceActor(nodeParams), name = UUID.randomUUID().toString)
59+
val child = ctx.spawnAnonymous(CreateInvoiceActor(nodeParams))
6160
child ! CreateInvoiceActor.CreatePaymentRequest(ctx.sender(), receivePayment)
6261

6362
case p: IncomingPacket.FinalPacket if doHandle(p.add.paymentHash) =>
63+
val child = ctx.spawnAnonymous(GetIncomingPaymentActor(nodeParams))
64+
child ! GetIncomingPaymentActor.GetIncomingPayment(ctx.self, p)
65+
66+
case ProcessPacket(p, payment_opt) if doHandle(p.add.paymentHash) =>
6467
Logs.withMdc(log)(Logs.mdc(paymentHash_opt = Some(p.add.paymentHash))) {
65-
db.getIncomingPayment(p.add.paymentHash) match {
68+
payment_opt match {
6669
case Some(record) => validatePayment(nodeParams, p, record) match {
6770
case Some(cmdFail) =>
6871
Metrics.PaymentFailed.withTag(Tags.Direction, Tags.Directions.Received).withTag(Tags.Failure, Tags.FailureType(cmdFail)).increment()
@@ -166,6 +169,7 @@ class MultiPartHandler(nodeParams: NodeParams, register: ActorRef, db: IncomingP
166169
object MultiPartHandler {
167170

168171
// @formatter:off
172+
case class ProcessPacket(packet: IncomingPacket.FinalPacket, payment_opt: Option[IncomingPayment])
169173
case class DoFulfill(preimage: ByteVector32, success: MultiPartPaymentFSM.MultiPartPaymentSucceeded)
170174

171175
case object GetPendingPayments
@@ -228,6 +232,22 @@ object MultiPartHandler {
228232
}
229233
}
230234

235+
object GetIncomingPaymentActor {
236+
237+
// @formatter:off
238+
sealed trait Command
239+
case class GetIncomingPayment(replyTo: ActorRef, packet: IncomingPacket.FinalPacket) extends Command
240+
// @formatter:on
241+
242+
def apply(nodeParams: NodeParams): Behavior[Command] = {
243+
Behaviors.receiveMessage {
244+
case GetIncomingPayment(replyTo, packet) =>
245+
replyTo ! ProcessPacket(packet, nodeParams.db.payments.getIncomingPayment(packet.add.paymentHash))
246+
Behaviors.stopped
247+
}
248+
}
249+
}
250+
231251
private def validatePaymentStatus(payment: IncomingPacket.FinalPacket, record: IncomingPayment)(implicit log: LoggingAdapter): Boolean = {
232252
if (record.status.isInstanceOf[IncomingPaymentStatus.Received]) {
233253
log.warning("ignoring incoming payment for which has already been paid")

eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala

+10-7
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,11 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
436436
val nodeParams = Alice.nodeParams.copy(multiPartPaymentExpiry = 250 millis, features = featuresWithMpp)
437437
val handler = TestActorRef[PaymentHandler](PaymentHandler.props(nodeParams, f.register.ref))
438438

439-
f.sender.send(handler, ReceivePayment(Some(1000 msat), "1 coffee, no sugar"))
439+
val preimage = randomBytes32()
440+
f.sender.send(handler, ReceivePayment(Some(1000 msat), "1 coffee, no sugar", paymentPreimage_opt = Some(preimage)))
440441
val pr = f.sender.expectMsgType[PaymentRequest]
441442
assert(pr.features.allowMultiPart)
443+
assert(pr.paymentHash == Crypto.sha256(preimage))
442444

443445
val add1 = UpdateAddHtlc(ByteVector32.One, 0, 800 msat, pr.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket)
444446
f.sender.send(handler, IncomingPacket.FinalPacket(add1, Onion.createMultiPartPayload(add1.amountMsat, 1000 msat, add1.cltvExpiry, pr.paymentSecret.get)))
@@ -453,14 +455,15 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
453455
val add3 = UpdateAddHtlc(ByteVector32.Zeroes, 5, 700 msat, pr.paymentHash, f.defaultExpiry, TestConstants.emptyOnionPacket)
454456
f.sender.send(handler, IncomingPacket.FinalPacket(add3, Onion.createMultiPartPayload(add3.amountMsat, 1000 msat, add3.cltvExpiry, pr.paymentSecret.get)))
455457

456-
val cmd1 = f.register.expectMsgType[Register.Forward[CMD_FULFILL_HTLC]]
457-
assert(cmd1.channelId === add2.channelId)
458-
assert(cmd1.message.id === 2)
459-
assert(Crypto.sha256(cmd1.message.r) === pr.paymentHash)
460-
f.register.expectMsg(Register.Forward(ActorRef.noSender, add3.channelId, CMD_FULFILL_HTLC(5, cmd1.message.r, commit = true)))
458+
// the fulfill are not necessarily in the same order as the commands
459+
f.register.expectMsgAllOf(
460+
Register.Forward(ActorRef.noSender, add2.channelId, CMD_FULFILL_HTLC(2, preimage, commit = true)),
461+
Register.Forward(ActorRef.noSender, add3.channelId, CMD_FULFILL_HTLC(5, preimage, commit = true))
462+
)
461463

462464
val paymentReceived = f.eventListener.expectMsgType[PaymentReceived]
463-
assert(paymentReceived.copy(parts = paymentReceived.parts.map(_.copy(timestamp = 0))) === PaymentReceived(pr.paymentHash, PartialPayment(300 msat, ByteVector32.One, 0) :: PartialPayment(700 msat, ByteVector32.Zeroes, 0) :: Nil))
465+
assert(paymentReceived.paymentHash === pr.paymentHash)
466+
assert(paymentReceived.parts.map(_.copy(timestamp = 0)).toSet === Set(PartialPayment(300 msat, ByteVector32.One, 0), PartialPayment(700 msat, ByteVector32.Zeroes, 0)))
464467
val received = nodeParams.db.payments.getIncomingPayment(pr.paymentHash)
465468
assert(received.isDefined && received.get.status.isInstanceOf[IncomingPaymentStatus.Received])
466469
assert(received.get.status.asInstanceOf[IncomingPaymentStatus.Received].amount === 1000.msat)

0 commit comments

Comments
 (0)