Skip to content
Closed
8 changes: 8 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import scodec.bits.ByteVector
* Created by PM on 13/02/2017.
*/
object Features {

/** LOCAL FEATURES **/
val OPTION_DATA_LOSS_PROTECT_MANDATORY = 0
val OPTION_DATA_LOSS_PROTECT_OPTIONAL = 1

Expand All @@ -36,6 +38,12 @@ object Features {
val CHANNEL_RANGE_QUERIES_BIT_MANDATORY = 6
val CHANNEL_RANGE_QUERIES_BIT_OPTIONAL = 7

val I_WUMBO_YOU_WUMBO_MANDATORY = 14
val I_WUMBO_YOU_WUMBO_OPTIONAL = 15

/** GLOBAL FEATURES **/
val WUMBORAMA_MANDATORY = 16
val WUMBORAMA_OPTIONAL = 17

def hasFeature(features: BitSet, bit: Int): Boolean = features.get(bit)

Expand Down
10 changes: 7 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import fr.acinq.eclair.transactions.Scripts._
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{Globals, NodeParams, ShortChannelId, addressToPublicKeyScript}
import fr.acinq.eclair._
import fr.acinq.eclair.Features._
import scodec.bits.ByteVector

import scala.concurrent.Await
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -81,10 +81,14 @@ object Helpers {
* Called by the fundee
*/
def validateParamsFundee(nodeParams: NodeParams, open: OpenChannel): Unit = {
val supportWumbo = hasFeature(nodeParams.localFeatures, I_WUMBO_YOU_WUMBO_OPTIONAL) || hasFeature(nodeParams.localFeatures, I_WUMBO_YOU_WUMBO_MANDATORY)

// BOLT #2: if the chain_hash value, within the open_channel, message is set to a hash of a chain that is unknown to the receiver:
// MUST reject the channel.
if (nodeParams.chainHash != open.chainHash) throw InvalidChainHash(open.temporaryChannelId, local = nodeParams.chainHash, remote = open.chainHash)
if (open.fundingSatoshis < nodeParams.minFundingSatoshis || open.fundingSatoshis >= Channel.MAX_FUNDING_SATOSHIS) throw InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS)

// BOLT #2: Channel funding limits and wumbo bit support
if (open.fundingSatoshis < nodeParams.minFundingSatoshis || (open.fundingSatoshis >= Channel.MAX_FUNDING_SATOSHIS && !supportWumbo)) throw InvalidFundingAmount(open.temporaryChannelId, open.fundingSatoshis, nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS)

// BOLT #2: The receiving node MUST fail the channel if: push_msat is greater than funding_satoshis * 1000.
if (open.pushMsat > 1000 * open.fundingSatoshis) throw InvalidPushAmount(open.temporaryChannelId, open.pushMsat, 1000 * open.fundingSatoshis)
Expand Down
18 changes: 15 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,22 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor
val remoteHasInitialRoutingSync = Features.hasFeature(remoteInit.localFeatures, Features.INITIAL_ROUTING_SYNC_BIT_OPTIONAL)
val remoteHasChannelRangeQueriesOptional = Features.hasFeature(remoteInit.localFeatures, Features.CHANNEL_RANGE_QUERIES_BIT_OPTIONAL)
val remoteHasChannelRangeQueriesMandatory = Features.hasFeature(remoteInit.localFeatures, Features.CHANNEL_RANGE_QUERIES_BIT_MANDATORY)
val remoteHasWumboOptional = Features.hasFeature(remoteInit.localFeatures, Features.I_WUMBO_YOU_WUMBO_OPTIONAL)
val remoteHasWumboMandatory = Features.hasFeature(remoteInit.localFeatures, Features.I_WUMBO_YOU_WUMBO_MANDATORY)
val remoteHasWumoramaOptional = Features.hasFeature(remoteInit.globalFeatures, Features.WUMBORAMA_OPTIONAL)
val remoteHasWumoramaMandatory = Features.hasFeature(remoteInit.globalFeatures, Features.WUMBORAMA_MANDATORY)

// If the remote peer advertised `option_wumborama` in its global features but doesn't `option_i_wumbo_you_wumbo` on local features
val hasWumboramaButNoLocalWumbo = (remoteHasWumoramaOptional || remoteHasWumoramaMandatory) && !(remoteHasWumboOptional || remoteHasWumboMandatory)

log.info(s"peer is using globalFeatures=${remoteInit.globalFeatures.toBin} and localFeatures=${remoteInit.localFeatures.toBin}")
log.info(s"$remoteNodeId has features: initialRoutingSync=$remoteHasInitialRoutingSync channelRangeQueriesOptional=$remoteHasChannelRangeQueriesOptional channelRangeQueriesMandatory=$remoteHasChannelRangeQueriesMandatory")
if (Features.areSupported(remoteInit.localFeatures)) {
log.info(s"$remoteNodeId has features: initialRoutingSync=$remoteHasInitialRoutingSync " +
s"channelRangeQueriesOptional=$remoteHasChannelRangeQueriesOptional " +
s"channelRangeQueriesMandatory=$remoteHasChannelRangeQueriesMandatory " +
s"wumboOptional=$remoteHasWumboOptional " +
s"wumboMandatory=$remoteHasWumboMandatory")

if (Features.areSupported(remoteInit.localFeatures) && !hasWumboramaButNoLocalWumbo) {
d.origin_opt.foreach(origin => origin ! "connected")

if (remoteHasInitialRoutingSync) {
Expand Down Expand Up @@ -546,7 +559,6 @@ object Peer {
case object Disconnect
case object ResumeAnnouncements
case class OpenChannel(remoteNodeId: PublicKey, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, fundingTxFeeratePerKw_opt: Option[Long], channelFlags: Option[Byte]) {
require(fundingSatoshis.amount < Channel.MAX_FUNDING_SATOSHIS, s"fundingSatoshis must be less than ${Channel.MAX_FUNDING_SATOSHIS}")
require(pushMsat.amount <= 1000 * fundingSatoshis.amount, s"pushMsat must be less or equal to fundingSatoshis")
require(fundingSatoshis.amount >= 0, s"fundingSatoshis must be positive")
require(pushMsat.amount >= 0, s"pushMsat must be positive")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ object Announcements {
)
}

def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, nodeAddresses: List[NodeAddress], timestamp: Long = Platform.currentTime / 1000): NodeAnnouncement = {
def makeNodeAnnouncement(nodeSecret: PrivateKey, alias: String, color: Color, globalFeatures: ByteVector, nodeAddresses: List[NodeAddress], timestamp: Long = Platform.currentTime / 1000): NodeAnnouncement = {
require(alias.size <= 32)
val witness = nodeAnnouncementWitnessEncode(timestamp, nodeSecret.publicKey, color, alias, ByteVector.empty, nodeAddresses)
val sig = Crypto.encodeSignature(Crypto.sign(witness, nodeSecret)) :+ 1.toByte
Expand All @@ -83,7 +83,7 @@ object Announcements {
nodeId = nodeSecret.publicKey,
rgbColor = color,
alias = alias,
features = ByteVector.empty,
features = globalFeatures,
addresses = nodeAddresses
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ object Graph {

// Low/High bound for channel capacity
val CAPACITY_CHANNEL_LOW_MSAT = 1000 * 1000L // 1000 sat
val CAPACITY_CHANNEL_HIGH_MSAT = Channel.MAX_FUNDING_SATOSHIS * 1000L
val CAPACITY_CHANNEL_HIGH_MSAT = 50000000000L // 0.5 BTC

// Low/High bound for CLTV channel value
val CLTV_LOW = 9
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef, initialized: Option[Prom

// on restart we update our node announcement
// note that if we don't currently have public channels, this will be ignored
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses)
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.globalFeatures, nodeParams.publicAddresses)
self ! nodeAnn

log.info(s"initialization completed, ready to process messages")
Expand Down Expand Up @@ -263,7 +263,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef, initialized: Option[Prom
// in case we just validated our first local channel, we announce the local node
if (!d0.nodes.contains(nodeParams.nodeId) && isRelatedTo(c, nodeParams.nodeId)) {
log.info("first local channel validated, announcing local node")
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.publicAddresses)
val nodeAnn = Announcements.makeNodeAnnouncement(nodeParams.privateKey, nodeParams.alias, nodeParams.color, nodeParams.globalFeatures, nodeParams.publicAddresses)
self ! nodeAnn
}
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,10 @@ class FeaturesSpec extends FunSuite {
assert(areSupported(hex"0141") == false)
}

}
test("'option_wumborama' feature") {
val globalFeatures = hex"20000"
assert(areSupported(globalFeatures))
assert(hasFeature(globalFeatures, WUMBORAMA_OPTIONAL))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import fr.acinq.bitcoin.{Block, ByteVector32}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
import fr.acinq.eclair.wire.{Error, Init, OpenChannel}
import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.scalatest.Outcome

import org.scalatest.{Outcome, Tag}
import scodec.bits.ByteVector
import scala.concurrent.duration._

/**
Expand All @@ -36,7 +36,12 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper
case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe)

override def withFixture(test: OneArgTest): Outcome = {
val setup = init()

val localFeaturesWithWumbo = ByteVector.fromValidHex("8000") // I_WUMBO_YOU_WUMBO_OPTIONAL
val setup = test.tags.toList match {
case "wumbo" :: Nil => init(nodeParamsB = Bob.nodeParams.copy(localFeatures = localFeaturesWithWumbo))
case _ => init()
}
import setup._
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures)
Expand Down Expand Up @@ -175,6 +180,15 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper
awaitCond(bob.stateName == CLOSED)
}

test("recv OpenChannel (wumbo size)", Tag("wumbo")) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val highFundingMsat = 100000000
bob ! open.copy(fundingSatoshis = highFundingMsat)
bob2alice.expectMsgType[AcceptChannel]
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}

test("recv Error") { f =>
import f._
bob ! Error(ByteVector32.Zeroes, "oops")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import fr.acinq.eclair.wire.{Color, NodeAddress, Tor2}
import fr.acinq.eclair.{ShortChannelId, randomBytes32, randomKey}
import org.scalatest.FunSuite
import org.sqlite.SQLiteException
import scodec.bits.ByteVector


class SqliteNetworkDbSpec extends FunSuite {
Expand All @@ -43,10 +44,10 @@ class SqliteNetworkDbSpec extends FunSuite {
val sqlite = inmem
val db = new SqliteNetworkDb(sqlite)

val node_1 = Announcements.makeNodeAnnouncement(randomKey, "node-alice", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_2 = Announcements.makeNodeAnnouncement(randomKey, "node-bob", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_3 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_4 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), Tor2("aaaqeayeaudaocaj", 42000) :: Nil)
val node_1 = Announcements.makeNodeAnnouncement(randomKey, "node-alice", Color(100.toByte, 200.toByte, 300.toByte), ByteVector.empty, NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_2 = Announcements.makeNodeAnnouncement(randomKey, "node-bob", Color(100.toByte, 200.toByte, 300.toByte), ByteVector.empty, NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_3 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), ByteVector.empty, NodeAddress.fromParts("192.168.1.42", 42000).get :: Nil)
val node_4 = Announcements.makeNodeAnnouncement(randomKey, "node-charlie", Color(100.toByte, 200.toByte, 300.toByte), ByteVector.empty, Tor2("aaaqeayeaudaocaj", 42000) :: Nil)

assert(db.listNodes().toSet === Set.empty)
db.addNode(node_1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,16 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
test("starting eclair nodes") {
import collection.JavaConversions._
instantiateEclairNode("A", ConfigFactory.parseMap(Map("eclair.node-alias" -> "A", "eclair.expiry-delta-blocks" -> 130, "eclair.server.port" -> 29730, "eclair.api.port" -> 28080, "eclair.channel-flags" -> 0)).withFallback(commonConfig)) // A's channels are private
instantiateEclairNode("B", ConfigFactory.parseMap(Map("eclair.node-alias" -> "B", "eclair.expiry-delta-blocks" -> 131, "eclair.server.port" -> 29731, "eclair.api.port" -> 28081)).withFallback(commonConfig))
instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.expiry-delta-blocks" -> 132, "eclair.server.port" -> 29732, "eclair.api.port" -> 28082, "eclair.payment-handler" -> "noop")).withFallback(commonConfig))
instantiateEclairNode("B", ConfigFactory.parseMap(Map("eclair.node-alias" -> "B", "eclair.expiry-delta-blocks" -> 131, "eclair.server.port" -> 29731, "eclair.api.port" -> 28081, "eclair.local-features" -> "808a")).withFallback(commonConfig)) // 808a contains support for option_i_wumbo_you_wumbo
instantiateEclairNode("C", ConfigFactory.parseMap(Map("eclair.node-alias" -> "C", "eclair.expiry-delta-blocks" -> 132, "eclair.server.port" -> 29732, "eclair.api.port" -> 28082, "eclair.payment-handler" -> "noop", "eclair.local-features" -> "808a")).withFallback(commonConfig))
instantiateEclairNode("D", ConfigFactory.parseMap(Map("eclair.node-alias" -> "D", "eclair.expiry-delta-blocks" -> 133, "eclair.server.port" -> 29733, "eclair.api.port" -> 28083)).withFallback(commonConfig))
instantiateEclairNode("E", ConfigFactory.parseMap(Map("eclair.node-alias" -> "E", "eclair.expiry-delta-blocks" -> 134, "eclair.server.port" -> 29734, "eclair.api.port" -> 28084)).withFallback(commonConfig))
instantiateEclairNode("F1", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F1", "eclair.expiry-delta-blocks" -> 135, "eclair.server.port" -> 29735, "eclair.api.port" -> 28085, "eclair.payment-handler" -> "noop")).withFallback(commonConfig)) // NB: eclair.payment-handler = noop allows us to manually fulfill htlcs
instantiateEclairNode("F2", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F2", "eclair.expiry-delta-blocks" -> 136, "eclair.server.port" -> 29736, "eclair.api.port" -> 28086, "eclair.payment-handler" -> "noop")).withFallback(commonConfig))
instantiateEclairNode("F3", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F3", "eclair.expiry-delta-blocks" -> 137, "eclair.server.port" -> 29737, "eclair.api.port" -> 28087, "eclair.payment-handler" -> "noop")).withFallback(commonConfig))
instantiateEclairNode("F4", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F4", "eclair.expiry-delta-blocks" -> 138, "eclair.server.port" -> 29738, "eclair.api.port" -> 28088, "eclair.payment-handler" -> "noop")).withFallback(commonConfig))
instantiateEclairNode("F5", ConfigFactory.parseMap(Map("eclair.node-alias" -> "F5", "eclair.expiry-delta-blocks" -> 139, "eclair.server.port" -> 29739, "eclair.api.port" -> 28089, "eclair.payment-handler" -> "noop")).withFallback(commonConfig))
instantiateEclairNode("G", ConfigFactory.parseMap(Map("eclair.node-alias" -> "G", "eclair.expiry-delta-blocks" -> 140, "eclair.server.port" -> 29740, "eclair.api.port" -> 28090, "eclair.fee-base-msat" -> 1010, "eclair.fee-proportional-millionths" -> 102)).withFallback(commonConfig))
instantiateEclairNode("G", ConfigFactory.parseMap(Map("eclair.node-alias" -> "G", "eclair.expiry-delta-blocks" -> 140, "eclair.server.port" -> 29740, "eclair.api.port" -> 28090, "eclair.fee-base-msat" -> 1005, "eclair.fee-proportional-millionths" -> 102, "eclair.local-features" -> "808a")).withFallback(commonConfig))

// by default C has a normal payment handler, but this can be overriden in tests
val paymentHandlerC = nodes("C").system.actorOf(LocalPaymentHandler.props(nodes("C").nodeParams))
Expand Down Expand Up @@ -194,8 +194,8 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
connect(nodes("C"), nodes("F3"), 5000000, 0)
connect(nodes("C"), nodes("F4"), 5000000, 0)
connect(nodes("C"), nodes("F5"), 5000000, 0)
connect(nodes("B"), nodes("G"), 16000000, 0)
connect(nodes("G"), nodes("C"), 16000000, 0)
connect(nodes("B"), nodes("G"), 46700000, 0)
connect(nodes("G"), nodes("C"), 46700000, 0)

val numberOfChannels = 13
val channelEndpointsCount = 2 * numberOfChannels
Expand Down
26 changes: 23 additions & 3 deletions eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@
package fr.acinq.eclair.io

import java.net.InetSocketAddress

import akka.actor.ActorRef
import akka.actor.{ActorRef, PoisonPill}
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair.blockchain.EclairWallet
import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.crypto.TransportHandler.ReadAck
import fr.acinq.eclair.io.Peer.{CHANNELID_ZERO, ResumeAnnouncements, SendPing}
import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast}
import fr.acinq.eclair.wire.{Error, Ping, Pong}
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, randomBytes, wire}
import org.scalatest.Outcome

import scodec.bits.ByteVector
import scala.concurrent.duration._


Expand Down Expand Up @@ -70,6 +70,26 @@ class PeerSpec extends TestkitBaseClass {
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")
}

test("refuse a connection if there is a wumbo conflict") { f =>
import f._

val globalWumboramaOptional = ByteVector.fromValidHex("20000")
val deathWatcher = TestProbe()

// let's simulate a connection
val probe = TestProbe()
probe.send(peer, Peer.Init(None, Set.empty))
authenticator.send(peer, Authenticator.Authenticated(connection.ref, transport.ref, remoteNodeId, new InetSocketAddress("1.2.3.4", 42000), outgoing = true, None))
transport.expectMsgType[TransportHandler.Listener]
transport.expectMsgType[wire.Init]
// Bob advertizes option_wumborama in the global features but nothing in the local features
transport.send(peer, wire.Init(globalWumboramaOptional, Bob.nodeParams.localFeatures))
transport.expectMsgType[ReadAck]

deathWatcher.watch(transport.ref) // connection refused, transport should be killed
deathWatcher.expectTerminated(transport.ref, max = 11 seconds)
}

test("reply to ping") { f =>
import f._
val probe = TestProbe()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import fr.acinq.eclair.router._
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{Globals, ShortChannelId, randomBytes32}
import scodec.bits.ByteVector

/**
* Created by PM on 29/08/2016.
Expand Down Expand Up @@ -334,7 +335,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {

val (priv_g, priv_funding_g) = (randomKey, randomKey)
val (g, funding_g) = (priv_g.publicKey, priv_funding_g.publicKey)
val ann_g = makeNodeAnnouncement(priv_g, "node-G", Color(-30, 10, -50), Nil)
val ann_g = makeNodeAnnouncement(priv_g, "node-G", Color(-30, 10, -50), ByteVector.empty, Nil)
val channelId_bg = ShortChannelId(420000, 5, 0)
val chan_bg = channelAnnouncement(channelId_bg, priv_b, priv_g, priv_funding_b, priv_funding_g)
val channelUpdate_bg = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, g, channelId_bg, cltvExpiryDelta = 9, htlcMinimumMsat = 0, feeBaseMsat = 0, feeProportionalMillionths = 0, htlcMaximumMsat = 500000000L)
Expand Down
Loading