diff --git a/README.md b/README.md
index 76793a79d..ebf175cc7 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
**Eclair** (french for Lightning) is a scala implementation of the Lightning Network. It can run with or without a GUI, and a JSON-RPC API is also available.
-This software follows the [Lightning Network Specifications (BOLTs)](https://github.com/lightningnetwork/lightning-rfc). Other implementations include [lightning-c], [lit], and [lnd].
+This software follows the [Lightning Network Specifications (BOLTs)](https://github.com/lightningnetwork/lightning-rfc). Other implementations include [c-lightning] and [lnd].
---
@@ -179,9 +179,6 @@ docker run -ti --rm -v "/path_on_host:/data" -e "JAVA_OPTS=-Declair.printToConso
- [2] [Reaching The Ground With Lightning](https://github.com/ElementsProject/lightning/raw/master/doc/deployable-lightning.pdf) by Rusty Russell
- [3] [Lightning Network Explorer](https://explorer.acinq.co) - Explore testnet LN nodes you can connect to
-[Amiko-Pay]: https://github.com/cornwarecjp/amiko-pay
-[lightning-c]: https://github.com/ElementsProject/lightning
+[c-lightning]: https://github.com/ElementsProject/lightning
[lnd]: https://github.com/LightningNetwork/lnd
-[lit]: https://github.com/mit-dci/lit
-[Thunder]: https://github.com/blockchain/thunder
diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml
index c18fd8deb..147411131 100644
--- a/eclair-core/pom.xml
+++ b/eclair-core/pom.xml
@@ -5,7 +5,7 @@
fr.acinq.eclair
eclair_2.11
- 0.2-android-alpha13
+ 0.2-android-SNAPSHOT
eclair-core_2.11
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 af720c396..f444d9070 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
@@ -123,9 +123,25 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
}
}
- case Event(LocalChannelDown(_, channelId, shortChannelId, _), d: Data) =>
- log.debug("removed local channel_update for channelId={} shortChannelId={}", channelId, shortChannelId)
- stay using d.copy(privateChannels = d.privateChannels - shortChannelId, privateUpdates = d.privateUpdates.filterKeys(_.shortChannelId != shortChannelId))
+ case Event(LocalChannelDown(_, channelId, shortChannelId, remoteNodeId), d: Data) =>
+ // a local channel has permanently gone down
+ if (d.channels.contains(shortChannelId)) {
+ // the channel was public, we will receive (or have already received) a WatchEventSpentBasic event, that will trigger a clean up of the channel
+ // so let's not do anything here
+ stay
+ } else if (d.privateChannels.contains(shortChannelId)) {
+ // the channel was private or public-but-not-yet-announced, let's do the clean up
+ log.debug("removing private local channel and channel_update for channelId={} shortChannelId={}", channelId, shortChannelId)
+ val desc1 = ChannelDesc(shortChannelId, nodeParams.nodeId, remoteNodeId)
+ val desc2 = ChannelDesc(shortChannelId, remoteNodeId, nodeParams.nodeId)
+ // we remove the corresponding updates from the graph
+ removeEdge(d.graph, desc1)
+ removeEdge(d.graph, desc2)
+ // and we remove the channel and channel_update from our state
+ stay using d.copy(privateChannels = d.privateChannels - shortChannelId, privateUpdates = d.privateUpdates - desc1 - desc2)
+ } else {
+ stay
+ }
case Event(c: ChannelAnnouncement, d) =>
log.debug("received channel announcement for shortChannelId={} nodeId1={} nodeId2={} from {}", c.shortChannelId, c.nodeId1, c.nodeId2, sender)
@@ -163,8 +179,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSM[State, Data]
log.debug("received channel update for shortChannelId={} from {}", u.shortChannelId, sender)
stay using handle(u, sender, d)
- case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d)
- if d.channels.contains(shortChannelId) =>
+ case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d) if d.channels.contains(shortChannelId) =>
val lostChannel = d.channels(shortChannelId)
log.info("funding tx of channelId={} has been spent", shortChannelId)
// we need to remove nodes that aren't tied to any channels anymore
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 575e583d2..cfffe1d40 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
@@ -1,9 +1,11 @@
package fr.acinq.eclair.payment
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
+import akka.actor.Status
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.MilliSatoshi
import fr.acinq.eclair.Globals
+import fr.acinq.eclair.channel.{AddHtlcFailed, ChannelUnavailable}
import fr.acinq.eclair.channel.Register.ForwardShortId
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.crypto.Sphinx.ErrorPacket
@@ -72,7 +74,33 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
// we allow 2 tries, so we send a 2nd request to the router
sender.expectMsg(PaymentFailed(request.paymentHash, UnreadableRemoteFailure(hops) :: UnreadableRemoteFailure(hops) :: Nil))
+ }
+ test("payment failed (local error)") { case (router, _) =>
+ val relayer = TestProbe()
+ val routerForwarder = TestProbe()
+ val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
+ val monitor = TestProbe()
+ val sender = TestProbe()
+
+ paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
+ val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
+
+ val request = SendPayment(142000L, "42" * 32, d, maxAttempts = 2)
+ sender.send(paymentFSM, request)
+ awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
+ val WaitingForRoute(_, _, Nil) = paymentFSM.stateData
+ routerForwarder.expectMsg(RouteRequest(a, d, assistedRoutes = Nil, ignoreNodes = Set.empty, ignoreChannels = Set.empty))
+ routerForwarder.forward(router)
+ awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
+ val WaitingForComplete(_, _, cmd1, Nil, _, _, _, hops) = paymentFSM.stateData
+
+ relayer.expectMsg(ForwardShortId(channelId_ab, cmd1))
+ sender.send(paymentFSM, Status.Failure(AddHtlcFailed("00" * 32, request.paymentHash, ChannelUnavailable("00" * 32), Local(Some(paymentFSM.underlying.self)), None)))
+
+ // then the payment lifecycle will ask for a new route excluding the channel
+ routerForwarder.expectMsg(RouteRequest(a, d, assistedRoutes = Nil, ignoreNodes = Set.empty, ignoreChannels = Set(channelId_ab)))
+ awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
}
test("payment failed (first hop returns an UpdateFailMalformedHtlc)") { case (router, _) =>
@@ -100,7 +128,6 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
// then the payment lifecycle will ask for a new route excluding the channel
routerForwarder.expectMsg(RouteRequest(a, d, assistedRoutes = Nil, ignoreNodes = Set.empty, ignoreChannels = Set(channelId_ab)))
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
-
}
test("payment failed (TemporaryChannelFailure)") { case (router, _) =>
@@ -193,7 +220,6 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
assert(paymentOK.amountMsat > request.amountMsat)
val PaymentSent(MilliSatoshi(request.amountMsat), feesPaid, request.paymentHash, paymentOK.paymentPreimage) = eventListener.expectMsgType[PaymentSent]
assert(feesPaid.amount > 0)
-
}
}
diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml
index 707169bbd..148aceed9 100644
--- a/eclair-node/pom.xml
+++ b/eclair-node/pom.xml
@@ -5,7 +5,7 @@
fr.acinq.eclair
eclair_2.11
- 0.2-android-alpha13
+ 0.2-android-SNAPSHOT
eclair-node_2.11
diff --git a/pom.xml b/pom.xml
index 3c80613fe..00dc008d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
fr.acinq.eclair
eclair_2.11
- 0.2-android-alpha13
+ 0.2-android-SNAPSHOT
pom