findroute RPC call tests/types (#301)

* Improved toString methdos

* Eclair RPC for findroute

* ConfigUtil for getStringOrElse

* Scaladoc

* Version bump

* Typed ChannelDesc a and b fields
This commit is contained in:
Torkel Rogstad 2019-01-23 15:45:46 +01:00 committed by Chris Stewart
parent 308384b2af
commit e068382701
11 changed files with 309 additions and 29 deletions

View File

@ -7,6 +7,15 @@ import scodec.bits.ByteVector
case class ShortChannelId(u64: UInt64) extends NetworkElement {
override def bytes: ByteVector = u64.bytes
/**
* Output example:
* {{{
* > ShortChannelId.fromHex("db0000010000")
* ShortChannelId(db0000010000)
* }}}
*/
override def toString: String = s"ShortChannelId(${hex.drop(4)})"
}
object ShortChannelId extends Factory[ShortChannelId] {

View File

@ -9,14 +9,24 @@ import scala.math.BigDecimal.RoundingMode
/**
* The common currency unit used in the
* LN protocol for updating HTLCs. See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc BOLT2]]
* LN protocol for updating HTLCs.
*
* @see [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc BOLT2]]
*/
sealed abstract class MilliSatoshis extends NetworkElement {
require(toBigInt >= 0, s"Millisatoshis cannot be negative, got $toBigInt")
protected def underlying: BigInt
/**
* Output example:
* {{{
* > MilliSatoshis(10)
* 10 msat
* }}}
*/
override def toString: String = s"$toLong msat"
def toBigInt: BigInt = underlying
def toLong: Long = toBigInt.bigInteger.longValueExact

View File

@ -43,11 +43,9 @@ trait EclairApi {
def close(id: ChannelId, spk: ScriptPubKey): Future[String]
def findRoute(nodeId: NodeId): Future[Vector[String]]
def findRoute(nodeId: NodeId): Future[Vector[NodeId]]
def findRoute(invoice: LnInvoice): Future[Vector[String]] = {
findRoute(nodeId = invoice.nodeId)
}
def findRoute(invoice: LnInvoice): Future[Vector[NodeId]]
def forceClose(id: ChannelId): Future[String]

View File

@ -127,8 +127,12 @@ class EclairRpcClient(val instance: EclairInstance)(
eclairCall[String]("connect", List(JsString(uri.toString)))
}
override def findRoute(nodeId: NodeId): Future[Vector[String]] = {
eclairCall[Vector[String]]("findroute", List(JsString(nodeId.hex)))
override def findRoute(nodeId: NodeId): Future[Vector[NodeId]] = {
eclairCall[Vector[NodeId]]("findroute", List(JsString(nodeId.hex)))
}
override def findRoute(invoice: LnInvoice): Future[Vector[NodeId]] = {
eclairCall[Vector[NodeId]]("findroute", List(JsString(invoice.toString)))
}
override def forceClose(channelId: ChannelId): Future[String] = {

View File

@ -6,6 +6,13 @@ import scala.util.{Failure, Success, Try}
object ConfigUtil {
def getStringOrElse(config: Config, path: String, default: String): String = {
Try(config.getString(path)) match {
case Success(str) => str
case Failure(_) => default
}
}
def getIntOrElse(config: Config, path: String, default: Int): Int = {
Try(config.getInt(path)) match {
case Success(num) => num

View File

@ -52,14 +52,21 @@ object EclairInstance {
* [[org.bitcoins.eclair.rpc.config.EclairInstance EclairInstance]]
*/
def fromConfig(config: Config): EclairInstance = {
val chain = config.getString("eclair.chain")
val chain = ConfigUtil.getStringOrElse(config, "eclair.chain", "testnet")
// default conf: https://github.com/ACINQ/eclair/blob/master/eclair-core/src/main/resources/reference.conf
val serverBindingIp =
ConfigUtil.getStringOrElse(config, "eclair.server.binding-ip", "0.0.0.0")
val serverBindingIp = config.getString("eclair.server.binding-ip")
val serverPort = ConfigUtil.getIntOrElse(config,
"eclair.server.port",
LnPolicy.DEFAULT_LN_P2P_PORT)
val rpcHost = config.getString("eclair.api.binding-ip")
// default conf: https://github.com/ACINQ/eclair/blob/master/eclair-core/src/main/resources/reference.conf
val rpcHost =
ConfigUtil.getStringOrElse(config, "eclair.api.binding-ip", "127.0.0.1")
val rpcPort = ConfigUtil.getIntOrElse(config,
"eclair.api.port",
LnPolicy.DEFAULT_ECLAIR_API_PORT)

View File

@ -82,7 +82,7 @@ case class NodeInfo(
alias: String,
addresses: Vector[String])
case class ChannelDesc(shortChannelId: ShortChannelId, a: String, b: String)
case class ChannelDesc(shortChannelId: ShortChannelId, a: NodeId, b: NodeId)
case class ChannelUpdate(
signature: ECDigitalSignature,

View File

@ -15,9 +15,11 @@ import org.bitcoins.eclair.rpc.config.{EclairAuthCredentials, EclairInstance}
import org.bitcoins.eclair.rpc.json._
import org.bitcoins.rpc.BitcoindRpcTestUtil
import org.bitcoins.rpc.client.BitcoindRpcClient
import org.bitcoins.rpc.util.AsyncUtil
import org.scalatest.{Assertion, AsyncFlatSpec, BeforeAndAfterAll}
import org.slf4j.Logger
import scala.collection.immutable.VectorBuilder
import scala.concurrent._
import scala.concurrent.duration.DurationInt
@ -33,8 +35,20 @@ class EclairRpcClientTest extends AsyncFlatSpec with BeforeAndAfterAll {
val bitcoindRpcClient: BitcoindRpcClient =
BitcoindRpcTestUtil.startedBitcoindRpcClient()
val (client, otherClient) =
EclairRpcTestUtil.createNodePair(Some(bitcoindRpcClient))
lazy val (client, otherClient) = {
val (c1, c2) = EclairRpcTestUtil.createNodePair(Some(bitcoindRpcClient))
clients += c1
clients += c2
(c1, c2)
}
private val clients =
Vector.newBuilder[EclairRpcClient]
override def beforeAll(): Unit = {
// make sure we have enough money open channels
bitcoindRpcClient.generate(200)
}
behavior of "RpcClient"
@ -320,6 +334,51 @@ class EclairRpcClientTest extends AsyncFlatSpec with BeforeAndAfterAll {
}
}
it should "get a route to a node ID" in {
val EclairNodes4(first, second, third, fourth) =
EclairRpcTestUtil.createNodeLink(bitcoindRpcClient)
clients ++= List(first, second, third, fourth)
val hasRoute = () => {
fourth.getInfo
.flatMap(info => first.findRoute(info.nodeId))
.map(route => route.length == 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 = 1000.millis, maxTries = 10)
succeed
}
it should "get a route to an invoice" in {
val EclairNodes4(first, second, third, fourth) =
EclairRpcTestUtil.createNodeLink(bitcoindRpcClient)
clients ++= List(first, second, third, fourth)
val hasRoute = () => {
fourth
.receive("foo")
.flatMap(invoice => first.findRoute(invoice))
.map(route => route.length == 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 = 1000.millis, maxTries = 10)
succeed
}
// We spawn fresh clients in this test because the test
// needs nodes with activity both related and not related
// to them
@ -329,6 +388,11 @@ class EclairRpcClientTest extends AsyncFlatSpec with BeforeAndAfterAll {
val (thirdFreshClient, fourthFreshClient) =
EclairRpcTestUtil.createNodePair(Some(bitcoindRpcClient))
clients ++= List(firstFreshClient,
secondFreshClient,
thirdFreshClient,
fourthFreshClient)
EclairRpcTestUtil.connectLNNodes(firstFreshClient, thirdFreshClient)
EclairRpcTestUtil.connectLNNodes(firstFreshClient, fourthFreshClient)
@ -371,11 +435,6 @@ class EclairRpcClientTest extends AsyncFlatSpec with BeforeAndAfterAll {
assert(ourChannelUpdates.forall(updateIsInChannels(ourOpenChannels)))
assert(allChannelUpdates.exists(updateIsNotInChannels(ourOpenChannels)))
List(firstFreshClient,
secondFreshClient,
thirdFreshClient,
fourthFreshClient).foreach(EclairRpcTestUtil.shutdown)
succeed
}
}
@ -440,7 +499,7 @@ class EclairRpcClientTest extends AsyncFlatSpec with BeforeAndAfterAll {
}
override def afterAll(): Unit = {
List(client, otherClient).foreach(EclairRpcTestUtil.shutdown)
clients.result().foreach(EclairRpcTestUtil.shutdown)
TestKit.shutdownActorSystem(system)
}
}

View File

@ -0,0 +1,89 @@
package org.bitcoins.eclair.rpc
import akka.actor.ActorSystem
import akka.testkit.TestKit
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.rpc.BitcoindRpcTestUtil
import org.scalatest.{AsyncFlatSpec, BeforeAndAfterAll}
class EclairRpcTestUtilTest extends AsyncFlatSpec with BeforeAndAfterAll {
private implicit val actorSystem: ActorSystem =
ActorSystem.create("EclairRpcTestUtilTest")
private val bitcoindRpc = BitcoindRpcTestUtil.startedBitcoindRpcClient()
private val clients =
Vector.newBuilder[EclairRpcClient]
override def beforeAll: Unit = {
bitcoindRpc.generate(200)
}
override def afterAll: Unit = {
clients.result().foreach(EclairRpcTestUtil.shutdown)
TestKit.shutdownActorSystem(actorSystem)
}
behavior of "EclairRpcTestUtilTest"
it must "spawn four nodes and create a P2P link between them" in {
val EclairNodes4(first, second, third, fourth) =
EclairRpcTestUtil.createNodeLink(bitcoindRpc)
clients ++= List(first, second, third, fourth)
for {
nodeInfoFirst <- first.getInfo
peersFirst <- first.getPeers
nodeInfoSecond <- second.getInfo
peersSecond <- second.getPeers
nodeInfoThird <- third.getInfo
peersThird <- third.getPeers
nodeInfoFourth <- fourth.getInfo
peersFourth <- fourth.getPeers
} yield {
assert(peersFirst.length == 1)
assert(peersFirst.exists(_.nodeId == nodeInfoSecond.nodeId))
assert(peersSecond.length == 2)
assert(peersSecond.exists(_.nodeId == nodeInfoFirst.nodeId))
assert(peersSecond.exists(_.nodeId == nodeInfoThird.nodeId))
assert(peersThird.length == 2)
assert(peersThird.exists(_.nodeId == nodeInfoSecond.nodeId))
assert(peersThird.exists(_.nodeId == nodeInfoFourth.nodeId))
assert(peersFourth.length == 1)
assert(peersFourth.exists(_.nodeId == nodeInfoThird.nodeId))
}
}
it must "spawn four nodes and create a channel link between them" in {
val EclairNodes4(first, second, third, fourth) =
EclairRpcTestUtil.createNodeLink(bitcoindRpc)
clients ++= List(first, second, third, fourth)
for {
nodeInfoFirst <- first.getInfo
channelsFirst <- first.channels
nodeInfoSecond <- second.getInfo
channelsSecond <- second.channels
nodeInfoThird <- third.getInfo
channelsThird <- third.channels
nodeInfoFourth <- fourth.getInfo
channelsFourth <- fourth.channels
} yield {
assert(channelsFirst.length == 1)
assert(channelsFirst.exists(_.nodeId == nodeInfoSecond.nodeId))
assert(channelsSecond.length == 2)
assert(channelsSecond.exists(_.nodeId == nodeInfoFirst.nodeId))
assert(channelsSecond.exists(_.nodeId == nodeInfoThird.nodeId))
assert(channelsThird.length == 2)
assert(channelsThird.exists(_.nodeId == nodeInfoSecond.nodeId))
assert(channelsThird.exists(_.nodeId == nodeInfoFourth.nodeId))
assert(channelsFourth.length == 1)
assert(channelsFourth.exists(_.nodeId == nodeInfoThird.nodeId))
}
}
}

View File

@ -17,7 +17,7 @@ object Deps {
val nativeLoaderV = "2.3.2"
val typesafeConfigV = "1.3.3"
val bitcoinsV = "0.0.4-SNAPSHOT"
val bitcoinsV = "0.0.4.1-SNAPSHOT"
}
object Compile {

View File

@ -6,8 +6,7 @@ import java.net.URI
import akka.actor.ActorSystem
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.number.Int64
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.protocol.ln.channel.{
ChannelId,
ChannelState,
@ -18,14 +17,25 @@ import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.eclair.rpc.client.EclairRpcClient
import org.bitcoins.eclair.rpc.config.EclairInstance
import org.bitcoins.eclair.rpc.json.PaymentResult
import org.bitcoins.rpc.BitcoindRpcTestUtil
import org.bitcoins.rpc.client.BitcoindRpcClient
import org.bitcoins.rpc.config.{BitcoindInstance, ZmqConfig}
import org.bitcoins.rpc.BitcoindRpcTestUtil
import org.bitcoins.rpc.util.RpcUtil
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, ExecutionContext, Future}
/**
* @define nodeLinkDoc
* Creates two Eclair nodes that are connected in the following manner:
* {{{
* node1 <-> node2 <-> node3 <-> node4
* }}}
*
* Each double sided arrow represents a P2P connection as well as a funded
* channel
*
*/
trait EclairRpcTestUtil extends BitcoinSLogger {
import collection.JavaConverters._
@ -215,8 +225,88 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
()
}
private def createNodeLink(
bitcoindRpcClient: Option[BitcoindRpcClient],
channelAmount: MilliSatoshis)(
implicit actorSystem: ActorSystem): EclairNodes4 = {
implicit val ec: ExecutionContext = actorSystem.dispatcher
val internalBitcoind = bitcoindRpcClient.getOrElse(
BitcoindRpcTestUtil.startedBitcoindRpcClient()
)
val (first, second) = createNodePair(Some(internalBitcoind))
val (third, fourth) = createNodePair(Some(internalBitcoind))
def open(
c1: EclairRpcClient,
c2: EclairRpcClient): Future[FundedChannelId] = {
openChannel(n1 = c1,
n2 = c2,
amt = channelAmount.toSatoshis,
pushMSat = MilliSatoshis(channelAmount.toLong / 2))
}
EclairRpcTestUtil.connectLNNodes(second, third)
val openF = Future.sequence(
List(open(first, second), open(second, third), open(third, fourth)))
Await.result(openF, 60.seconds)
internalBitcoind.generate(3)
EclairNodes4(first, second, third, fourth)
}
/**
* Creates two eclair nodes that are connected together and returns their
* $nodeLinkDoc
* @note Blocks the current thread
* @return A 4-tuple of the created nodes' respective
* [[org.bitcoins.eclair.rpc.client.EclairRpcClient EclairRpcClient]]
*/
def createNodeLink(
bitcoindRpcClient: BitcoindRpcClient
)(implicit actorSystem: ActorSystem): EclairNodes4 = {
createNodeLink(Some(bitcoindRpcClient), DEFAULT_CHANNEL_MSAT_AMT)
}
/**
* $nodeLinkDoc
* @note Blocks the current thread
* @return A 4-tuple of the created nodes' respective
* [[org.bitcoins.eclair.rpc.client.EclairRpcClient EclairRpcClient]]
*/
def createNodeLink(
bitcoindRpcClient: BitcoindRpcClient,
channelAmount: MilliSatoshis)(
implicit actorSystem: ActorSystem): EclairNodes4 = {
createNodeLink(Some(bitcoindRpcClient), channelAmount)
}
/**
* $nodeLinkDoc
* @note Blocks the current thread
* @return A 4-tuple of the created nodes' respective
* [[org.bitcoins.eclair.rpc.client.EclairRpcClient EclairRpcClient]]
*/
def createNodeLink()(implicit actorSystem: ActorSystem): EclairNodes4 = {
createNodeLink(None, DEFAULT_CHANNEL_MSAT_AMT)
}
/**
* $nodeLinkDoc
* @note Blocks the current thread
* @return A 4-tuple of the created nodes' respective
* [[org.bitcoins.eclair.rpc.client.EclairRpcClient EclairRpcClient]]
*/
def createNodeLink(
channelAmount: MilliSatoshis
)(implicit actorSystem: ActorSystem): EclairNodes4 = {
createNodeLink(None, channelAmount)
}
/**
* Creates two Eclair nodes that are connected together and returns their
* respective [[org.bitcoins.eclair.rpc.client.EclairRpcClient EclairRpcClient]]s
*/
def createNodePair(bitcoindRpcClientOpt: Option[BitcoindRpcClient])(
@ -301,14 +391,15 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
Future.sequence(payments)
}
private val DEFAULT_CHANNEL_MSAT_AMT = 500000
private val DEFAULT_CHANNEL_MSAT_AMT = MilliSatoshis(500000000L)
/** Opens a channel from n1 -> n2 */
def openChannel(
n1: EclairRpcClient,
n2: EclairRpcClient,
amt: CurrencyUnit = Satoshis(Int64(DEFAULT_CHANNEL_MSAT_AMT)),
pushMSat: MilliSatoshis = MilliSatoshis(DEFAULT_CHANNEL_MSAT_AMT))(
amt: CurrencyUnit = DEFAULT_CHANNEL_MSAT_AMT.toSatoshis,
pushMSat: MilliSatoshis = MilliSatoshis(
DEFAULT_CHANNEL_MSAT_AMT.toLong / 2))(
implicit system: ActorSystem): Future[FundedChannelId] = {
val bitcoindRpcClient = getBitcoindRpc(n1)
@ -382,3 +473,9 @@ trait EclairRpcTestUtil extends BitcoinSLogger {
}
object EclairRpcTestUtil extends EclairRpcTestUtil
case class EclairNodes4(
c1: EclairRpcClient,
c2: EclairRpcClient,
c3: EclairRpcClient,
c4: EclairRpcClient)