Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add detailed message to remote failures #431

Merged
merged 5 commits into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package fr.acinq.eclair.channel

import fr.acinq.bitcoin.{BinaryData, Transaction}
import fr.acinq.eclair.UInt64
import fr.acinq.eclair.payment.Origin
import fr.acinq.eclair.payment.{Origin, Relayed}
import fr.acinq.eclair.wire.{ChannelUpdate, UpdateAddHtlc}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import akka.actor.{ActorRef, FSM, Props, Status}
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.{CMD_ADD_HTLC, Register}
import fr.acinq.eclair.channel.{AddHtlcFailed, CMD_ADD_HTLC, Register}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.crypto.Sphinx.{ErrorPacket, Packet}
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
Expand Down Expand Up @@ -219,4 +219,24 @@ object PaymentLifecycle {
CMD_ADD_HTLC(firstAmountMsat, paymentHash, firstExpiry, Packet.write(onion.packet), upstream_opt = None, commit = true) -> onion.sharedSecrets
}

/**
* Rewrites a list of failures to retrieve the meaningful part.
* <p>
* If a list of failures with many elements ends up with a LocalFailure RouteNotFound, this RouteNotFound failure
* should be removed. This last failure is irrelevant information. In such a case only the n-1 attempts were rejected
* with a **significant reason** ; the final RouteNotFound error provides no meaningful insight.
* <p>
* This method should be used by the user interface to provide a non-exhaustive but more useful feedback.
*
* @param failures a list of payment failures for a payment
*/
def transformForUser(failures: Seq[PaymentFailure]): Seq[PaymentFailure] = {
failures.map {
case LocalFailure(AddHtlcFailed(_, _, t, _, _)) => LocalFailure(t) // we're interested in the error which caused the add-htlc to fail
case other => other
} match {
case previousFailures :+ LocalFailure(RouteNotFound) if previousFailures.nonEmpty => previousFailures
case _ => failures
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ package fr.acinq.eclair.router

class RouterException(message: String) extends RuntimeException(message)

object RouteNotFound extends RouterException("Route not found")
object RouteNotFound extends RouterException("route not found")

object CannotRouteToSelf extends RouterException("Cannot route to self")
object CannotRouteToSelf extends RouterException("cannot route to self")
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,40 @@ import fr.acinq.eclair.wire.LightningMessageCodecs.{binarydata, channelUpdateCod
import scodec.Codec
import scodec.codecs._


/**
* see /~https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md
* Created by fabrice on 14/03/17.
*/

// @formatter:off
sealed trait FailureMessage
sealed trait FailureMessage { def message: String }
sealed trait BadOnion extends FailureMessage { def onionHash: BinaryData }
sealed trait Perm extends FailureMessage
sealed trait Node extends FailureMessage
sealed trait Update extends FailureMessage { def update: ChannelUpdate }

case object InvalidRealm extends Perm
case object TemporaryNodeFailure extends Node
case object PermanentNodeFailure extends Perm with Node
case object RequiredNodeFeatureMissing extends Perm with Node
case class InvalidOnionVersion(onionHash: BinaryData) extends BadOnion with Perm
case class InvalidOnionHmac(onionHash: BinaryData) extends BadOnion with Perm
case class InvalidOnionKey(onionHash: BinaryData) extends BadOnion with Perm
case class TemporaryChannelFailure(update: ChannelUpdate) extends Update
case object PermanentChannelFailure extends Perm
case object RequiredChannelFeatureMissing extends Perm
case object UnknownNextPeer extends Perm
case class AmountBelowMinimum(amountMsat: Long, update: ChannelUpdate) extends Update
case class FeeInsufficient(amountMsat: Long, update: ChannelUpdate) extends Update
case class IncorrectCltvExpiry(expiry: Long, update: ChannelUpdate) extends Update
case class ExpiryTooSoon(update: ChannelUpdate) extends Update
case class ChannelDisabled(flags: BinaryData, update: ChannelUpdate) extends Update
case object UnknownPaymentHash extends Perm
case object IncorrectPaymentAmount extends Perm
case object FinalExpiryTooSoon extends FailureMessage
case class FinalIncorrectCltvExpiry(expiry: Long) extends FailureMessage
case class FinalIncorrectHtlcAmount(amountMsat: Long) extends FailureMessage
case object ExpiryTooFar extends FailureMessage
case object InvalidRealm extends Perm { def message = "realm was not understood by the processing node" }
case object TemporaryNodeFailure extends Node { def message = "general temporary failure of the processing node" }
case object PermanentNodeFailure extends Perm with Node { def message = "general permanent failure of the processing node" }
case object RequiredNodeFeatureMissing extends Perm with Node { def message = "processing node requires features that are missing from this onion" }
case class InvalidOnionVersion(onionHash: BinaryData) extends BadOnion with Perm { def message = "onion version was not understood by the processing node" }
case class InvalidOnionHmac(onionHash: BinaryData) extends BadOnion with Perm { def message = "onion HMAC was incorrect when it reached the processing node" }
case class InvalidOnionKey(onionHash: BinaryData) extends BadOnion with Perm { def message = "ephemeral key was unparsable by the processing node" }
case class TemporaryChannelFailure(update: ChannelUpdate) extends Update { def message = s"channel ${update.shortChannelId.toHexString} is currently unavailable" }
case object PermanentChannelFailure extends Perm { def message = "channel is permanently unavailable" }
case object RequiredChannelFeatureMissing extends Perm { def message = "channel requires features not present in the onion" }
case object UnknownNextPeer extends Perm { def message = "processing node does not know the next peer in the route" }
case class AmountBelowMinimum(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment amount was below the minimum required by the channel" }
case class FeeInsufficient(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment fee was below the minimum required by the channel" }
case class ChannelDisabled(flags: BinaryData, update: ChannelUpdate) extends Update { def message = "channel is currently disabled" }
case class IncorrectCltvExpiry(expiry: Long, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" }
case object UnknownPaymentHash extends Perm { def message = "payment hash is unknown to the final node" }
case object IncorrectPaymentAmount extends Perm { def message = "payment amount is incorrect" }
case class ExpiryTooSoon(update: ChannelUpdate) extends Update { def message = "payment expiry is too close to the current block height for safe handling by the relaying node" }
case object FinalExpiryTooSoon extends FailureMessage { def message = "payment expiry is too close to the current block height for safe handling by the final node" }
case class FinalIncorrectCltvExpiry(expiry: Long) extends FailureMessage { def message = "payment expiry doesn't match the value in the onion" }
case class FinalIncorrectHtlcAmount(amountMsat: Long) extends FailureMessage { def message = "payment amount is incorrect in the final htlc" }
case object ExpiryTooFar extends FailureMessage { def message = "payment expiry is too far in the future" }
// @formatter:on

object FailureMessageCodecs {
Expand All @@ -66,7 +65,7 @@ object FailureMessageCodecs {
.typecase(PERM | 10, provide(UnknownNextPeer))
.typecase(UPDATE | 11, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[AmountBelowMinimum])
.typecase(UPDATE | 12, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
.typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec)).as[ExpiryTooSoon])
.typecase(UPDATE | 20, (("flags" | binarydata(2)) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled])
.typecase(PERM | 15, provide(UnknownPaymentHash))
Expand Down
2 changes: 1 addition & 1 deletion eclair-node-gui/src/main/resources/gui/main/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
-fx-text-fill: rgb(220, 220, 220);
}
.notification-pane .label.notification-message {
-fx-font-size: 16px;
-fx-font-size: 14px;
-fx-font-weight: bold;
}
.button.notification-close {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane fx:id="rootPane" maxWidth="300.0" minWidth="300.0" onMouseEntered="#handleMouseEnter" onMouseExited="#handleMouseExit" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<GridPane fx:id="rootPane" maxWidth="400.0" minWidth="400.0" onMouseEntered="#handleMouseEnter" onMouseExited="#handleMouseExit" prefWidth="400.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="25.0" prefWidth="25.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="220.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="320.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="30.0" minWidth="5.0" prefWidth="20.0" />
</columnConstraints>
<rowConstraints>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class FxApp extends Application with Logging {
popup.setHideOnEscape(false)
popup.setAutoFix(false)
val margin = 10
val width = 300
val width = 400
popup.setWidth(margin + width)
popup.getContent.add(root)
// positioning the popup @ TOP RIGHT of screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionConte
val message = CoinUtils.formatAmountInUnit(MilliSatoshi(amountMsat), FxApp.getUnit, withUnit = true)
notification("Payment Sent", message, NOTIFICATION_SUCCESS)
case Success(PaymentFailed(_, failures)) =>
val message = s"${
failures.lastOption match {
case Some(LocalFailure(t)) => t.getMessage
case Some(RemoteFailure(_, e)) => e.failureMessage
case _ => "Unknown error"
}
} (${failures.size} attempts)"
val distilledFailures = PaymentLifecycle.transformForUser(failures)
val message = s"${distilledFailures.size} attempts:\n${
distilledFailures.map {
case LocalFailure(t) => s"- (local) ${t.getMessage}"
case RemoteFailure(_, e) => s"- (remote) ${e.failureMessage.message}"
case _ => "- Unknown error"
}.mkString("\n")
}"
notification("Payment Failed", message, NOTIFICATION_ERROR)
case Failure(t) =>
val message = t.getMessage
Expand Down