From 458a54fcc135b28f910c6d117cc116b50e1e6880 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 11:06:07 +0100 Subject: [PATCH 01/11] Add local and global wumbo bits --- .../src/main/scala/fr/acinq/eclair/Features.scala | 8 ++++++++ eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala index 9ff59b4b00..4ec33af7ee 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala @@ -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 @@ -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) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index f58f601011..a3c59e793d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -121,8 +121,15 @@ 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) 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") + 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)) { d.origin_opt.foreach(origin => origin ! "connected") From c2685ff98606fd210df740469c40c6a182a886c0 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 11:37:29 +0100 Subject: [PATCH 02/11] Use actual globalFeatures from the conf in NodeAnnouncement message. --- .../scala/fr/acinq/eclair/router/Announcements.scala | 4 ++-- .../main/scala/fr/acinq/eclair/router/Router.scala | 4 ++-- .../fr/acinq/eclair/db/SqliteNetworkDbSpec.scala | 9 +++++---- .../acinq/eclair/payment/PaymentLifecycleSpec.scala | 3 ++- .../fr/acinq/eclair/router/AnnouncementsSpec.scala | 2 +- .../fr/acinq/eclair/router/BaseRouterSpec.scala | 12 ++++++------ .../fr/acinq/eclair/router/RoutingSyncSpec.scala | 8 +++----- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index ab0488195b..fa0020c273 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -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 @@ -83,7 +83,7 @@ object Announcements { nodeId = nodeSecret.publicKey, rgbColor = color, alias = alias, - features = ByteVector.empty, + features = globalFeatures, addresses = nodeAddresses ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index e08a8e0e54..90ec506e2e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -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") @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala index 7b4568532c..2f550ee2f9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala @@ -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 { @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 45f8597bbf..28df6487e1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -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. @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index 5b3b259a18..3bee26c63c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -48,7 +48,7 @@ class AnnouncementsSpec extends FunSuite { } test("create valid signed node announcement") { - val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, Alice.nodeParams.publicAddresses) + val ann = makeNodeAnnouncement(Alice.nodeParams.privateKey, Alice.nodeParams.alias, Alice.nodeParams.color, ByteVector.empty, Alice.nodeParams.publicAddresses) assert(checkSig(ann)) assert(checkSig(ann.copy(timestamp = 153)) === false) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index 7d08d7add9..de03b10f0c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -53,12 +53,12 @@ abstract class BaseRouterSpec extends TestkitBaseClass { //val DUMMY_SIG = hex"3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201" - val ann_a = makeNodeAnnouncement(priv_a, "node-A", Color(15, 10, -70), Nil) - val ann_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), Nil) - val ann_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), Nil) - val ann_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), Nil) - val ann_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), Nil) - val ann_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), Nil) + val ann_a = makeNodeAnnouncement(priv_a, "node-A", Color(15, 10, -70), ByteVector.empty, Nil) + val ann_b = makeNodeAnnouncement(priv_b, "node-B", Color(50, 99, -80), ByteVector.empty, Nil) + val ann_c = makeNodeAnnouncement(priv_c, "node-C", Color(123, 100, -40), ByteVector.empty, Nil) + val ann_d = makeNodeAnnouncement(priv_d, "node-D", Color(-120, -20, 60), ByteVector.empty, Nil) + val ann_e = makeNodeAnnouncement(priv_e, "node-E", Color(-50, 0, 10), ByteVector.empty, Nil) + val ann_f = makeNodeAnnouncement(priv_f, "node-F", Color(30, 10, -50), ByteVector.empty, Nil) val channelId_ab = ShortChannelId(420000, 1, 0) val channelId_bc = ShortChannelId(420000, 2, 0) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index dcc4500ab4..55b9b99c92 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -19,7 +19,6 @@ package fr.acinq.eclair.router import akka.actor.ActorSystem import akka.testkit.{TestFSMRef, TestKit, TestProbe} import fr.acinq.bitcoin.Block -import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair._ import fr.acinq.eclair.crypto.TransportHandler import fr.acinq.eclair.io.Peer.PeerRoutingMessage @@ -27,10 +26,9 @@ import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnounce import fr.acinq.eclair.router.BaseRouterSpec.channelAnnouncement import fr.acinq.eclair.wire._ import org.scalatest.FunSuiteLike - +import scodec.bits.ByteVector import scala.concurrent.duration._ - class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike { import RoutingSyncSpec.makeFakeRoutingInfo @@ -134,8 +132,8 @@ object RoutingSyncSpec { val TxCoordinates(blockHeight, _, _) = ShortChannelId.coordinates(shortChannelId) val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L, timestamp = blockHeight) val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L, timestamp = blockHeight) - val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Color(0, 0, 0), List()) - val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Color(0, 0, 0), List()) + val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Color(0, 0, 0), ByteVector.empty, List()) + val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Color(0, 0, 0), ByteVector.empty, List()) (channelAnn_ab, channelUpdate_ab, channelUpdate_ba, nodeAnnouncement_a, nodeAnnouncement_b) } } From 124a769ab9b2ca470c822d9014ada632a5b606bd Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 11:51:29 +0100 Subject: [PATCH 03/11] Add test for accepting an OpenChannel with wumbo size --- .../a/WaitForOpenChannelStateSpec.scala | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index ed0aae7798..d9ca0005c6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -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._ /** @@ -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) @@ -175,6 +180,15 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper awaitCond(bob.stateName == CLOSED) } + test("recv OpenChannel with 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") From d6109c6c315e5091488aea953622c8339e859cd6 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 12:05:46 +0100 Subject: [PATCH 04/11] Support wumbo bit --- .../main/scala/fr/acinq/eclair/channel/Helpers.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 0013f3a6ff..1204e6ba0f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -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} @@ -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) From 3c728f4da988b8ec215cffcf11003e2c9055bafd Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 12:05:58 +0100 Subject: [PATCH 05/11] Rename test --- .../eclair/channel/states/a/WaitForOpenChannelStateSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index d9ca0005c6..c1ecf8ce94 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -180,7 +180,7 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper awaitCond(bob.stateName == CLOSED) } - test("recv OpenChannel with wumbo size", Tag("wumbo")) { f => + test("recv OpenChannel (wumbo size)", Tag("wumbo")) { f => import f._ val open = alice2bob.expectMsgType[OpenChannel] val highFundingMsat = 100000000 From 9cd0b7e7b1b3320151f117e2f273b56b17b6f1a8 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 15:10:37 +0100 Subject: [PATCH 06/11] Remove fundingSatoshis limit from OpenChannel initialization --- eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index a3c59e793d..73fdad8e24 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -553,7 +553,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") From cbe0ca50c9d748c0fff5711328a7892b10c36e20 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 17:17:26 +0100 Subject: [PATCH 07/11] Add test for refusing incompatible wumbo options at Init message --- .../main/scala/fr/acinq/eclair/io/Peer.scala | 8 +++++++- .../scala/fr/acinq/eclair/FeaturesSpec.scala | 8 +++++++- .../scala/fr/acinq/eclair/io/PeerSpec.scala | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 73fdad8e24..c59131631c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -123,6 +123,12 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor 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 " + s"channelRangeQueriesOptional=$remoteHasChannelRangeQueriesOptional " + @@ -130,7 +136,7 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor s"wumboOptional=$remoteHasWumboOptional " + s"wumboMandatory=$remoteHasWumboMandatory") - if (Features.areSupported(remoteInit.localFeatures)) { + if (Features.areSupported(remoteInit.localFeatures) && !hasWumboramaButNoLocalWumbo) { d.origin_opt.foreach(origin => origin ! "connected") if (remoteHasInitialRoutingSync) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index 0f2dc94013..c84c9ac747 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -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)) + } + +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index dad5b1c3be..7b29f30ca1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -30,6 +30,7 @@ import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Reb 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._ @@ -70,6 +71,25 @@ 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") + + // 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[TransportHandler.ReadAck] + router.expectNoMsg(1 second) + probe.send(peer, Peer.GetPeerInfo) + assert(probe.expectMsgType[Peer.PeerInfo].state == "DISCONNECTED") + } + test("reply to ping") { f => import f._ val probe = TestProbe() From 23c09e147ae3609dd41543b92804093a8615dfd4 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 17:24:13 +0100 Subject: [PATCH 08/11] Improve test in PeerSpec --- eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 7b29f30ca1..367b756696 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -84,8 +84,6 @@ class PeerSpec extends TestkitBaseClass { 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[TransportHandler.ReadAck] - router.expectNoMsg(1 second) probe.send(peer, Peer.GetPeerInfo) assert(probe.expectMsgType[Peer.PeerInfo].state == "DISCONNECTED") } From 8915e31ad375263b5fbb2210552733b00a4b4377 Mon Sep 17 00:00:00 2001 From: Andrea Date: Wed, 27 Mar 2019 17:33:11 +0100 Subject: [PATCH 09/11] Update heuristic for large channel for a wumbo world --- eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala index 82551ae3c8..8796bd1bec 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Graph.scala @@ -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 From f6c98d306306e8fd09533e80ac2180796a393505 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 28 Mar 2019 10:28:50 +0100 Subject: [PATCH 10/11] Fix test for refusing connections if the peer has wumbo conflicts in its features --- .../src/test/scala/fr/acinq/eclair/io/PeerSpec.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index 367b756696..5a3c2381f4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -17,13 +17,13 @@ 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} @@ -31,7 +31,6 @@ 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._ @@ -75,6 +74,7 @@ class PeerSpec extends TestkitBaseClass { import f._ val globalWumboramaOptional = ByteVector.fromValidHex("20000") + val deathWatcher = TestProbe() // let's simulate a connection val probe = TestProbe() @@ -84,8 +84,10 @@ class PeerSpec extends TestkitBaseClass { 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)) - probe.send(peer, Peer.GetPeerInfo) - assert(probe.expectMsgType[Peer.PeerInfo].state == "DISCONNECTED") + 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 => From 227b81bb67ac0918f7cbc48d52be007b40fbe9f5 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 28 Mar 2019 11:46:32 +0100 Subject: [PATCH 11/11] Use wumbo channels in integration test --- .../fr/acinq/eclair/integration/IntegrationSpec.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index 142f74a333..c9afca0169 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -140,8 +140,8 @@ 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 @@ -149,7 +149,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService 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)) @@ -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