mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
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:
parent
308384b2af
commit
e068382701
@ -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] {
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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] = {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user