diff --git a/.travis.yml b/.travis.yml index 1a0a834e8..560a952c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,9 @@ scala: env: - export LD_LIBRARY_PATH=/usr/local/lib before_install: - - wget http://mirror.ibcp.fr/pub/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.zip - - unzip -qq apache-maven-3.5.4-bin.zip - - export M2_HOME=$PWD/apache-maven-3.5.4 + - wget http://apache.crihan.fr/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.zip + - unzip -qq apache-maven-3.6.0-bin.zip + - export M2_HOME=$PWD/apache-maven-3.6.0 - export PATH=$M2_HOME/bin:$PATH script: - mvn install diff --git a/BUILD.md b/BUILD.md index 753ecccb6..0cb88b0ef 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,7 +2,7 @@ ## Requirements - [OpenJDK 11](https://jdk.java.net/11/). -- [Maven](https://maven.apache.org/download.cgi) 3.5.4 or newer +- [Maven](https://maven.apache.org/download.cgi) 3.6.0 or newer - [Docker](https://www.docker.com/) 18.03 or newer (optional) if you want to run all tests :warning: You can also use [Oracle JDK 1.8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) to build and run eclair, but we recommend you use Open JDK11. diff --git a/Dockerfile b/Dockerfile index 00ca879c6..f26f23741 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ FROM openjdk:8u171-jdk-alpine as BUILD RUN apk add --no-cache curl tar bash -ARG MAVEN_VERSION=3.5.4 +ARG MAVEN_VERSION=3.6.0 ARG USER_HOME_DIR="/root" ARG SHA=ce50b1c91364cb77efe3776f756a6d92b76d9038b0a0782f7d53acf1e997a14d ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries @@ -54,4 +54,4 @@ ENV JAVA_OPTS= RUN mkdir -p "$ECLAIR_DATADIR" VOLUME [ "/data" ] -ENTRYPOINT java $JAVA_OPTS -Declair.datadir=$ECLAIR_DATADIR -jar eclair-node.jar \ No newline at end of file +ENTRYPOINT java $JAVA_OPTS -Declair.datadir=$ECLAIR_DATADIR -jar eclair-node.jar 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 1b4c8ee3a..32a4a016f 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 @@ -339,7 +339,7 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor case (count, (_, origins)) if origins.contains(self) => // the announcement came from this peer, we don't send it back count - case (count, (msg: HasTimestamp, _)) if !timestampInRange(msg, d.gossipTimestampFilter) => + case (count, (msg, _)) if !timestampInRange(msg, d.gossipTimestampFilter) => // the peer has set up a filter on timestamp and this message is out of range count case (count, (msg, _)) => @@ -606,14 +606,15 @@ object Peer { * * @param gossipTimestampFilter_opt optional gossip timestamp range * @return - * - true if the msg's timestamp is in the requested range, or if there is no filtering + * - true if there is a filter and msg has no timestamp, or has one that matches the filter * - false otherwise */ - def timestampInRange(msg: HasTimestamp, gossipTimestampFilter_opt: Option[GossipTimestampFilter]): Boolean = { + def timestampInRange(msg: RoutingMessage, gossipTimestampFilter_opt: Option[GossipTimestampFilter]): Boolean = { // check if this message has a timestamp that matches our timestamp filter - gossipTimestampFilter_opt match { - case None => true // no filtering - case Some(GossipTimestampFilter(_, firstTimestamp, timestampRange)) => msg.timestamp >= firstTimestamp && msg.timestamp <= firstTimestamp + timestampRange + (msg, gossipTimestampFilter_opt) match { + case (_, None) => false // BOLT 7: A node which wants any gossip messages would have to send this, otherwise [...] no gossip messages would be received. + case (hasTs: HasTimestamp, Some(GossipTimestampFilter(_, firstTimestamp, timestampRange))) => hasTs.timestamp >= firstTimestamp && hasTs.timestamp <= firstTimestamp + timestampRange + case _ => true // if there is a filter and message doesn't have a timestamp (e.g. channel_announcement), then we send it } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala index efdc8fa73..ed310ca38 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/StateTestsHelperMethods.scala @@ -47,7 +47,7 @@ trait StateTestsHelperMethods extends TestKitBase { relayerB: TestProbe, channelUpdateListener: TestProbe) - def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams): SetupFixture = { + def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams, wallet: EclairWallet = new TestWallet): SetupFixture = { Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw)) val alice2bob = TestProbe() val bob2alice = TestProbe() @@ -59,7 +59,6 @@ trait StateTestsHelperMethods extends TestKitBase { system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelUpdate]) system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelDown]) val router = TestProbe() - val wallet = new TestWallet val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref)) val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref)) SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 5b08d8471..25d846ef4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -17,14 +17,17 @@ package fr.acinq.eclair.channel.states.a import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.{Block, ByteVector32} +import fr.acinq.bitcoin.{Block, ByteVector32, Satoshi} import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _} import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel} import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.{Outcome, Tag} +import scodec.bits.ByteVector +import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ /** @@ -36,10 +39,13 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { + val noopWallet = new TestWallet { + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed + } val setup = if (test.tags.contains("mainnet")) { - init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash)) + init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), wallet = noopWallet) } else { - init() + init(wallet = noopWallet) } import setup._ val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index a18650363..8bdb25f76 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -17,14 +17,17 @@ package fr.acinq.eclair.channel.states.b import akka.testkit.{TestFSMRef, TestProbe} -import fr.acinq.bitcoin.ByteVector32 +import fr.acinq.bitcoin.{ByteVector32, Satoshi} import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.blockchain.{MakeFundingTxResponse, TestWallet} import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.states.StateTestsHelperMethods import fr.acinq.eclair.wire._ import fr.acinq.eclair.{TestConstants, TestkitBaseClass} import org.scalatest.Outcome +import scodec.bits.ByteVector +import scala.concurrent.{Future, Promise} import scala.concurrent.duration._ /** @@ -36,7 +39,10 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe) override def withFixture(test: OneArgTest): Outcome = { - val setup = init() + val noopWallet = new TestWallet { + override def makeFundingTx(pubkeyScript: ByteVector, amount: Satoshi, feeRatePerKw: Long): Future[MakeFundingTxResponse] = Promise[MakeFundingTxResponse].future // will never be completed + } + val setup = init(wallet = noopWallet) import setup._ val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures) val bobInit = Init(Bob.channelParams.globalFeatures, Bob.channelParams.localFeatures) 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 cf8323735..9458d08e8 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 @@ -67,7 +67,7 @@ class SqliteNetworkDbSpec extends FunSuite { val sqlite = inmem val db = new SqliteNetworkDb(sqlite) - def sig = Crypto.encodeSignature(Crypto.sign(randomKey.toBin, randomKey)) :+ 1.toByte + def sig = Crypto.encodeSignature(Crypto.sign(randomBytes32, randomKey)) :+ 1.toByte val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig) val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(43), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig) @@ -107,7 +107,7 @@ class SqliteNetworkDbSpec extends FunSuite { test("remove many channels") { val sqlite = inmem val db = new SqliteNetworkDb(sqlite) - val sig = Crypto.encodeSignature(Crypto.sign(randomKey.toBin, randomKey)) :+ 1.toByte + val sig = Crypto.encodeSignature(Crypto.sign(randomBytes32, randomKey)) :+ 1.toByte val priv = randomKey val pub = priv.publicKey val capacity = Satoshi(10000) 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 29b61c0ef..652ca806c 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 @@ -149,6 +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)) // 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)) @@ -172,7 +173,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService } test("connect nodes") { - // ,--G--, // G is being added later in a test + // ,--G--, // / \ // A---B ------- C ==== D // \ / \ @@ -182,7 +183,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService val eventListener = TestProbe() nodes.values.foreach(_.system.eventStream.subscribe(eventListener.ref, classOf[ChannelStateChanged])) - connect(nodes("A"), nodes("B"), 10000000, 0) + connect(nodes("A"), nodes("B"), 11000000, 0) connect(nodes("B"), nodes("C"), 2000000, 0) connect(nodes("C"), nodes("D"), 5000000, 0) connect(nodes("C"), nodes("D"), 5000000, 0) @@ -193,8 +194,10 @@ 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) - val numberOfChannels = 11 + val numberOfChannels = 13 val channelEndpointsCount = 2 * numberOfChannels // we make sure all channels have set up their WatchConfirmed for the funding tx @@ -246,8 +249,8 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService // A requires private channels, as a consequence: // - only A and B know about channel A-B // - A is not announced - awaitAnnouncements(nodes.filterKeys(key => List("A", "B").contains(key)), 9, 10, 22) - awaitAnnouncements(nodes.filterKeys(key => !List("A", "B").contains(key)), 9, 10, 20) + awaitAnnouncements(nodes.filterKeys(key => List("A", "B").contains(key)), 10, 12, 26) + awaitAnnouncements(nodes.filterKeys(key => !List("A", "B").contains(key)), 10, 12, 24) } test("send an HTLC A->D") { @@ -399,33 +402,21 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService test("send an HTLC A->B->G->C using heuristics to select the route") { val sender = TestProbe() - - // G has very large channels but slightly more expensive than the others - 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)) - connect(nodes("B"), nodes("G"), 16000000, 0) - connect(nodes("G"), nodes("C"), 16000000, 0) - - sender.send(bitcoincli, BitcoinReq("generate", 10)) - sender.expectMsgType[JValue](10 seconds) - - awaitCond({ - sender.send(nodes("A").router, 'channels) - sender.expectMsgType[Iterable[ChannelAnnouncement]](5 seconds).exists(chanAnn => chanAnn.nodeId1 == nodes("G").nodeParams.nodeId || chanAnn.nodeId2 == nodes("G").nodeParams.nodeId) - }, max = 60 seconds, interval = 3 seconds) - - val amountMsat = MilliSatoshi(2000) // first we retrieve a payment hash from C + val amountMsat = MilliSatoshi(2000) sender.send(nodes("C").paymentHandler, ReceivePayment(Some(amountMsat), "Change from coffee")) val pr = sender.expectMsgType[PaymentRequest](30 seconds) // the payment is requesting to use a capacity-optimized route which will select node G even though it's a bit more expensive sender.send(nodes("A").paymentInitiator, - SendPayment(amountMsat.amount, pr.paymentHash, nodes("C").nodeParams.nodeId, routeParams = integrationTestRouteParams.map(_.copy(ratios = Some(WeightRatios(0, 0, 1)))))) + SendPayment(amountMsat.amount, pr.paymentHash, nodes("C").nodeParams.nodeId, maxAttempts = 1, routeParams = integrationTestRouteParams.map(_.copy(ratios = Some(WeightRatios(0, 0, 1)))))) awaitCond({ - val route = sender.expectMsgType[PaymentSucceeded].route - route.exists(_.nodeId == nodes("G").nodeParams.nodeId) // assert the used route is actually going through G - }, max = 30 seconds, interval = 3 seconds) + sender.expectMsgType[PaymentResult](10 seconds) match { + case PaymentFailed(_, failures) => failures == Seq.empty // if something went wrong fail with a hint + case PaymentSucceeded(_, _, _, route) => route.exists(_.nodeId == nodes("G").nodeParams.nodeId) + } + }, max = 30 seconds, interval = 10 seconds) } 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 fdbc2a360..dad5b1c3b 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 @@ -109,9 +109,7 @@ class PeerSpec extends TestkitBaseClass { connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer) val rebroadcast = Rebroadcast(channels.map(_ -> Set.empty[ActorRef]).toMap, updates.map(_ -> Set.empty[ActorRef]).toMap, nodes.map(_ -> Set.empty[ActorRef]).toMap) probe.send(peer, rebroadcast) - channels.foreach(transport.expectMsg(_)) - updates.foreach(transport.expectMsg(_)) - nodes.foreach(transport.expectMsg(_)) + transport.expectNoMsg(2 seconds) } test("filter gossip message (filtered by origin)") { f => @@ -122,6 +120,8 @@ class PeerSpec extends TestkitBaseClass { channels.map(_ -> Set.empty[ActorRef]).toMap + (channels(5) -> Set(peer)), updates.map(_ -> Set.empty[ActorRef]).toMap + (updates(6) -> Set(peer)) + (updates(10) -> Set(peer)), nodes.map(_ -> Set.empty[ActorRef]).toMap + (nodes(4) -> Set(peer))) + val filter = wire.GossipTimestampFilter(Alice.nodeParams.chainHash, 0, Long.MaxValue) // no filtering on timestamps + probe.send(peer, filter) probe.send(peer, rebroadcast) // peer won't send out announcements that came from itself (channels.toSet - channels(5)).foreach(transport.expectMsg(_)) 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 4a2434b56..dcc4500ab 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 @@ -134,8 +134,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", Alice.nodeParams.color, List()) - val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Bob.nodeParams.color, List()) + val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Color(0, 0, 0), List()) + val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Color(0, 0, 0), List()) (channelAnn_ab, channelUpdate_ab, channelUpdate_ba, nodeAnnouncement_a, nodeAnnouncement_b) } } diff --git a/pom.xml b/pom.xml index e46032cf7..87dfb025a 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ 2.11 2.3.14 1.3.9 - 0.10 + 0.11 24.0-android