Skip to content

Commit

Permalink
Support for Eclair 0.3.3 (#1097)
Browse files Browse the repository at this point in the history
  • Loading branch information
rorp committed Feb 12, 2020
1 parent b259185 commit 06070cc
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 150 deletions.
@@ -1,6 +1,7 @@
package org.bitcoins.eclair.rpc

import java.nio.file.Files
import java.time.Instant

import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt64
Expand Down Expand Up @@ -31,7 +32,6 @@ import org.scalatest.Assertion

import scala.concurrent._
import scala.concurrent.duration.{DurationInt, _}
import scala.reflect.ClassTag

class EclairRpcClientTest extends BitcoinSAsyncTest {

Expand Down Expand Up @@ -153,6 +153,10 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
invoice <- client4.createInvoice("foo", 1000.msats)
info <- client4.getInfo
_ = assert(info.nodeId == invoice.nodeId)
_ = assert(
info.publicAddresses
.map(_.getHostString)
.exists(_.endsWith(".onion")))
route <- client1.findRoute(invoice, None)
} yield {
route.size == 4
Expand Down Expand Up @@ -533,12 +537,20 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
preimage = PaymentPreimage.random
invoice <- otherClient.createInvoice("foo", amt, preimage)
route <- client.findRoute(otherClientNodeId, amt)
paymentId <- client.sendToRoute(route,
amt,
invoice.lnTags.paymentHash.hash,
144,
Some("ext_id"))
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
result <- client.sendToRoute(invoice,
route,
amt,
invoice.lnTags.paymentHash.hash,
finalCltvExpiry = 144,
recipientAmountMsat = None,
parentId = None,
externalId = Some("ext_id"))
_ <- EclairRpcTestUtil
.awaitUntilIncomingPaymentStatus[IncomingPaymentStatus.Received](
otherClient,
invoice.lnTags.paymentHash.hash)
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client,
result.parentId)
succeeded <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
} yield {
assert(otherClientNodeId == invoice.nodeId)
Expand Down Expand Up @@ -935,13 +947,11 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
val client1F = connectedClientsF.map(_.c1)
val client2F = connectedClientsF.map(_.c2)

// 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 { _ =>
sendPaymentsF.flatMap { _ =>
executeSpecificClients(clientF = client1F,
otherClientF = client2F,
test = getChannelUpdates)
})
}

}

Expand Down Expand Up @@ -1034,31 +1044,14 @@ 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 {
// 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 {
for {
client <- clientF
nodeInfo <- client.getInfo
updates <- client.allUpdates(nodeInfo.nodeId)
} yield {
assert(updates.nonEmpty)
})
}
}

it must "receive gossip messages about channel updates for nodes we do not have a direct channel with" in {
Expand Down Expand Up @@ -1098,14 +1091,11 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {

//the second client and fourth client aren't directly connected
//which is why i am choosing to use them for this test
// 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
))
executeSpecificClients(
clientF = secondClientF,
otherClientF = fourthClientF,
test = gossipFromPeerWithNoChannel
)
}

it should "detect what network we are on" in {
Expand Down Expand Up @@ -1152,12 +1142,15 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
}
}

it should "list invoice" in {
it should "list invoices" in {
for {
c <- clientF
res <- c.listInvoices(from = None, to = None)
res <- c.listInvoices(from = None, to = Some(Instant.now()))
i <- c.createInvoice(description = "abc")
pending <- c.listPendingInvoices(from = None, to = None)
} yield {
assert(res.nonEmpty)
assert(pending.exists(_ == i))
}
}

Expand All @@ -1170,6 +1163,15 @@ class EclairRpcClientTest extends BitcoinSAsyncTest {
}
}

it should "get new address" in {
for {
c <- clientF
res <- c.getNewAddress()
} yield {
assert(res.toString.nonEmpty)
}
}

