mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
Upgrade to ecalir v0.3.2 (#818)
* Upgrade to ecalir v0.3.2 * addressed the PR comments * some more changes * and some more * external payment id
This commit is contained in:
parent
a17a4d3673
commit
06d210ba80
7 changed files with 497 additions and 373 deletions
|
@ -1,38 +1,48 @@
|
|||
package org.bitcoins.eclair.rpc
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.testkit.TestKit
|
||||
import org.bitcoins.core.config.RegTest
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
|
||||
import org.bitcoins.core.number.{Int64, UInt64}
|
||||
import org.bitcoins.core.protocol.ln.LnParams.LnBitcoinRegTest
|
||||
import org.bitcoins.core.protocol.ln.channel.{ChannelId, ChannelState, FundedChannelId}
|
||||
import org.bitcoins.core.protocol.ln.channel.{
|
||||
ChannelId,
|
||||
ChannelState,
|
||||
FundedChannelId
|
||||
}
|
||||
import org.bitcoins.core.protocol.ln.currency._
|
||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.eclair.rpc.client.EclairRpcClient
|
||||
import org.bitcoins.eclair.rpc.config.{EclairAuthCredentials, EclairInstance}
|
||||
import org.bitcoins.eclair.rpc.json._
|
||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||
import org.bitcoins.rpc.util.AsyncUtil
|
||||
import org.bitcoins.testkit.eclair.rpc.{EclairNodes4, EclairRpcTestUtil}
|
||||
import org.scalatest.{Assertion, AsyncFlatSpec, BeforeAndAfterAll}
|
||||
import org.slf4j.Logger
|
||||
import org.scalatest.Assertion
|
||||
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||
import akka.stream.StreamTcpException
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.ln.{LnHumanReadablePart, LnInvoice, PaymentPreimage}
|
||||
import org.bitcoins.core.protocol.ln.{
|
||||
LnHumanReadablePart,
|
||||
LnInvoice,
|
||||
PaymentPreimage
|
||||
}
|
||||
import org.bitcoins.testkit.async.TestAsyncUtil
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.bitcoins.eclair.rpc.api.{
|
||||
ChannelResult,
|
||||
ChannelUpdate,
|
||||
IncomingPaymentStatus,
|
||||
InvoiceResult,
|
||||
OpenChannelInfo,
|
||||
OutgoingPaymentStatus
|
||||
}
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
class EclairRpcClientTest extends BitcoinSAsyncTest {
|
||||
|
||||
private val dirExists = Files.exists(EclairRpcTestUtil.binaryDirectory)
|
||||
|
@ -53,9 +63,6 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
lazy val bitcoindRpcClientF: Future[BitcoindRpcClient] = {
|
||||
for {
|
||||
cli <- EclairRpcTestUtil.startedBitcoindRpcClient()
|
||||
|
@ -137,22 +144,21 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
it should "wait for all gossip messages get propagated throughout the network and get a route to an invoice" in {
|
||||
val invoiceF = fourthClientF.flatMap(_.createInvoice("foo", 1000.msats))
|
||||
val hasRoute = () => {
|
||||
invoiceF.flatMap { invoice =>
|
||||
firstClientF
|
||||
.flatMap(_.findRoute(invoice, None))
|
||||
.map(route => route.length == 4)
|
||||
.recover {
|
||||
case err: RuntimeException
|
||||
if err.getMessage.contains("route not found") =>
|
||||
false
|
||||
}
|
||||
(for {
|
||||
c1 <- firstClientF
|
||||
invoice <- invoiceF
|
||||
route <- c1.findRoute(invoice, None)
|
||||
} yield {
|
||||
route.size == 4
|
||||
}).recover {
|
||||
case err: RuntimeException
|
||||
if err.getMessage.contains("route not found") =>
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Eclair is a bit slow in propagating channel changes
|
||||
AsyncUtil
|
||||
.awaitConditionF(hasRoute, duration = 10.seconds, maxTries = 20)
|
||||
.awaitConditionF(hasRoute, duration = 1.second, maxTries = 60)
|
||||
.map(_ => succeed)
|
||||
}
|
||||
|
||||
|
@ -165,7 +171,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
paymentId <- client1.payInvoice(invoice)
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client1,
|
||||
paymentId,
|
||||
duration = 5.seconds)
|
||||
duration = 1.second)
|
||||
received <- client4.audit()
|
||||
relayed <- client2.audit()
|
||||
sent <- client1.audit()
|
||||
|
@ -190,9 +196,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
}
|
||||
}
|
||||
|
||||
// Eclair is a bit slow in propagating channel changes
|
||||
AsyncUtil
|
||||
.awaitConditionF(hasRoute, duration = 10.seconds, maxTries = 20)
|
||||
.awaitConditionF(hasRoute, duration = 1.second, maxTries = 60)
|
||||
.map(_ => succeed)
|
||||
}
|
||||
|
||||
|
@ -202,9 +207,13 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
for {
|
||||
_ <- openAndConfirmChannel(clientF, otherClientF)
|
||||
invoice <- otherClient.createInvoice("abc", 50.msats)
|
||||
paymentResult <- client.payAndMonitorInvoice(invoice, 1.second, 10)
|
||||
paymentResult <- client.payAndMonitorInvoice(invoice,
|
||||
Some("ext_id"),
|
||||
1.second,
|
||||
60)
|
||||
} yield {
|
||||
assert(paymentResult.amountMsat == 50.msats)
|
||||
assert(paymentResult.amount == 50.msats)
|
||||
assert(paymentResult.externalId.contains("ext_id"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +232,7 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
sentInfo <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
} yield {
|
||||
assert(sentInfo.head.amountMsat == 50.msats)
|
||||
assert(sentInfo.head.amount == 50.msats)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,21 +498,28 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
{
|
||||
for {
|
||||
otherClientNodeId <- otherClient.getInfo.map(_.nodeId)
|
||||
invoice <- otherClient.createInvoice("foo", amt)
|
||||
preimage = PaymentPreimage.random
|
||||
invoice <- otherClient.createInvoice("foo", amt, preimage)
|
||||
route <- client.findRoute(otherClientNodeId, amt)
|
||||
paymentId <- client.sendToRoute(route,
|
||||
amt,
|
||||
invoice.lnTags.paymentHash.hash,
|
||||
144)
|
||||
144,
|
||||
Some("ext_id"))
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
} yield {
|
||||
assert(succeeded.nonEmpty)
|
||||
|
||||
val succeededPayment = succeeded.head
|
||||
assert(succeededPayment.status == PaymentStatus.SUCCEEDED)
|
||||
assert(succeededPayment.amountMsat == amt)
|
||||
assert(succeededPayment.preimage.nonEmpty)
|
||||
assert(succeededPayment.amount == amt)
|
||||
assert(succeededPayment.externalId.contains("ext_id"))
|
||||
succeededPayment.status match {
|
||||
case sent: OutgoingPaymentStatus.Succeeded =>
|
||||
assert(sent.paymentPreimage == preimage)
|
||||
case s: OutgoingPaymentStatus =>
|
||||
fail(s"Unexpected payment status ${s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -525,13 +541,15 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
// probably an async issue, this is more elegant than Thread.sleep
|
||||
_ = assert(channels.exists(_.state == ChannelState.NORMAL),
|
||||
"Nodes did not have open channel!")
|
||||
invoice <- otherClient.createInvoice("foo", amt)
|
||||
preimage = PaymentPreimage.random
|
||||
invoice <- otherClient.createInvoice("foo", amt, preimage)
|
||||
paymentId <- client.sendToNode(otherClientNodeId,
|
||||
amt,
|
||||
invoice.lnTags.paymentHash.hash,
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
None,
|
||||
Some("ext_id"))
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
_ <- client.close(channelId)
|
||||
|
@ -542,9 +560,14 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
assert(succeeded.nonEmpty)
|
||||
|
||||
val succeededPayment = succeeded.head
|
||||
assert(succeededPayment.status == PaymentStatus.SUCCEEDED)
|
||||
assert(succeededPayment.amountMsat == amt)
|
||||
assert(succeededPayment.preimage.nonEmpty)
|
||||
assert(succeededPayment.amount == amt)
|
||||
assert(succeededPayment.externalId.contains("ext_id"))
|
||||
succeededPayment.status match {
|
||||
case sent: OutgoingPaymentStatus.Succeeded =>
|
||||
assert(sent.paymentPreimage == preimage)
|
||||
case s: OutgoingPaymentStatus =>
|
||||
fail(s"Unexpected payment status ${s}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -561,7 +584,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
{
|
||||
for {
|
||||
channelId <- openAndConfirmChannel(clientF, otherClientF)
|
||||
invoice <- otherClient.createInvoice("test", amt)
|
||||
preimage = PaymentPreimage.random
|
||||
invoice <- otherClient.createInvoice("test", amt, preimage)
|
||||
paymentId <- client.payInvoice(invoice)
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
|
@ -576,13 +600,21 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
} yield {
|
||||
assert(succeeded.nonEmpty)
|
||||
|
||||
assert(received.get.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
assert(received.get.amountMsat == amt)
|
||||
assert(
|
||||
received.get.paymentRequest.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
assert(
|
||||
received.get.status
|
||||
.asInstanceOf[IncomingPaymentStatus.Received]
|
||||
.amount == amt)
|
||||
|
||||
val succeededPayment = succeeded.head
|
||||
assert(succeededPayment.status == PaymentStatus.SUCCEEDED)
|
||||
assert(succeededPayment.amountMsat == amt)
|
||||
assert(succeededPayment.preimage.nonEmpty)
|
||||
assert(succeededPayment.amount == amt)
|
||||
succeededPayment.status match {
|
||||
case sent: OutgoingPaymentStatus.Succeeded =>
|
||||
assert(sent.paymentPreimage == preimage)
|
||||
case s: OutgoingPaymentStatus =>
|
||||
fail(s"Unexpected payment status ${s}")
|
||||
}
|
||||
|
||||
assert(channel.state == ChannelState.CLOSING)
|
||||
}
|
||||
|
@ -602,7 +634,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
invoice <- otherClient.createInvoice("no amount")
|
||||
paymentId <- client.payInvoice(invoice, amt)
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
_ <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
succeeded <- client.getSentInfo(paymentId)
|
||||
_ <- client.close(channelId)
|
||||
bitcoind <- bitcoindRpcClientF
|
||||
address <- bitcoind.getNewAddress
|
||||
|
@ -611,9 +644,10 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
assert(succeeded.nonEmpty)
|
||||
|
||||
val succeededPayment = succeeded.head
|
||||
assert(succeededPayment.status == PaymentStatus.SUCCEEDED)
|
||||
assert(succeededPayment.amountMsat == amt)
|
||||
assert(succeededPayment.preimage.nonEmpty)
|
||||
assert(succeededPayment.amount == amt)
|
||||
assert(
|
||||
succeededPayment.status
|
||||
.isInstanceOf[OutgoingPaymentStatus.Succeeded])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -720,17 +754,22 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
c2 <- otherClientF
|
||||
invoice <- c2.createInvoice(s"invoice-payment")
|
||||
receiveOpt <- c2.getReceivedInfo(invoice)
|
||||
_ = assert(receiveOpt.isEmpty)
|
||||
_ = assert(receiveOpt.get.status == IncomingPaymentStatus.Pending)
|
||||
_ <- c1.payInvoice(invoice, amt)
|
||||
_ <- AsyncUtil.retryUntilSatisfiedF(
|
||||
() => c2.getReceivedInfo(invoice).map(_.isDefined),
|
||||
() =>
|
||||
c2.getReceivedInfo(invoice)
|
||||
.map(_.get.status.isInstanceOf[IncomingPaymentStatus.Received]),
|
||||
1.seconds)
|
||||
receivedAgainOpt <- c2.getReceivedInfo(invoice)
|
||||
} yield {
|
||||
assert(receivedAgainOpt.isDefined)
|
||||
assert(receivedAgainOpt.get.amountMsat == amt)
|
||||
assert(
|
||||
receivedAgainOpt.get.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
receivedAgainOpt.get.status
|
||||
.asInstanceOf[IncomingPaymentStatus.Received]
|
||||
.amount == amt)
|
||||
assert(
|
||||
receivedAgainOpt.get.paymentRequest.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -740,13 +779,17 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
val invoiceF = otherClient.createInvoice("monitor an invoice", amt)
|
||||
val paidF = invoiceF.flatMap(i => client.payInvoice(i))
|
||||
for {
|
||||
paid <- paidF
|
||||
_ <- paidF
|
||||
invoice <- invoiceF
|
||||
//CI is super slow... wait 2 minutes
|
||||
received <- otherClient.monitorInvoice(invoice, maxAttempts = 120)
|
||||
received <- otherClient.monitorInvoice(invoice)
|
||||
} yield {
|
||||
assert(received.amountMsat == amt)
|
||||
assert(received.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
assert(
|
||||
received.status
|
||||
.asInstanceOf[IncomingPaymentStatus.Received]
|
||||
.amount == amt)
|
||||
assert(
|
||||
received.paymentRequest.paymentHash == invoice.lnTags.paymentHash.hash)
|
||||
}
|
||||
}
|
||||
executeWithClientOtherClient(test)
|
||||
|
@ -846,11 +889,13 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
val client1F = connectedClientsF.map(_.c1)
|
||||
val client2F = connectedClientsF.map(_.c2)
|
||||
|
||||
sendPaymentsF.flatMap { _ =>
|
||||
// allupdates for a single node is broken in Eclair 0.3.2
|
||||
// TODO remove recoverToPendingIf when https://github.com/ACINQ/eclair/issues/1179 is fixed
|
||||
recoverToPendingIf[RuntimeException](sendPaymentsF.flatMap { _ =>
|
||||
executeSpecificClients(clientF = client1F,
|
||||
otherClientF = client2F,
|
||||
test = getChannelUpdates)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
@ -874,8 +919,8 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
paymentId2)
|
||||
sentInfo2 <- otherClient.getSentInfo(invoice2.lnTags.paymentHash.hash)
|
||||
} yield {
|
||||
assert(sentInfo.head.amountMsat == paymentAmount)
|
||||
assert(sentInfo2.head.amountMsat == paymentAmount2)
|
||||
assert(sentInfo.head.amount == paymentAmount)
|
||||
assert(sentInfo2.head.amount == paymentAmount2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -924,6 +969,10 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
}
|
||||
}
|
||||
|
||||
it should "get all nodes" in {
|
||||
clientF.flatMap(_.allNodes().flatMap(nodes => assert(nodes.nonEmpty)))
|
||||
}
|
||||
|
||||
it should "get channels" in {
|
||||
clientF.flatMap(_.channels().flatMap(channels => assert(channels.nonEmpty)))
|
||||
}
|
||||
|
@ -939,18 +988,31 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
})
|
||||
}
|
||||
|
||||
def recoverToPendingIf[T <: AnyRef](future: Future[Any])(
|
||||
implicit classTag: ClassTag[T]): Future[Assertion] = {
|
||||
|
||||
val clazz = classTag.runtimeClass
|
||||
future.failed.transform(
|
||||
ex =>
|
||||
if (!clazz.isAssignableFrom(ex.getClass))
|
||||
fail(s"Unknown exception ${ex}")
|
||||
else pending,
|
||||
ex => {
|
||||
fail(s"Unexpected exception ${ex}")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
it should "get updates for a single node" in {
|
||||
for {
|
||||
// allupdates for a single node is broken in Eclair 0.3.2
|
||||
// TODO remove recoverToPendingIf when https://github.com/ACINQ/eclair/issues/1179 is fixed
|
||||
recoverToPendingIf[RuntimeException](for {
|
||||
client <- clientF
|
||||
nodeInfo <- client.getInfo
|
||||
updates <- client.allUpdates(nodeInfo.nodeId)
|
||||
} yield {
|
||||
assert(updates.nonEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
it should "get all nodes" in {
|
||||
clientF.flatMap(_.allNodes().flatMap(nodes => assert(nodes.nonEmpty)))
|
||||
})
|
||||
}
|
||||
|
||||
it must "receive gossip messages about channel updates for nodes we do not have a direct channel with" in {
|
||||
|
@ -980,27 +1042,24 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
|
|||
})
|
||||
}
|
||||
|
||||
val checkedUpatesF: Future[Unit] =
|
||||
AsyncUtil.retryUntilSatisfiedF((() => checkUpdates()),
|
||||
duration = 5.seconds,
|
||||
maxTries = 15)
|
||||
|
||||
val hasUpdateP = Promise[Assertion]()
|
||||
checkedUpatesF.onComplete { t =>
|
||||
hasUpdateP.success(assert(t.isSuccess))
|
||||
}
|
||||
|
||||
hasUpdateP.future
|
||||
AsyncUtil
|
||||
.retryUntilSatisfiedF((() => checkUpdates()),
|
||||
duration = 1.second,
|
||||
maxTries = 60)
|
||||
.transform(_ => succeed, ex => ex)
|
||||
}
|
||||
}
|
||||
|
||||
//the second client and fourth client aren't directly connected
|
||||
//which is why i am choosing to use them for this test
|
||||
executeSpecificClients(
|
||||
clientF = secondClientF,
|
||||
otherClientF = fourthClientF,
|
||||
test = gossipFromPeerWithNoChannel
|
||||
)
|
||||
// allupdates for a single node is broken in Eclair 0.3.2
|
||||
// TODO remove pendingUntilFixed when https://github.com/ACINQ/eclair/issues/1179 is fixed
|
||||
recoverToPendingIf[RuntimeException](
|
||||
executeSpecificClients(
|
||||
clientF = secondClientF,
|
||||
otherClientF = fourthClientF,
|
||||
test = gossipFromPeerWithNoChannel
|
||||
))
|
||||
}
|
||||
|
||||
it should "detect what network we are on" in {
|
||||
|
|
|
@ -19,8 +19,8 @@ TaskKeys.downloadEclair := {
|
|||
Files.createDirectories(binaryDir)
|
||||
}
|
||||
|
||||
val version = "0.3.1"
|
||||
val commit = "6906ecb"
|
||||
val version = "0.3.2"
|
||||
val commit = "5ad3944"
|
||||
|
||||
logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
|||
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||
import org.bitcoins.eclair.rpc.json._
|
||||
import org.bitcoins.eclair.rpc.network.NodeUri
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
@ -158,7 +157,7 @@ trait EclairApi {
|
|||
|
||||
/**
|
||||
* Returns a future that is completed when this invoice has been paid too.
|
||||
* This also publishes the [[ReceivedPaymentResult received payment result]] to the event bush
|
||||
* This also publishes the [[IncomingPayment received payment result]] to the event bush
|
||||
* when the payment is received
|
||||
*
|
||||
* @param lnInvoice the invoice to monitor
|
||||
|
@ -166,7 +165,8 @@ trait EclairApi {
|
|||
* */
|
||||
def monitorInvoice(
|
||||
lnInvoice: LnInvoice,
|
||||
maxAttempts: Int): Future[ReceivedPaymentResult]
|
||||
interval: FiniteDuration,
|
||||
maxAttempts: Int): Future[IncomingPayment]
|
||||
|
||||
def getInvoice(paymentHash: Sha256Digest): Future[LnInvoice]
|
||||
|
||||
|
@ -180,15 +180,25 @@ trait EclairApi {
|
|||
|
||||
def payInvoice(invoice: LnInvoice, amount: MilliSatoshis): Future[PaymentId]
|
||||
|
||||
def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
externalId: Option[String]): Future[PaymentId]
|
||||
|
||||
def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
amount: MilliSatoshis,
|
||||
externalId: Option[String]): Future[PaymentId]
|
||||
|
||||
def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
amountMsat: Option[MilliSatoshis],
|
||||
maxAttempts: Option[Int],
|
||||
feeThresholdSat: Option[Satoshis],
|
||||
maxFeePct: Option[Int]): Future[PaymentId]
|
||||
maxFeePct: Option[Int],
|
||||
externalId: Option[String]): Future[PaymentId]
|
||||
|
||||
/**
|
||||
* Pings eclair to see if a invoice has been paid and returns [[org.bitcoins.eclair.rpc.json.PaymentResult PaymentResult]]
|
||||
* Pings eclair to see if a invoice has been paid and returns [[OutgoingPayment PaymentResult]]
|
||||
*
|
||||
* @param paymentId the payment id returnned by [[org.bitcoins.eclair.rpc.api.EclairApi.payInvoice payInvoice]]
|
||||
* @param interval the ping interval
|
||||
|
@ -198,36 +208,37 @@ trait EclairApi {
|
|||
def monitorSentPayment(
|
||||
paymentId: PaymentId,
|
||||
interval: FiniteDuration,
|
||||
maxAttempts: Int): Future[PaymentResult]
|
||||
maxAttempts: Int): Future[OutgoingPayment]
|
||||
|
||||
def payAndMonitorInvoice(
|
||||
invoice: LnInvoice,
|
||||
externalId: Option[String],
|
||||
interval: FiniteDuration,
|
||||
maxAttempts: Int): Future[PaymentResult] =
|
||||
maxAttempts: Int): Future[OutgoingPayment] =
|
||||
for {
|
||||
paymentId <- payInvoice(invoice)
|
||||
paymentId <- payInvoice(invoice, externalId)
|
||||
paymentResult <- monitorSentPayment(paymentId, interval, maxAttempts)
|
||||
} yield paymentResult
|
||||
|
||||
def payAndMonitorInvoice(
|
||||
invoice: LnInvoice,
|
||||
amount: MilliSatoshis,
|
||||
externalId: Option[String],
|
||||
interval: FiniteDuration,
|
||||
maxAttempts: Int): Future[PaymentResult] =
|
||||
maxAttempts: Int): Future[OutgoingPayment] =
|
||||
for {
|
||||
paymentId <- payInvoice(invoice, amount)
|
||||
paymentId <- payInvoice(invoice, amount, externalId)
|
||||
paymentResult <- monitorSentPayment(paymentId, interval, maxAttempts)
|
||||
} yield paymentResult
|
||||
|
||||
def getSentInfo(paymentHash: Sha256Digest): Future[Vector[PaymentResult]]
|
||||
def getSentInfo(paymentHash: Sha256Digest): Future[Vector[OutgoingPayment]]
|
||||
|
||||
def getSentInfo(id: PaymentId): Future[Vector[PaymentResult]]
|
||||
def getSentInfo(id: PaymentId): Future[Vector[OutgoingPayment]]
|
||||
|
||||
def getReceivedInfo(
|
||||
paymentHash: Sha256Digest): Future[Option[ReceivedPaymentResult]]
|
||||
paymentHash: Sha256Digest): Future[Option[IncomingPayment]]
|
||||
|
||||
def getReceivedInfo(
|
||||
invoice: LnInvoice): Future[Option[ReceivedPaymentResult]] = {
|
||||
def getReceivedInfo(invoice: LnInvoice): Future[Option[IncomingPayment]] = {
|
||||
getReceivedInfo(invoice.lnTags.paymentHash.hash)
|
||||
}
|
||||
|
||||
|
@ -237,7 +248,8 @@ trait EclairApi {
|
|||
paymentHash: Sha256Digest,
|
||||
maxAttempts: Option[Int],
|
||||
feeThresholdSat: Option[Satoshis],
|
||||
maxFeePct: Option[Int]): Future[PaymentId]
|
||||
maxFeePct: Option[Int],
|
||||
externalId: Option[String]): Future[PaymentId]
|
||||
|
||||
/**
|
||||
* Documented by not implemented in Eclair
|
||||
|
@ -246,7 +258,8 @@ trait EclairApi {
|
|||
route: scala.collection.immutable.Seq[NodeId],
|
||||
amountMsat: MilliSatoshis,
|
||||
paymentHash: Sha256Digest,
|
||||
finalCltvExpiry: Long): Future[PaymentId]
|
||||
finalCltvExpiry: Long,
|
||||
externalId: Option[String]): Future[PaymentId]
|
||||
|
||||
def usableBalances(): Future[Vector[UsableBalancesResult]]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
package org.bitcoins.eclair.rpc.json
|
||||
package org.bitcoins.eclair.rpc.api
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.bitcoins.core.crypto.{
|
||||
DoubleSha256Digest,
|
||||
|
@ -13,7 +15,6 @@ import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
|||
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||
import org.bitcoins.core.protocol.ln.{
|
||||
LnHumanReadablePart,
|
||||
LnInvoiceSignature,
|
||||
PaymentPreimage,
|
||||
ShortChannelId
|
||||
}
|
||||
|
@ -100,32 +101,40 @@ case class NetworkFeesResult(
|
|||
remoteNodeId: NodeId,
|
||||
channelId: FundedChannelId,
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeSat: Satoshis,
|
||||
fee: Satoshis,
|
||||
txType: String,
|
||||
timestamp: FiniteDuration
|
||||
)
|
||||
|
||||
case class ChannelStats(
|
||||
channelId: FundedChannelId,
|
||||
avgPaymentAmountSatoshi: Satoshis,
|
||||
avgPaymentAmount: Satoshis,
|
||||
paymentCount: Long,
|
||||
relayFeeSatoshi: Satoshis,
|
||||
networkFeeSatoshi: Satoshis
|
||||
relayFee: Satoshis,
|
||||
networkFee: Satoshis
|
||||
)
|
||||
|
||||
case class UsableBalancesResult(
|
||||
canSendMsat: MilliSatoshis,
|
||||
canReceiveMsat: MilliSatoshis,
|
||||
remoteNodeId: NodeId,
|
||||
shortChannelId: ShortChannelId,
|
||||
canSend: MilliSatoshis,
|
||||
canReceive: MilliSatoshis,
|
||||
isPublic: Boolean
|
||||
)
|
||||
|
||||
case class ReceivedPayment(
|
||||
amount: MilliSatoshis,
|
||||
paymentHash: Sha256Digest,
|
||||
fromChannelId: FundedChannelId,
|
||||
timestamp: FiniteDuration
|
||||
parts: Vector[ReceivedPayment.Part]
|
||||
)
|
||||
|
||||
object ReceivedPayment {
|
||||
case class Part(
|
||||
amount: MilliSatoshis,
|
||||
fromChannelId: FundedChannelId,
|
||||
timestamp: FiniteDuration
|
||||
)
|
||||
}
|
||||
|
||||
case class RelayedPayment(
|
||||
amountIn: MilliSatoshis,
|
||||
amountOut: MilliSatoshis,
|
||||
|
@ -136,14 +145,22 @@ case class RelayedPayment(
|
|||
)
|
||||
|
||||
case class SentPayment(
|
||||
amount: MilliSatoshis,
|
||||
feesPaid: MilliSatoshis,
|
||||
id: PaymentId,
|
||||
paymentHash: Sha256Digest,
|
||||
paymentPreimage: String,
|
||||
toChannelId: FundedChannelId,
|
||||
timestamp: FiniteDuration
|
||||
paymentPreimage: PaymentPreimage,
|
||||
parts: Vector[SentPayment.Part]
|
||||
)
|
||||
|
||||
object SentPayment {
|
||||
case class Part(
|
||||
id: PaymentId,
|
||||
amount: MilliSatoshis,
|
||||
feesPaid: MilliSatoshis,
|
||||
toChannelId: FundedChannelId,
|
||||
timestamp: FiniteDuration
|
||||
)
|
||||
}
|
||||
|
||||
case class ChannelUpdate(
|
||||
signature: ECDigitalSignature,
|
||||
chainHash: DoubleSha256Digest,
|
||||
|
@ -157,116 +174,6 @@ case class ChannelUpdate(
|
|||
htlcMaximumMsat: Option[MilliSatoshis],
|
||||
feeBaseMsat: MilliSatoshis)
|
||||
|
||||
/* ChannelResult starts here, some of this may be useful but it seems that data is different at different times
|
||||
|
||||
case class CommitInput(
|
||||
outPoint: String,
|
||||
amountSatoshis: Long)
|
||||
implicit val commitInputReads: Reads[CommitInput] =
|
||||
Json.reads[CommitInput]
|
||||
|
||||
case class CommitChanges(
|
||||
proposed: Vector[String], // IDK WHAT TYPE THIS SHOULD BE
|
||||
signed: Vector[String], // IDK WHAT TYPE THIS SHOULD BE
|
||||
acked: Vector[String] // IDK WHAT TYPE THIS SHOULD BE
|
||||
)
|
||||
implicit val commitChangesReads: Reads[CommitChanges] =
|
||||
Json.reads[CommitChanges]
|
||||
|
||||
case class CommitSpec(
|
||||
htlcs: Vector[String],
|
||||
feeratePerKw: Long,
|
||||
toLocalMsat: Long,
|
||||
toRemoteMsat: Long)
|
||||
implicit val commitSpecReads: Reads[CommitSpec] =
|
||||
Json.reads[CommitSpec]
|
||||
|
||||
case class RemoteCommit(
|
||||
index: Int,
|
||||
spec: CommitSpec,
|
||||
txid: String,
|
||||
remotePerCommitmentPoint: String)
|
||||
implicit val remoteCommitReads: Reads[RemoteCommit] =
|
||||
Json.reads[RemoteCommit]
|
||||
|
||||
case class PublishableTxs(
|
||||
commitTx: String,
|
||||
htlcTxsAndSigs: Vector[String])
|
||||
implicit val publishableTxsReads: Reads[PublishableTxs] =
|
||||
Json.reads[PublishableTxs]
|
||||
|
||||
case class LocalCommit(
|
||||
index: Int,
|
||||
spec: CommitSpec,
|
||||
publishableTxs: PublishableTxs)
|
||||
implicit val localCommitReads: Reads[LocalCommit] =
|
||||
Json.reads[LocalCommit]
|
||||
|
||||
case class RemoteParams(
|
||||
nodeId: String,
|
||||
dustLimitSatoshis: Long,
|
||||
maxHtlcValueInFlightMsat: Long,
|
||||
channelReserveSatoshis: Long,
|
||||
htlcMinimumMsat: Long,
|
||||
toSelfDelay: Long,
|
||||
maxAcceptedHtlcs: Long,
|
||||
fundingPubKey: String,
|
||||
revocationBasepoint: String,
|
||||
paymentBasepoint: String,
|
||||
delayedPaymentBasepoint: String,
|
||||
htlcBasepoint: String,
|
||||
globalFeatures: String,
|
||||
localFeatures: String)
|
||||
implicit val remoteParamsReads: Reads[RemoteParams] =
|
||||
Json.reads[RemoteParams]
|
||||
|
||||
case class ChannelKeyPath(
|
||||
path: Vector[Long])
|
||||
implicit val channelKeyPathReads: Reads[ChannelKeyPath] =
|
||||
Json.reads[ChannelKeyPath]
|
||||
|
||||
case class LocalParams(
|
||||
nodeId: String,
|
||||
channelKeyPath: ChannelKeyPath,
|
||||
dustLimitSatoshis: Long,
|
||||
maxHtlcValueInFlightMsat: Long,
|
||||
channelReserveSatoshis: Long,
|
||||
htlcMinimumMsat: Long,
|
||||
toSelfDelay: Long,
|
||||
maxAcceptedHtlcs: Long,
|
||||
isFunder: Boolean,
|
||||
defaultFinalScriptPubKey: String,
|
||||
globalFeatures: String,
|
||||
localFeatures: String)
|
||||
implicit val localParamsReads: Reads[LocalParams] =
|
||||
Json.reads[LocalParams]
|
||||
|
||||
case class ChannelCommitments(
|
||||
localParams: LocalParams,
|
||||
remoteParams: RemoteParams,
|
||||
channelFlags: Int,
|
||||
localCommit: LocalCommit,
|
||||
remoteCommit: RemoteCommit,
|
||||
localChanges: CommitChanges,
|
||||
remoteChanges: CommitChanges,
|
||||
localNextHtlcId: Long,
|
||||
remoteNextHtlcId: Long,
|
||||
originChannels: String, // IDK WHAT TYPE THIS SHOULD BE
|
||||
remoteNextCommitInfo: String,
|
||||
commitInput: CommitInput,
|
||||
remotePerCommitmentSecrets: Option[String], // IDK WHAT TYPE THIS SHOULD BE
|
||||
channelId: String)
|
||||
implicit val channelCommitmentsReads: Reads[ChannelCommitments] =
|
||||
Json.reads[ChannelCommitments]
|
||||
|
||||
case class ChannelData(
|
||||
commitments: ChannelCommitments,
|
||||
shortChannelId: String,
|
||||
buried: Boolean,
|
||||
channelUpdate: ChannelUpdate)
|
||||
implicit val channelDataReads: Reads[ChannelData] =
|
||||
Json.reads[ChannelData]
|
||||
*/
|
||||
case class ChannelResult(
|
||||
nodeId: NodeId,
|
||||
channelId: FundedChannelId,
|
||||
|
@ -274,7 +181,7 @@ case class ChannelResult(
|
|||
feeBaseMsat: Option[MilliSatoshis],
|
||||
feeProportionalMillionths: Option[FeeProportionalMillionths],
|
||||
data: JsObject) {
|
||||
import JsonReaders._
|
||||
import org.bitcoins.eclair.rpc.client.JsonReaders._
|
||||
lazy val shortChannelId: Option[ShortChannelId] =
|
||||
(data \ "shortChannelId").validate[ShortChannelId].asOpt
|
||||
}
|
||||
|
@ -290,49 +197,81 @@ case class InvoiceResult(
|
|||
paymentHash: Sha256Digest,
|
||||
expiry: FiniteDuration)
|
||||
|
||||
case class PaymentId(value: UUID) {
|
||||
override def toString: String = value.toString
|
||||
}
|
||||
|
||||
case class PaymentRequest(
|
||||
prefix: LnHumanReadablePart,
|
||||
amount: Option[MilliSatoshis],
|
||||
timestamp: Long,
|
||||
nodeId: NodeId,
|
||||
tags: Vector[JsObject],
|
||||
signature: LnInvoiceSignature)
|
||||
|
||||
case class PaymentResult(
|
||||
id: String,
|
||||
serialized: String,
|
||||
description: String,
|
||||
paymentHash: Sha256Digest,
|
||||
preimage: Option[PaymentPreimage],
|
||||
amountMsat: MilliSatoshis,
|
||||
expiry: FiniteDuration,
|
||||
amount: Option[MilliSatoshis])
|
||||
|
||||
case class OutgoingPayment(
|
||||
id: PaymentId,
|
||||
parentId: PaymentId,
|
||||
externalId: Option[String],
|
||||
paymentHash: Sha256Digest,
|
||||
amount: MilliSatoshis,
|
||||
targetNodeId: NodeId,
|
||||
createdAt: FiniteDuration,
|
||||
completedAt: Option[FiniteDuration],
|
||||
status: PaymentStatus)
|
||||
paymentRequest: Option[PaymentRequest],
|
||||
status: OutgoingPaymentStatus)
|
||||
|
||||
case class ReceivedPaymentResult(
|
||||
paymentHash: Sha256Digest,
|
||||
amountMsat: MilliSatoshis,
|
||||
receivedAt: FiniteDuration)
|
||||
case class IncomingPayment(
|
||||
paymentRequest: PaymentRequest,
|
||||
paymentPreimage: PaymentPreimage,
|
||||
createdAt: FiniteDuration,
|
||||
status: IncomingPaymentStatus)
|
||||
|
||||
sealed trait PaymentStatus
|
||||
sealed trait IncomingPaymentStatus
|
||||
|
||||
object PaymentStatus {
|
||||
case object PENDING extends PaymentStatus
|
||||
case object SUCCEEDED extends PaymentStatus
|
||||
case object FAILED extends PaymentStatus
|
||||
object IncomingPaymentStatus {
|
||||
|
||||
case object Pending extends IncomingPaymentStatus
|
||||
|
||||
case object Expired extends IncomingPaymentStatus
|
||||
|
||||
case class Received(amount: MilliSatoshis, receivedAt: Long)
|
||||
extends IncomingPaymentStatus
|
||||
|
||||
def apply(s: String): PaymentStatus = s match {
|
||||
case "PENDING" => PENDING
|
||||
case "SUCCEEDED" => SUCCEEDED
|
||||
case "FAILED" => FAILED
|
||||
case err =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Unknown payment status code `${err}`")
|
||||
}
|
||||
}
|
||||
|
||||
case class PaymentId(value: String) {
|
||||
override def toString: String = value
|
||||
sealed trait OutgoingPaymentStatus
|
||||
|
||||
object OutgoingPaymentStatus {
|
||||
case object Pending extends OutgoingPaymentStatus
|
||||
|
||||
case class Succeeded(
|
||||
paymentPreimage: PaymentPreimage,
|
||||
feesPaid: MilliSatoshis,
|
||||
route: Seq[Hop],
|
||||
completedAt: FiniteDuration)
|
||||
extends OutgoingPaymentStatus
|
||||
|
||||
case class Failed(failures: Seq[PaymentFailure]) extends OutgoingPaymentStatus
|
||||
}
|
||||
|
||||
case class PaymentFailure(
|
||||
failureType: PaymentFailure.Type,
|
||||
failureMessage: String,
|
||||
failedRoute: Seq[Hop])
|
||||
|
||||
object PaymentFailure {
|
||||
sealed trait Type
|
||||
case object Local extends Type
|
||||
case object Remote extends Type
|
||||
case object UnreadableRemote extends Type
|
||||
}
|
||||
case class Hop(
|
||||
nodeId: NodeId,
|
||||
nextNodeId: NodeId,
|
||||
shortChannelId: Option[ShortChannelId])
|
||||
|
||||
sealed trait WebSocketEvent
|
||||
|
||||
object WebSocketEvent {
|
|
@ -24,9 +24,27 @@ import org.bitcoins.core.protocol.ln.{
|
|||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.util.BitcoinSUtil
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||
import org.bitcoins.eclair.rpc.api.EclairApi
|
||||
import org.bitcoins.eclair.rpc.api.{
|
||||
AuditResult,
|
||||
ChannelDesc,
|
||||
ChannelInfo,
|
||||
ChannelResult,
|
||||
ChannelStats,
|
||||
ChannelUpdate,
|
||||
EclairApi,
|
||||
GetInfoResult,
|
||||
IncomingPayment,
|
||||
IncomingPaymentStatus,
|
||||
InvoiceResult,
|
||||
NetworkFeesResult,
|
||||
NodeInfo,
|
||||
OutgoingPayment,
|
||||
OutgoingPaymentStatus,
|
||||
PaymentId,
|
||||
PeerInfo,
|
||||
UsableBalancesResult
|
||||
}
|
||||
import org.bitcoins.eclair.rpc.config.EclairInstance
|
||||
import org.bitcoins.eclair.rpc.json._
|
||||
import org.bitcoins.eclair.rpc.network.{NodeUri, PeerState}
|
||||
import org.bitcoins.rpc.serializers.JsonReaders._
|
||||
import org.bitcoins.rpc.util.AsyncUtil
|
||||
|
@ -38,6 +56,7 @@ import scala.concurrent.{ExecutionContext, Future, Promise}
|
|||
import scala.sys.process._
|
||||
import scala.util.{Failure, Properties, Success}
|
||||
import java.nio.file.NoSuchFileException
|
||||
|
||||
import org.bitcoins.core.util.FutureUtil
|
||||
|
||||
/**
|
||||
|
@ -367,8 +386,9 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
/** @inheritdoc */
|
||||
override def monitorInvoice(
|
||||
lnInvoice: LnInvoice,
|
||||
maxAttempts: Int = 60): Future[ReceivedPaymentResult] = {
|
||||
val p: Promise[ReceivedPaymentResult] = Promise[ReceivedPaymentResult]()
|
||||
interval: FiniteDuration = 1.second,
|
||||
maxAttempts: Int = 60): Future[IncomingPayment] = {
|
||||
val p: Promise[IncomingPayment] = Promise[IncomingPayment]()
|
||||
val attempts = new AtomicInteger(0)
|
||||
val runnable = new Runnable() {
|
||||
|
||||
|
@ -378,7 +398,8 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
//register callback that publishes a payment to our actor system's
|
||||
//event stream,
|
||||
receivedInfoF.foreach {
|
||||
case None =>
|
||||
case None |
|
||||
Some(IncomingPayment(_, _, _, IncomingPaymentStatus.Pending)) =>
|
||||
if (attempts.incrementAndGet() >= maxAttempts) {
|
||||
// too many tries to get info about a payment
|
||||
// either Eclair is down or the payment is still in PENDING state for some reason
|
||||
|
@ -401,7 +422,7 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
}
|
||||
}
|
||||
|
||||
val cancellable = system.scheduler.schedule(1.seconds, 1.seconds, runnable)
|
||||
val cancellable = system.scheduler.schedule(interval, interval, runnable)
|
||||
|
||||
p.future.map(_ => cancellable.cancel())
|
||||
|
||||
|
@ -412,59 +433,69 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
eclairCall[InvoiceResult]("parseinvoice", "invoice" -> invoice.toString)
|
||||
}
|
||||
|
||||
override def payInvoice(invoice: LnInvoice): Future[PaymentId] = {
|
||||
payInvoice(invoice, None, None, None, None)
|
||||
}
|
||||
override def payInvoice(invoice: LnInvoice): Future[PaymentId] =
|
||||
payInvoice(invoice, None, None, None, None, None)
|
||||
|
||||
override def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
amount: MilliSatoshis): Future[PaymentId] = {
|
||||
payInvoice(invoice, Some(amount), None, None, None)
|
||||
}
|
||||
amount: MilliSatoshis): Future[PaymentId] =
|
||||
payInvoice(invoice, Some(amount), None, None, None, None)
|
||||
|
||||
override def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
externalId: Option[String]): Future[PaymentId] =
|
||||
payInvoice(invoice, None, None, None, None, externalId)
|
||||
|
||||
override def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
amount: MilliSatoshis,
|
||||
externalId: Option[String]): Future[PaymentId] =
|
||||
payInvoice(invoice, Some(amount), None, None, None, externalId)
|
||||
|
||||
override def payInvoice(
|
||||
invoice: LnInvoice,
|
||||
amountMsat: Option[MilliSatoshis],
|
||||
maxAttempts: Option[Int],
|
||||
feeThresholdSat: Option[Satoshis],
|
||||
maxFeePct: Option[Int]): Future[PaymentId] = {
|
||||
maxFeePct: Option[Int],
|
||||
externalId: Option[String]): Future[PaymentId] = {
|
||||
val params = Seq(
|
||||
Some("invoice" -> invoice.toString),
|
||||
amountMsat.map(x => "amountMsat" -> x.toBigDecimal.toString),
|
||||
maxAttempts.map(x => "maxAttempts" -> x.toString),
|
||||
feeThresholdSat.map(x => "feeThresholdSat" -> x.toBigDecimal.toString),
|
||||
maxFeePct.map(x => "maxFeePct" -> x.toString)
|
||||
maxFeePct.map(x => "maxFeePct" -> x.toString),
|
||||
externalId.map(x => "externalId" -> x)
|
||||
).flatten
|
||||
|
||||
eclairCall[PaymentId]("payinvoice", params: _*)
|
||||
}
|
||||
|
||||
override def getReceivedInfo(
|
||||
paymentHash: Sha256Digest): Future[Option[ReceivedPaymentResult]] = {
|
||||
paymentHash: Sha256Digest): Future[Option[IncomingPayment]] = {
|
||||
|
||||
//eclair continues the tradition of not responding to things in json...
|
||||
//the failure case here is the string 'Not found'
|
||||
implicit val r: Reads[Option[ReceivedPaymentResult]] = Reads { js =>
|
||||
val result: JsResult[ReceivedPaymentResult] =
|
||||
js.validate[ReceivedPaymentResult]
|
||||
implicit val r: Reads[Option[IncomingPayment]] = Reads { js =>
|
||||
val result: JsResult[IncomingPayment] =
|
||||
js.validate[IncomingPayment]
|
||||
result match {
|
||||
case JsSuccess(result, _) => JsSuccess(Some(result))
|
||||
case _: JsError => JsSuccess(None)
|
||||
}
|
||||
}
|
||||
eclairCall[Option[ReceivedPaymentResult]](
|
||||
"getreceivedinfo",
|
||||
"paymentHash" -> paymentHash.hex)(r)
|
||||
eclairCall[Option[IncomingPayment]]("getreceivedinfo",
|
||||
"paymentHash" -> paymentHash.hex)(r)
|
||||
}
|
||||
|
||||
override def getSentInfo(
|
||||
paymentHash: Sha256Digest): Future[Vector[PaymentResult]] = {
|
||||
eclairCall[Vector[PaymentResult]]("getsentinfo",
|
||||
"paymentHash" -> paymentHash.hex)
|
||||
paymentHash: Sha256Digest): Future[Vector[OutgoingPayment]] = {
|
||||
eclairCall[Vector[OutgoingPayment]]("getsentinfo",
|
||||
"paymentHash" -> paymentHash.hex)
|
||||
}
|
||||
|
||||
override def getSentInfo(id: PaymentId): Future[Vector[PaymentResult]] = {
|
||||
eclairCall[Vector[PaymentResult]]("getsentinfo", "id" -> id.toString)
|
||||
override def getSentInfo(id: PaymentId): Future[Vector[OutgoingPayment]] = {
|
||||
eclairCall[Vector[OutgoingPayment]]("getsentinfo", "id" -> id.toString)
|
||||
}
|
||||
|
||||
override def sendToNode(
|
||||
|
@ -473,13 +504,15 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
paymentHash: Sha256Digest,
|
||||
maxAttempts: Option[Int],
|
||||
feeThresholdSat: Option[Satoshis],
|
||||
maxFeePct: Option[Int]): Future[PaymentId] = {
|
||||
maxFeePct: Option[Int],
|
||||
externalId: Option[String]): Future[PaymentId] = {
|
||||
val params = Seq("nodeId" -> nodeId.toString,
|
||||
"amountMsat" -> amountMsat.toBigDecimal.toString,
|
||||
"paymentHash" -> paymentHash.hex) ++ Seq(
|
||||
maxAttempts.map(x => "maxAttempts" -> x.toString),
|
||||
feeThresholdSat.map(x => "feeThresholdSat" -> x.toBigDecimal.toString),
|
||||
maxFeePct.map(x => "maxFeePct" -> x.toString)
|
||||
maxFeePct.map(x => "maxFeePct" -> x.toString),
|
||||
externalId.map(x => "externalId" -> x)
|
||||
).flatten
|
||||
|
||||
eclairCall[PaymentId]("sendtonode", params: _*)
|
||||
|
@ -489,14 +522,15 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
route: scala.collection.immutable.Seq[NodeId],
|
||||
amountMsat: MilliSatoshis,
|
||||
paymentHash: Sha256Digest,
|
||||
finalCltvExpiry: Long): Future[PaymentId] = {
|
||||
eclairCall[PaymentId](
|
||||
"sendtoroute",
|
||||
finalCltvExpiry: Long,
|
||||
externalId: Option[String]): Future[PaymentId] = {
|
||||
val params = Seq(
|
||||
"route" -> route.iterator.mkString(","),
|
||||
"amountMsat" -> amountMsat.toBigDecimal.toString,
|
||||
"paymentHash" -> paymentHash.hex,
|
||||
"finalCltvExpiry" -> finalCltvExpiry.toString
|
||||
)
|
||||
) ++ Seq(externalId.map(x => "externalId" -> x)).flatten
|
||||
eclairCall[PaymentId]("sendtoroute", params: _*)
|
||||
}
|
||||
|
||||
override def updateRelayFee(
|
||||
|
@ -599,7 +633,7 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
val errMsg =
|
||||
s"Error for command=${commandName} ${datadirMsg}, ${err.value.error}"
|
||||
logger.error(errMsg)
|
||||
throw new RuntimeException(errMsg)
|
||||
throw new RuntimeException(err.value.error)
|
||||
case _: JsError =>
|
||||
logger.error(JsError.toJson(res).toString())
|
||||
throw new IllegalArgumentException(
|
||||
|
@ -738,7 +772,7 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
/**
|
||||
* Pings eclair to see if a invoice has been paid
|
||||
* If the invoice has been paid or the payment has failed, we publish a
|
||||
* [[org.bitcoins.eclair.rpc.json.PaymentResult PaymentResult]]
|
||||
* [[OutgoingPayment]]
|
||||
* event to the [[akka.actor.ActorSystem ActorSystem]]'s
|
||||
* [[akka.event.EventStream ActorSystem.eventStream]]
|
||||
*
|
||||
|
@ -750,8 +784,8 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
override def monitorSentPayment(
|
||||
paymentId: PaymentId,
|
||||
interval: FiniteDuration,
|
||||
maxAttempts: Int): Future[PaymentResult] = {
|
||||
val p: Promise[PaymentResult] = Promise[PaymentResult]()
|
||||
maxAttempts: Int): Future[OutgoingPayment] = {
|
||||
val p: Promise[OutgoingPayment] = Promise[OutgoingPayment]()
|
||||
|
||||
val runnable = new Runnable() {
|
||||
|
||||
|
@ -779,9 +813,10 @@ class EclairRpcClient(val instance: EclairInstance, binary: Option[File] = None)
|
|||
} yield {
|
||||
results.foreach { result =>
|
||||
result.status match {
|
||||
case PaymentStatus.PENDING =>
|
||||
case OutgoingPaymentStatus.Pending =>
|
||||
//do nothing, while we wait for eclair to attempt to process
|
||||
case PaymentStatus.SUCCEEDED | PaymentStatus.FAILED =>
|
||||
case (_: OutgoingPaymentStatus.Succeeded |
|
||||
_: OutgoingPaymentStatus.Failed) =>
|
||||
// invoice has been succeeded or has failed, let's publish to event stream
|
||||
// so subscribers to the event stream can see that a payment
|
||||
// was received or failed
|
||||
|
@ -832,8 +867,8 @@ object EclairRpcClient {
|
|||
implicit system: ActorSystem) = new EclairRpcClient(instance, binary)
|
||||
|
||||
/** The current commit we support of Eclair */
|
||||
private[bitcoins] val commit = "6906ecb"
|
||||
private[bitcoins] val commit = "5ad3944"
|
||||
|
||||
/** The current version we support of Eclair */
|
||||
private[bitcoins] val version = "0.3.1"
|
||||
private[bitcoins] val version = "0.3.2"
|
||||
}
|
||||
|
|
|
@ -1,11 +1,41 @@
|
|||
package org.bitcoins.eclair.rpc.json
|
||||
package org.bitcoins.eclair.rpc.client
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.bitcoins.core.crypto.Sha256Digest
|
||||
import org.bitcoins.core.protocol.ln._
|
||||
import org.bitcoins.core.protocol.ln.channel.{ChannelState, FundedChannelId}
|
||||
import org.bitcoins.core.protocol.ln.currency.{MilliSatoshis, PicoBitcoins}
|
||||
import org.bitcoins.core.protocol.ln.fee.FeeProportionalMillionths
|
||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||
import org.bitcoins.core.protocol.ln._
|
||||
import org.bitcoins.eclair.rpc.api.{
|
||||
AuditResult,
|
||||
BaseChannelInfo,
|
||||
ChannelDesc,
|
||||
ChannelInfo,
|
||||
ChannelResult,
|
||||
ChannelStats,
|
||||
ChannelUpdate,
|
||||
GetInfoResult,
|
||||
Hop,
|
||||
IncomingPayment,
|
||||
IncomingPaymentStatus,
|
||||
InvoiceResult,
|
||||
NetworkFeesResult,
|
||||
NodeInfo,
|
||||
OpenChannelInfo,
|
||||
OutgoingPayment,
|
||||
OutgoingPaymentStatus,
|
||||
PaymentFailure,
|
||||
PaymentId,
|
||||
PaymentRequest,
|
||||
PeerInfo,
|
||||
ReceivedPayment,
|
||||
RelayedPayment,
|
||||
SentPayment,
|
||||
UsableBalancesResult,
|
||||
WebSocketEvent
|
||||
}
|
||||
import org.bitcoins.eclair.rpc.network.PeerState
|
||||
import org.bitcoins.rpc.serializers.SerializerUtil
|
||||
import play.api.libs.json._
|
||||
|
@ -139,9 +169,9 @@ object JsonReaders {
|
|||
.validate[ShortChannelId]
|
||||
channelId <- (jsValue \ "channelId").validate[FundedChannelId]
|
||||
state <- (jsValue \ "state").validate[ChannelState.NORMAL.type]
|
||||
remoteMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocalMsat")
|
||||
remoteMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toRemote")
|
||||
.validate[MilliSatoshis]
|
||||
localMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocalMsat")
|
||||
localMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
|
||||
.validate[MilliSatoshis]
|
||||
|
||||
} yield OpenChannelInfo(nodeId = nodeId,
|
||||
|
@ -157,9 +187,9 @@ object JsonReaders {
|
|||
nodeId <- (jsValue \ "nodeId").validate[NodeId]
|
||||
channelId <- (jsValue \ "channelId").validate[FundedChannelId]
|
||||
state <- (jsValue \ "state").validate[ChannelState]
|
||||
remoteMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toRemoteMsat")
|
||||
remoteMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toRemote")
|
||||
.validate[MilliSatoshis]
|
||||
localMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocalMsat")
|
||||
localMsat <- (jsValue \ "data" \ "commitments" \ "localCommit" \ "spec" \ "toLocal")
|
||||
.validate[MilliSatoshis]
|
||||
|
||||
} yield BaseChannelInfo(nodeId = nodeId,
|
||||
|
@ -184,16 +214,8 @@ object JsonReaders {
|
|||
Json.reads[ChannelUpdate]
|
||||
}
|
||||
|
||||
implicit val paymentRequestReads: Reads[PaymentRequest] = {
|
||||
Json.reads[PaymentRequest]
|
||||
}
|
||||
|
||||
implicit val paymentIdReads: Reads[PaymentId] = Reads { jsValue =>
|
||||
SerializerUtil.processJsString(PaymentId.apply)(jsValue)
|
||||
}
|
||||
|
||||
implicit val paymentStatusReads: Reads[PaymentStatus] = Reads { jsValue =>
|
||||
SerializerUtil.processJsString(PaymentStatus.apply)(jsValue)
|
||||
SerializerUtil.processJsString(s => PaymentId(UUID.fromString(s)))(jsValue)
|
||||
}
|
||||
|
||||
implicit val finiteDurationReads: Reads[FiniteDuration] =
|
||||
|
@ -201,11 +223,63 @@ object JsonReaders {
|
|||
SerializerUtil.processJsNumberBigInt(_.longValue.millis)(js)
|
||||
}
|
||||
|
||||
implicit val paymentSucceededReads: Reads[PaymentResult] =
|
||||
Json.reads[PaymentResult]
|
||||
implicit val paymentReceivedReads: Reads[IncomingPaymentStatus.Received] =
|
||||
Json.reads[IncomingPaymentStatus.Received]
|
||||
implicit val hopReads: Reads[Hop] =
|
||||
Json.reads[Hop]
|
||||
implicit val paymentSentReads: Reads[OutgoingPaymentStatus.Succeeded] =
|
||||
Json.reads[OutgoingPaymentStatus.Succeeded]
|
||||
implicit val paymentFailureTypeReads: Reads[PaymentFailure.Type] = Reads {
|
||||
jsValue =>
|
||||
(jsValue \ "type")
|
||||
.validate[String]
|
||||
.flatMap { s =>
|
||||
s.toLowerCase match {
|
||||
case "local" => JsSuccess(PaymentFailure.Local)
|
||||
case "remote" => JsSuccess(PaymentFailure.Remote)
|
||||
case "unreadableremote" =>
|
||||
JsSuccess(PaymentFailure.UnreadableRemote)
|
||||
case _ =>
|
||||
throw new RuntimeException(s"Unknown payment failure type `$s`")
|
||||
}
|
||||
}
|
||||
}
|
||||
implicit val paymentFailureReads: Reads[PaymentFailure] =
|
||||
Json.reads[PaymentFailure]
|
||||
implicit val paymentFailedReads: Reads[OutgoingPaymentStatus.Failed] =
|
||||
Json.reads[OutgoingPaymentStatus.Failed]
|
||||
|
||||
implicit val receivedPaymentResultReads: Reads[ReceivedPaymentResult] =
|
||||
Json.reads[ReceivedPaymentResult]
|
||||
implicit val outgoingPaymentStatusReads: Reads[OutgoingPaymentStatus] =
|
||||
Reads { jsValue =>
|
||||
(jsValue \ "type")
|
||||
.validate[String]
|
||||
.flatMap {
|
||||
case "pending" => JsSuccess(OutgoingPaymentStatus.Pending)
|
||||
case "sent" => jsValue.validate[OutgoingPaymentStatus.Succeeded]
|
||||
case "failed" => jsValue.validate[OutgoingPaymentStatus.Failed]
|
||||
}
|
||||
}
|
||||
|
||||
implicit val incomingPaymentStatusReads: Reads[IncomingPaymentStatus] =
|
||||
Reads { jsValue =>
|
||||
(jsValue \ "type")
|
||||
.validate[String]
|
||||
.flatMap {
|
||||
case "pending" => JsSuccess(IncomingPaymentStatus.Pending)
|
||||
case "expired" => JsSuccess(IncomingPaymentStatus.Expired)
|
||||
case "received" => jsValue.validate[IncomingPaymentStatus.Received]
|
||||
}
|
||||
}
|
||||
|
||||
implicit val paymentRequestReads: Reads[PaymentRequest] = {
|
||||
Json.reads[PaymentRequest]
|
||||
}
|
||||
|
||||
implicit val paymentSucceededReads: Reads[OutgoingPayment] =
|
||||
Json.reads[OutgoingPayment]
|
||||
|
||||
implicit val receivedPaymentResultReads: Reads[IncomingPayment] =
|
||||
Json.reads[IncomingPayment]
|
||||
|
||||
implicit val channelResultReads: Reads[ChannelResult] = Reads { js =>
|
||||
for {
|
||||
|
@ -238,8 +312,12 @@ object JsonReaders {
|
|||
JsError(s"Invalid type on refund invoice: $bad, expected JsString")
|
||||
}
|
||||
|
||||
implicit val receivedPaymentPartReads: Reads[ReceivedPayment.Part] =
|
||||
Json.reads[ReceivedPayment.Part]
|
||||
implicit val receivedPaymentReads: Reads[ReceivedPayment] =
|
||||
Json.reads[ReceivedPayment]
|
||||
implicit val sentPaymentPartReads: Reads[SentPayment.Part] =
|
||||
Json.reads[SentPayment.Part]
|
||||
implicit val sentPaymentReads: Reads[SentPayment] = Json.reads[SentPayment]
|
||||
implicit val relayedPaymentReads: Reads[RelayedPayment] =
|
||||
Json.reads[RelayedPayment]
|
||||
|
@ -254,21 +332,21 @@ object JsonReaders {
|
|||
implicit val usableBalancesResultReads: Reads[UsableBalancesResult] =
|
||||
Json.reads[UsableBalancesResult]
|
||||
|
||||
import WebSocketEvent._
|
||||
implicit val paymentRelayedEventReads: Reads[WebSocketEvent.PaymentRelayed] =
|
||||
Json.reads[WebSocketEvent.PaymentRelayed]
|
||||
|
||||
implicit val paymentRelayedEventReads: Reads[PaymentRelayed] =
|
||||
Json.reads[PaymentRelayed]
|
||||
implicit val paymentReceivedEventReads: Reads[
|
||||
WebSocketEvent.PaymentReceived] =
|
||||
Json.reads[WebSocketEvent.PaymentReceived]
|
||||
|
||||
implicit val paymentReceivedEventReads: Reads[PaymentReceived] =
|
||||
Json.reads[PaymentReceived]
|
||||
implicit val paymentFailedEventReads: Reads[WebSocketEvent.PaymentFailed] =
|
||||
Json.reads[WebSocketEvent.PaymentFailed]
|
||||
|
||||
implicit val paymentFailedEventReads: Reads[PaymentFailed] =
|
||||
Json.reads[PaymentFailed]
|
||||
implicit val paymentSentEventReads: Reads[WebSocketEvent.PaymentSent] =
|
||||
Json.reads[WebSocketEvent.PaymentSent]
|
||||
|
||||
implicit val paymentSentEventReads: Reads[PaymentSent] =
|
||||
Json.reads[PaymentSent]
|
||||
|
||||
implicit val paymentSettlingOnchainEventReads: Reads[PaymentSettlingOnchain] =
|
||||
Json.reads[PaymentSettlingOnchain]
|
||||
implicit val paymentSettlingOnchainEventReads: Reads[
|
||||
WebSocketEvent.PaymentSettlingOnchain] =
|
||||
Json.reads[WebSocketEvent.PaymentSettlingOnchain]
|
||||
|
||||
}
|
|
@ -15,10 +15,14 @@ import org.bitcoins.core.protocol.ln.channel.{
|
|||
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
||||
import org.bitcoins.core.protocol.ln.node.NodeId
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.eclair.rpc.api.EclairApi
|
||||
import org.bitcoins.eclair.rpc.api.{
|
||||
EclairApi,
|
||||
OutgoingPayment,
|
||||
OutgoingPaymentStatus,
|
||||
PaymentId
|
||||
}
|
||||
import org.bitcoins.eclair.rpc.client.EclairRpcClient
|
||||
import org.bitcoins.eclair.rpc.config.EclairInstance
|
||||
import org.bitcoins.eclair.rpc.json.{PaymentId, PaymentStatus}
|
||||
import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion}
|
||||
import org.bitcoins.rpc.config.BitcoindInstance
|
||||
import org.bitcoins.rpc.util.RpcUtil
|
||||
|
@ -31,9 +35,11 @@ import scala.util.{Failure, Success}
|
|||
import org.bitcoins.rpc.config.BitcoindAuthCredentials
|
||||
import java.nio.file.Files
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
/**
|
||||
* @define nodeLinkDoc
|
||||
* Creates two Eclair nodes that are connected in the following manner:
|
||||
* Creates four Eclair nodes that are connected in the following manner:
|
||||
* {{{
|
||||
* node1 <-> node2 <-> node3 <-> node4
|
||||
* }}}
|
||||
|
@ -121,6 +127,7 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
|
|||
"eclair.max-htlc-value-in-flight-msat" -> 100000000000L,
|
||||
"eclair.router.broadcast-interval" -> "2 second",
|
||||
"eclair.auto-reconnect" -> false,
|
||||
"eclair.to-remote-delay-blocks" -> 144,
|
||||
"eclair.db.driver" -> "org.sqlite.JDBC",
|
||||
"eclair.db.regtest.url" -> "jdbc:sqlite:regtest/",
|
||||
"eclair.max-payment-fee" -> 10, // avoid complaints about too high fees
|
||||
|
@ -251,63 +258,56 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
|
|||
client: EclairApi,
|
||||
paymentId: PaymentId,
|
||||
duration: FiniteDuration = 1.second,
|
||||
maxTries: Int = 50,
|
||||
maxTries: Int = 60,
|
||||
failFast: Boolean = true)(implicit system: ActorSystem): Future[Unit] = {
|
||||
awaitUntilPaymentStatus(client,
|
||||
paymentId,
|
||||
PaymentStatus.SUCCEEDED,
|
||||
duration,
|
||||
maxTries,
|
||||
failFast)
|
||||
awaitUntilPaymentStatus[OutgoingPaymentStatus.Succeeded](client,
|
||||
paymentId,
|
||||
duration,
|
||||
maxTries,
|
||||
failFast)
|
||||
}
|
||||
|
||||
def awaitUntilPaymentFailed(
|
||||
client: EclairApi,
|
||||
paymentId: PaymentId,
|
||||
duration: FiniteDuration = 1.second,
|
||||
maxTries: Int = 50,
|
||||
maxTries: Int = 60,
|
||||
failFast: Boolean = false)(implicit system: ActorSystem): Future[Unit] = {
|
||||
awaitUntilPaymentStatus(client,
|
||||
paymentId,
|
||||
PaymentStatus.FAILED,
|
||||
duration,
|
||||
maxTries,
|
||||
failFast)
|
||||
awaitUntilPaymentStatus[OutgoingPaymentStatus.Failed](client,
|
||||
paymentId,
|
||||
duration,
|
||||
maxTries,
|
||||
failFast)
|
||||
}
|
||||
|
||||
def awaitUntilPaymentPending(
|
||||
private def awaitUntilPaymentStatus[T <: OutgoingPaymentStatus](
|
||||
client: EclairApi,
|
||||
paymentId: PaymentId,
|
||||
duration: FiniteDuration = 1.second,
|
||||
maxTries: Int = 50,
|
||||
failFast: Boolean = true)(implicit system: ActorSystem): Future[Unit] = {
|
||||
awaitUntilPaymentStatus(client,
|
||||
paymentId,
|
||||
PaymentStatus.PENDING,
|
||||
duration,
|
||||
maxTries,
|
||||
failFast)
|
||||
}
|
||||
|
||||
private def awaitUntilPaymentStatus(
|
||||
client: EclairApi,
|
||||
paymentId: PaymentId,
|
||||
state: PaymentStatus,
|
||||
duration: FiniteDuration,
|
||||
maxTries: Int,
|
||||
failFast: Boolean)(implicit system: ActorSystem): Future[Unit] = {
|
||||
logger.debug(s"Awaiting payment ${paymentId} to enter ${state} state")
|
||||
failFast: Boolean)(
|
||||
implicit system: ActorSystem,
|
||||
tag: ClassTag[T]): Future[Unit] = {
|
||||
logger.debug(
|
||||
s"Awaiting payment ${paymentId} to enter ${tag.runtimeClass.getName} state")
|
||||
|
||||
def isState(): Future[Boolean] = {
|
||||
def isFailed(status: OutgoingPaymentStatus): Boolean = status match {
|
||||
case _: OutgoingPaymentStatus.Failed => true
|
||||
case _: OutgoingPaymentStatus => false
|
||||
}
|
||||
|
||||
val sentInfoF = client.getSentInfo(paymentId)
|
||||
def isInState(): Future[Boolean] = {
|
||||
|
||||
val sentInfoF: Future[Vector[OutgoingPayment]] =
|
||||
client.getSentInfo(paymentId)
|
||||
sentInfoF.map { payment =>
|
||||
if (failFast && payment.exists(_.status == PaymentStatus.FAILED)) {
|
||||
if (failFast && payment.exists(result => isFailed(result.status))) {
|
||||
throw new RuntimeException(s"Payment ${paymentId} has failed")
|
||||
}
|
||||
if (!payment.exists(_.status == state)) {
|
||||
if (!payment.exists(
|
||||
result => tag.runtimeClass == result.status.getClass)) {
|
||||
logger.trace(
|
||||
s"Payment ${paymentId} has not entered ${state} yet. Currently in ${payment.map(_.status).mkString(",")}")
|
||||
s"Payment ${paymentId} has not entered ${tag.runtimeClass.getName} yet. Currently in ${payment.map(_.status).mkString(",")}")
|
||||
false
|
||||
} else {
|
||||
true
|
||||
|
@ -315,7 +315,7 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
|
|||
}(system.dispatcher)
|
||||
}
|
||||
|
||||
TestAsyncUtil.retryUntilSatisfiedF(conditionF = () => isState(),
|
||||
TestAsyncUtil.retryUntilSatisfiedF(conditionF = () => isInState(),
|
||||
duration = duration,
|
||||
maxTries = maxTries)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue