|
| 1 | +/* |
| 2 | + * Copyright 2019 ACINQ SAS |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package fr.acinq.eclair.integration |
| 18 | + |
| 19 | +import akka.testkit.TestProbe |
| 20 | +import com.typesafe.config.ConfigFactory |
| 21 | +import fr.acinq.bitcoin.SatoshiLong |
| 22 | +import fr.acinq.eclair.MilliSatoshiLong |
| 23 | +import fr.acinq.eclair.channel._ |
| 24 | +import fr.acinq.eclair.payment._ |
| 25 | +import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment |
| 26 | +import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle.PreimageReceived |
| 27 | +import fr.acinq.eclair.payment.send.PaymentInitiator |
| 28 | +import fr.acinq.eclair.router.Router |
| 29 | +import org.scalatest.Ignore |
| 30 | + |
| 31 | +import java.util.UUID |
| 32 | +import java.util.concurrent.Executors |
| 33 | +import scala.concurrent.duration._ |
| 34 | +import scala.concurrent.{Await, ExecutionContext, Future} |
| 35 | +import scala.jdk.CollectionConverters._ |
| 36 | + |
| 37 | +/** |
| 38 | + * Created by PM on 12/07/2021. |
| 39 | + */ |
| 40 | + |
| 41 | +@Ignore |
| 42 | +class PerformanceIntegrationSpec extends IntegrationSpec { |
| 43 | + |
| 44 | + test("start eclair nodes") { |
| 45 | + val commonPerfTestConfig = ConfigFactory.parseMap(Map( |
| 46 | + "eclair.max-funding-satoshis" -> 100_000_000, |
| 47 | + "eclair.max-accepted-htlcs" -> Channel.MAX_ACCEPTED_HTLCS, |
| 48 | + "eclair.file-backup.enabled" -> false, |
| 49 | + ).asJava) |
| 50 | + |
| 51 | + instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.server.port" -> 29730).asJava).withFallback(commonPerfTestConfig).withFallback(commonFeatures).withFallback(commonConfig)) // A's channels are private |
| 52 | + instantiateEclairNode("B", ConfigFactory.parseMap(Map("eclair.node-alias" -> "B", "eclair.server.port" -> 29731).asJava).withFallback(commonPerfTestConfig).withFallback(commonFeatures).withFallback(commonConfig)) |
| 53 | + } |
| 54 | + |
| 55 | + test("connect nodes") { |
| 56 | + // A---B |
| 57 | + |
| 58 | + val eventListener = TestProbe() |
| 59 | + nodes.values.foreach(_.system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged])) |
| 60 | + |
| 61 | + connect(nodes("A"), nodes("B"), 100_000_000 sat, 0 msat) |
| 62 | + |
| 63 | + // confirming the funding tx |
| 64 | + generateBlocks(6) |
| 65 | + |
| 66 | + within(60 seconds) { |
| 67 | + eventListener.expectMsgType[ChannelStateChanged](60 seconds).currentState == NORMAL |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + test("wait for channels") { |
| 72 | + // Channels should now be available in the router |
| 73 | + val sender = TestProbe() |
| 74 | + awaitCond({ |
| 75 | + sender.send(nodes("A").router, Router.GetRoutingState) |
| 76 | + val routingState = sender.expectMsgType[Router.RoutingState] |
| 77 | + routingState.channels.nonEmpty |
| 78 | + }, 60 seconds) |
| 79 | + } |
| 80 | + |
| 81 | + def sendPayment()(implicit ec: ExecutionContext): Future[PaymentSent] = Future { |
| 82 | + val sender = TestProbe() |
| 83 | + val amountMsat = 100_000.msat |
| 84 | + // first we retrieve a payment hash from B |
| 85 | + sender.send(nodes("B").paymentHandler, ReceivePayment(Some(amountMsat), "1 coffee")) |
| 86 | + val pr = sender.expectMsgType[PaymentRequest] |
| 87 | + // then we make the actual payment |
| 88 | + sender.send(nodes("A").paymentInitiator, PaymentInitiator.SendPayment(amountMsat, pr, fallbackFinalExpiryDelta = finalCltvExpiryDelta, routeParams = integrationTestRouteParams, maxAttempts = 1)) |
| 89 | + val paymentId = sender.expectMsgType[UUID] |
| 90 | + sender.expectMsgType[PreimageReceived] |
| 91 | + val ps = sender.expectMsgType[PaymentSent] |
| 92 | + assert(ps.id == paymentId) |
| 93 | + ps |
| 94 | + } |
| 95 | + |
| 96 | + test("send a large number of htlcs A->B") { |
| 97 | + val SENDERS_COUNT = 16 |
| 98 | + val PAYMENTS_COUNT = 3_000 |
| 99 | + val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(SENDERS_COUNT)) |
| 100 | + val start = System.currentTimeMillis() |
| 101 | + val futures = (0 until PAYMENTS_COUNT).map(_ => sendPayment()(ec)) |
| 102 | + implicit val dummyEc: ExecutionContext = ExecutionContext.Implicits.global |
| 103 | + val f = Future.sequence(futures) |
| 104 | + Await.result(f, 1 hour) |
| 105 | + val end = System.currentTimeMillis() |
| 106 | + val duration = end - start |
| 107 | + println(s"$PAYMENTS_COUNT payments in ${duration}ms ${PAYMENTS_COUNT * 1000 / duration}htlc/s (senders=$SENDERS_COUNT)") |
| 108 | + } |
| 109 | + |
| 110 | +} |
0 commit comments