it should "disconnect node" in {
for {
c1 <- clientF
Expand Down
4 changes: 2 additions & 2 deletions eclair-rpc/eclair-rpc.sbt
Expand Up @@ -19,8 +19,8 @@ TaskKeys.downloadEclair := {
Files.createDirectories(binaryDir)
}

val version = "0.3.2"
val commit = "5ad3944"
val version = "0.3.3"
val commit = "12ac145"

logger.debug(s"(Maybe) downloading Eclair binaries for version: $version")

Expand Down
@@ -1,18 +1,21 @@
package org.bitcoins.eclair.rpc.api

import java.net.InetSocketAddress
import java.time.Instant

import org.bitcoins.core.crypto.Sha256Digest
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.protocol.Address
import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId}
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.protocol.ln.{
LnInvoice,
LnParams,
PaymentPreimage,
ShortChannelId
}
import org.bitcoins.core.protocol.ln.channel.{ChannelId, FundedChannelId}
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.protocol.{Address, BitcoinAddress}
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.eclair.rpc.network.NodeUri

Expand Down Expand Up @@ -42,9 +45,7 @@ trait EclairApi {
* @param from start timestamp
* @param to end timestamp
*/
def audit(
from: Option[FiniteDuration],
to: Option[FiniteDuration]): Future[AuditResult]
def audit(from: Option[Instant], to: Option[Instant]): Future[AuditResult]

def allUpdates(): Future[Vector[ChannelUpdate]]

Expand All @@ -67,6 +68,10 @@ trait EclairApi {

def connect(nodeId: NodeId, host: String, port: Int): Future[Unit]

def connect(nodeId: NodeId, addr: InetSocketAddress): Future[Unit]

def connect(nodeId: NodeId): Future[Unit]

def disconnect(nodeId: NodeId): Future[Unit]

def close(id: ChannelId, spk: ScriptPubKey): Future[Unit]
Expand Down Expand Up @@ -106,10 +111,11 @@ trait EclairApi {

def open(
nodeId: NodeId,
fundingSatoshis: CurrencyUnit,
funding: CurrencyUnit,
pushMsat: Option[MilliSatoshis],
feerateSatPerByte: Option[SatoshisPerByte],
channelFlags: Option[Byte]): Future[FundedChannelId]
channelFlags: Option[Byte],
openTimeout: Option[FiniteDuration]): Future[FundedChannelId]

/** The network that this [[org.bitcoins.eclair.rpc.api.EclairApi EclairApi]] is
* running on. This is not available directly from the eclair api, but is a very
Expand Down Expand Up @@ -171,8 +177,12 @@ trait EclairApi {
def getInvoice(paymentHash: Sha256Digest): Future[LnInvoice]

def listInvoices(
from: Option[FiniteDuration],
to: Option[FiniteDuration]): Future[Vector[LnInvoice]]
from: Option[Instant],
to: Option[Instant]): Future[Vector[LnInvoice]]

def listPendingInvoices(
from: Option[Instant],
to: Option[Instant]): Future[Vector[LnInvoice]]

def parseInvoice(invoice: LnInvoice): Future[InvoiceResult]

Expand Down Expand Up @@ -238,9 +248,7 @@ trait EclairApi {
def getReceivedInfo(
paymentHash: Sha256Digest): Future[Option[IncomingPayment]]

def getReceivedInfo(invoice: LnInvoice): Future[Option[IncomingPayment]] = {
getReceivedInfo(invoice.lnTags.paymentHash.hash)
}
def getReceivedInfo(invoice: LnInvoice): Future[Option[IncomingPayment]]

def sendToNode(
nodeId: NodeId,
Expand All @@ -255,14 +263,19 @@ trait EclairApi {
* Documented by not implemented in Eclair
*/
def sendToRoute(
invoice: LnInvoice,
route: scala.collection.immutable.Seq[NodeId],
amountMsat: MilliSatoshis,
paymentHash: Sha256Digest,
finalCltvExpiry: Long,
externalId: Option[String]): Future[PaymentId]
recipientAmountMsat: Option[MilliSatoshis],
parentId: Option[PaymentId],
externalId: Option[String]): Future[SendToRouteResult]

def usableBalances(): Future[Vector[UsableBalancesResult]]

/** Connects to the Eclair web socket end point and passes [[WebSocketEvent]]s to the given [[eventHandler]] */
def connectToWebSocket(eventHandler: WebSocketEvent => Unit): Future[Unit]

def getNewAddress(): Future[BitcoinAddress]
}
@@ -1,5 +1,6 @@
package org.bitcoins.eclair.rpc.api

import java.net.InetSocketAddress
import java.util.UUID

import org.bitcoins.core.crypto.{
Expand Down Expand Up @@ -30,7 +31,7 @@ case class GetInfoResult(
alias: String,
chainHash: DoubleSha256Digest,
blockHeight: Long,
publicAddresses: Seq[String])
publicAddresses: Seq[InetSocketAddress])

case class PeerInfo(
nodeId: NodeId,
Expand Down Expand Up @@ -87,7 +88,7 @@ case class NodeInfo(
nodeId: NodeId,
rgbColor: String,
alias: String,
addresses: Vector[String])
addresses: Vector[InetSocketAddress])

case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)

Expand Down Expand Up @@ -148,6 +149,8 @@ case class SentPayment(
id: PaymentId,
paymentHash: Sha256Digest,
paymentPreimage: PaymentPreimage,
recipientAmount: MilliSatoshis,
recipientNodeId: NodeId,
parts: Vector[SentPayment.Part]
)

Expand Down Expand Up @@ -201,6 +204,8 @@ case class PaymentId(value: UUID) {
override def toString: String = value.toString
}

case class SendToRouteResult(paymentId: PaymentId, parentId: PaymentId)

case class PaymentRequest(
prefix: LnHumanReadablePart,
timestamp: FiniteDuration, //seconds
Expand All @@ -211,13 +216,32 @@ case class PaymentRequest(
expiry: FiniteDuration, //seconds
amount: Option[MilliSatoshis])

sealed trait PaymentType

object PaymentType {

case object Standard extends PaymentType
case object SwapIn extends PaymentType
case object SwapOut extends PaymentType

def fromString(str: String): PaymentType = str match {
case "Standard" => Standard
case "SwapIn" => SwapIn
case "SwapOut" => SwapOut
case _ => throw new RuntimeException(s"Unknown payment type `$str`")
}

}

case class OutgoingPayment(
id: PaymentId,
parentId: PaymentId,
externalId: Option[String],
paymentHash: Sha256Digest,
paymentType: PaymentType,
amount: MilliSatoshis,
targetNodeId: NodeId,
recipientAmount: MilliSatoshis,
recipientNodeId: NodeId,
createdAt: FiniteDuration, //milliseconds
paymentRequest: Option[PaymentRequest],
status: OutgoingPaymentStatus)
Expand Down

0 comments on commit 06070cc

Please sign in to comment.