mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 14:40:34 +01:00
Added an activity tab to the GUI (#52)
* (gui) added Activity tab (payment sent, received, relayed) * GUIUpdater listens to PaymentEvent * payments are listed in separate tableviews (sent, received, relayed) * added Payment[Sent, Relayed, Received] events * (gui) Handling Relayed and Sent payments in Activity Tab * (gui) fixed amount columns in activity * (gui) Added formatting to monetary columns of activity tables * (gui) payments are prepended to activity tables
This commit is contained in:
parent
c3bfbf4a14
commit
34677f0ed6
24 changed files with 353 additions and 309 deletions
|
@ -48,11 +48,17 @@
|
|||
-fx-text-fill: rgb(146,149,151);
|
||||
}
|
||||
|
||||
.align-right {
|
||||
/* useful for table columns */
|
||||
-fx-alignment: CENTER_RIGHT;
|
||||
}
|
||||
|
||||
/* ---------- Context Menu ---------- */
|
||||
|
||||
.context-menu {
|
||||
-fx-padding: 4px;
|
||||
-fx-font-weight: normal;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.context-menu .menu-item:focused {
|
||||
-fx-background-color: rgb(63,179,234);
|
||||
|
@ -64,6 +70,11 @@
|
|||
-fx-padding: 2px 0;
|
||||
}
|
||||
|
||||
.menu-bar .context-menu {
|
||||
/* font size in menu context popup is standard */
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
/* ---------- Grid Structure ---------- */
|
||||
|
||||
.grid {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 728 B |
|
@ -127,3 +127,43 @@
|
|||
.button.notification-close:pressed {
|
||||
-fx-background-color: #7e7e7e;
|
||||
}
|
||||
|
||||
|
||||
/* ------------- Activity tab -------------- */
|
||||
|
||||
.activities-tab.tab-pane > *.tab-header-area {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.activities-tab.tab-pane > *.tab-header-area > *.tab-header-background {
|
||||
-fx-background-color: rgb(244,244,244);
|
||||
}
|
||||
|
||||
/* header buttons style */
|
||||
.activities-tab.tab-pane .tab:top {
|
||||
-fx-padding: 0.25em 1em;
|
||||
-fx-background-color: transparent;
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
/* header buttons style */
|
||||
.activities-tab.tab-pane .tab:top .text {
|
||||
-fx-fill: rgb(100, 104, 108);
|
||||
}
|
||||
.activities-tab.tab-pane .tab:top:selected .text {
|
||||
-fx-font-weight: bold;
|
||||
-fx-fill: rgb(0, 0, 0);
|
||||
}
|
||||
/* table style */
|
||||
.activities-tab .table-view {
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.label.activity-disclaimer {
|
||||
-fx-font-size: 10px;
|
||||
-fx-text-fill: rgb(166,169,171);
|
||||
-fx-padding: 2px 7px 0 0;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<BorderPane fx:id="root" minHeight="300.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<BorderPane fx:id="root" minHeight="300.0" prefHeight="400.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<center>
|
||||
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
|
@ -31,7 +31,7 @@
|
|||
</StackPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Nodes" fx:id="networkNodesTab">
|
||||
<Tab text="All Nodes" fx:id="networkNodesTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
<children>
|
||||
|
@ -48,7 +48,7 @@
|
|||
</VBox>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Channels" fx:id="networkChannelsTab">
|
||||
<Tab text="All Channels" fx:id="networkChannelsTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
<children>
|
||||
|
@ -64,6 +64,59 @@
|
|||
</VBox>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="Activity" closable="false">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
|
||||
styleClass="activities-tab" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab fx:id="paymentSentTab" closable="false" text="Sent">
|
||||
<TableView fx:id="paymentSentTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentSentDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentSentAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentFeesColumn" text="Fees Paid (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentReceivedTab" closable="false" text="Received">
|
||||
<TableView fx:id="paymentReceivedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentReceivedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentReceivedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentReceivedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentRelayedTab" closable="false" text="Relayed">
|
||||
<TableView fx:id="paymentRelayedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentRelayedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentRelayedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedFeesColumn" text="Fees Earned (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
<Label AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" textAlignment="RIGHT"
|
||||
maxWidth="180.0" wrapText="true" styleClass="activity-disclaimer"
|
||||
text="Payment history will be erased when the node is shutdown." />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</center>
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Push Amount (mSat)" />
|
||||
<Label styleClass="text-strong" text="Push Amount (msat)" />
|
||||
<Label styleClass="label-description" text="Sent when opening channel" textAlignment="RIGHT" wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<Label fx:id="paymentRequestError" opacity="0.0" text="Generic Invalid Payment Request" mouseTransparent="true"
|
||||
styleClass="text-error" GridPane.columnSpan="2" GridPane.rowIndex="2"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Amount (mSat)" GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
|
||||
<Label styleClass="text-muted" text="Amount (msat)" GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
|
||||
<TextField fx:id="amountField" focusTraversable="false" editable="false" styleClass="noteditable" text="0" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Node Id" GridPane.halignment="RIGHT" GridPane.rowIndex="4"/>
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
/* ---------- Global ---------- */
|
||||
.root {
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-family: "Open Sans", "Arial", "sans-serif";
|
||||
-fx-base: rgb(255, 255, 255);
|
||||
-fx-background: rgb(255, 255, 255);
|
||||
-fx-text-fill: rgb(80, 82, 84);
|
||||
}
|
||||
|
||||
.label {
|
||||
-fx-text-fill: rgb(80, 82, 84);
|
||||
}
|
||||
.label.text-muted {
|
||||
-fx-text-fill: rgb(156,159,161);
|
||||
}
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
|
||||
.button {
|
||||
-fx-padding: .5em 1em;
|
||||
-fx-border-insets: 0;
|
||||
-fx-border-width: 0;
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 0;
|
||||
-fx-background-color: rgb(63,179,234);
|
||||
-fx-cursor: hand;
|
||||
-fx-border-color: rgb(63,179,234);
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
.button .text {
|
||||
-fx-fill: white;
|
||||
}
|
||||
.button:hover,
|
||||
.button:focused {
|
||||
-fx-background-color: rgb(24,158,222);
|
||||
-fx-border-color: rgb(24,158,222);
|
||||
}
|
||||
.button:pressed {
|
||||
-fx-background-color: rgb(0,131,195);
|
||||
-fx-border-color: rgb(0,131,195);
|
||||
}
|
||||
|
||||
.button.close {
|
||||
-fx-background-color: rgb(232,65,90);
|
||||
}
|
||||
|
||||
.button.cancel {
|
||||
-fx-background-color: white;
|
||||
-fx-border-color: rgb(233,190,198);
|
||||
-fx-border-width: 1px;
|
||||
}
|
||||
.button.cancel:hover,
|
||||
.button.cancel:focused,
|
||||
.button.cancel:pressed {
|
||||
-fx-border-color: rgb(217,144,158);
|
||||
}
|
||||
.button.cancel .text {
|
||||
-fx-fill: rgb(212,125,140);
|
||||
}
|
||||
.button.cancel:hover .text,
|
||||
.button.cancel:focused .text,
|
||||
.button.cancel:pressed .text {
|
||||
-fx-fill: rgb(191,68,92);
|
||||
}
|
||||
|
||||
.button.grey {
|
||||
-fx-background-color: rgb(230,230,230);
|
||||
-fx-border-color: rgb(230,230,230);
|
||||
}
|
||||
.button.grey:hover,
|
||||
.button.grey:pressed {
|
||||
-fx-background-color: rgb(220,220,220);
|
||||
-fx-border-color: rgb(220,220,220);
|
||||
}
|
||||
.button.grey .text {
|
||||
-fx-fill: rgb(95,95,95);
|
||||
}
|
||||
|
||||
.button.close-channel {
|
||||
-fx-background-color: white;
|
||||
-fx-background-image: url("../commons/images/close.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: 5px;
|
||||
-fx-background-position: 8px center;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0px 5px 0px 17px;
|
||||
}
|
||||
|
||||
.button.close-channel:hover {
|
||||
-fx-background-color: rgb(249,243,243);
|
||||
-fx-border-color: rgb(249,243,243);
|
||||
}
|
||||
|
||||
.button.close-channel .text {
|
||||
-fx-fill: rgb(232,65,90);
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
/* ------------- TEXT FIELD ------------- */
|
||||
|
||||
.text-field, .text-area {
|
||||
-fx-padding: .6em;
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-color: rgb(220,220,220);
|
||||
-fx-border-width: 1px;
|
||||
-fx-background-radius: 0;
|
||||
-fx-background-color: rgb(249,249,249);
|
||||
}
|
||||
.text-field:focused {
|
||||
-fx-border-color: rgb(63,179,234);
|
||||
}
|
||||
|
||||
/* ------------- TEXT AREA ------------- */
|
||||
|
||||
.ta.text-area {
|
||||
-fx-border-width: 1px;
|
||||
-fx-padding: .4em .2em;
|
||||
-fx-font-family: "Consolas", "Lucida Console", Monaco, monospace;
|
||||
}
|
||||
|
||||
.ta.text-area *.scroll-pane > *.viewport,
|
||||
.ta.text-area *.scroll-pane > *.viewport *.content {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
|
||||
/* ------------- Status Bar ------------- */
|
||||
|
||||
.status-bar {
|
||||
-fx-background-color: rgb(240,245,246);
|
||||
}
|
||||
.status-bar .separator:vertical .line {
|
||||
-fx-background-color: rgb(226,227,229);
|
||||
}
|
||||
|
||||
/* ------------- About modal ------------- */
|
||||
|
||||
.about-text {
|
||||
-fx-fill: rgb(80,80,80);
|
||||
}
|
||||
|
||||
/* ------------- Splash Error Box ------------- */
|
||||
|
||||
.error-box {
|
||||
-fx-background-color: rgb(255,255,255);
|
||||
-fx-text-fill: white;
|
||||
-fx-border-color: rgb(180,180,180);
|
||||
}
|
||||
|
||||
/* ------------- Menu ------------- */
|
||||
|
||||
.menu-bar .menu-button:hover,
|
||||
.menu-bar .menu-button:focused,
|
||||
.menu-bar .menu-button:showing,
|
||||
.menu-bar .menu-item:hover,
|
||||
.menu-bar .menu-item:focused {
|
||||
-fx-background-color: rgb(109,197,239);
|
||||
}
|
||||
|
||||
/* ------------- Tabs ------------- */
|
||||
|
||||
.tab-pane > *.tab-header-area {
|
||||
-fx-padding: .7em 1em 0;
|
||||
}
|
||||
|
||||
.tab-pane > *.tab-header-area > *.tab-header-background {
|
||||
-fx-base: rgb(25,170,240);
|
||||
-fx-background-color: rgb(228,235,238);
|
||||
}
|
||||
|
||||
.tab:top {
|
||||
-fx-padding: 0.25em 1em;
|
||||
-fx-background-color: rgba(255,255,255, .5);
|
||||
-fx-background-radius: 0;
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0 0 0 0;
|
||||
}
|
||||
|
||||
.tab:top:selected {
|
||||
-fx-background-color: white;
|
||||
-fx-base: rgb(25,170,240);
|
||||
}
|
||||
.tab-content-area {
|
||||
-fx-background-color: white;
|
||||
}
|
|
@ -383,7 +383,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
|
|||
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, downstream_opt, do_commit), d@DATA_NORMAL(commitments, _)) =>
|
||||
Try(Commitments.sendAdd(commitments, c)) match {
|
||||
case Success(Right((commitments1, add))) =>
|
||||
val origin = downstream_opt.map(Relayed(_)).getOrElse(Local(sender))
|
||||
val origin = downstream_opt.map(u => Relayed(sender, u)).getOrElse(Local(sender))
|
||||
relayer ! AddHtlcSucceeded(add, origin)
|
||||
if (do_commit) self ! CMD_SIGN
|
||||
handleCommandSuccess(sender, d.copy(commitments = commitments1))
|
||||
|
@ -895,7 +895,7 @@ class Channel(val nodeParams: NodeParams, remoteNodeId: PublicKey, blockchain: A
|
|||
log.info(s"we are disconnected so we just include the add in our commitments")
|
||||
Try(Commitments.sendAdd(commitments, c)) match {
|
||||
case Success(Right((commitments1, add))) =>
|
||||
val origin = downstream_opt.map(Relayed(_)).getOrElse(Local(sender))
|
||||
val origin = downstream_opt.map(u => Relayed(sender, u)).getOrElse(Local(sender))
|
||||
relayer ! AddHtlcSucceeded(add, origin)
|
||||
sender ! "ok"
|
||||
goto(stateName) using d.copy(commitments = commitments1)
|
||||
|
|
|
@ -13,6 +13,7 @@ import akka.actor.{Props, SupervisorStrategy}
|
|||
import akka.stream.StreamTcpException
|
||||
import fr.acinq.eclair.channel.ChannelEvent
|
||||
import fr.acinq.eclair.gui.controllers.{MainController, NotificationsController}
|
||||
import fr.acinq.eclair.payment.PaymentEvent
|
||||
import fr.acinq.eclair.router.NetworkEvent
|
||||
import fr.acinq.eclair.{Setup, SimpleSupervisor, TCPBindException}
|
||||
import grizzled.slf4j.Logging
|
||||
|
@ -42,6 +43,7 @@ class FxApp extends Application with Logging {
|
|||
val guiUpdater = setup.system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller, setup), "gui-updater", SupervisorStrategy.Resume))
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[PaymentEvent])
|
||||
|
||||
Platform.runLater(new Runnable {
|
||||
override def run(): Unit = {
|
||||
|
@ -51,7 +53,7 @@ class FxApp extends Application with Logging {
|
|||
val scene = new Scene(mainRoot)
|
||||
|
||||
primaryStage.setTitle("Eclair")
|
||||
primaryStage.setMinWidth(570)
|
||||
primaryStage.setMinWidth(600)
|
||||
primaryStage.setWidth(650)
|
||||
primaryStage.setMinHeight(400)
|
||||
primaryStage.setHeight(400)
|
||||
|
|
|
@ -5,7 +5,6 @@ import javafx.application.Platform
|
|||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.layout.VBox
|
||||
import collection.JavaConversions._
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Terminated}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
|
@ -14,10 +13,13 @@ import fr.acinq.eclair.Setup
|
|||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.gui.controllers.{ChannelPaneController, MainController}
|
||||
import fr.acinq.eclair.io.Reconnect
|
||||
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.router.{ChannelDiscovered, ChannelLost, NodeDiscovered, NodeLost}
|
||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
|
@ -139,5 +141,17 @@ class GUIUpdater(mainController: MainController, setup: Setup) extends Actor wit
|
|||
mainController.networkChannelsList.removeIf(new Predicate[ChannelAnnouncement] {
|
||||
override def test(ca: ChannelAnnouncement) = ca.shortChannelId == shortChannelId
|
||||
})
|
||||
|
||||
case p: PaymentSent =>
|
||||
log.debug(s"payment sent with h=${p.paymentHash}, amount=${p.amount}, fees=${p.feesPaid}")
|
||||
mainController.paymentSentList.prepend(p)
|
||||
|
||||
case p: PaymentReceived =>
|
||||
log.debug(s"payment received with h=${p.paymentHash}, amount=${p.amount}")
|
||||
mainController.paymentReceivedList.prepend(p)
|
||||
|
||||
case p: PaymentRelayed =>
|
||||
log.debug(s"payment relayed with h=${p.paymentHash}, amount=${p.amount}, feesEarned=${p.feesEarned}")
|
||||
mainController.paymentRelayedList.prepend(p)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,13 +52,13 @@ class Handlers(setup: Setup) extends Logging {
|
|||
logger.info(s"sending $amountMsat to $paymentHash @ $nodeId")
|
||||
(paymentInitiator ? CreatePayment(amountMsat, paymentHash, nodeId)).mapTo[PaymentResult].onComplete {
|
||||
case Success(PaymentSucceeded(_)) =>
|
||||
val message = s"Amount (mSat): $amountMsat\nH: $paymentHash"
|
||||
val message = s"Amount (msat): $amountMsat\nH: $paymentHash"
|
||||
notification("Payment Successful", message, NOTIFICATION_SUCCESS)
|
||||
case Success(PaymentFailed(_, reason)) =>
|
||||
val message = s"Cause: ${reason.getOrElse("unknown")}\nAmount (mSat): $amountMsat\nH: $paymentHash"
|
||||
val message = s"Cause: ${reason.getOrElse("unknown")}\nAmount (msat): $amountMsat\nH: $paymentHash"
|
||||
notification("Payment Failed", message, NOTIFICATION_ERROR)
|
||||
case Failure(t) =>
|
||||
val message = s"Cause: ${t.getMessage}\nAmount (mSat): $amountMsat\nH: $paymentHash"
|
||||
val message = s"Cause: ${t.getMessage}\nAmount (msat): $amountMsat\nH: $paymentHash"
|
||||
notification("Payment Failed", message, NOTIFICATION_ERROR)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import java.text.NumberFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import javafx.application.{HostServices, Platform}
|
||||
import javafx.beans.property._
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
|
@ -21,6 +25,7 @@ import fr.acinq.eclair.Setup
|
|||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.stages._
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import fr.acinq.eclair.payment.{PaymentEvent, PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
|
@ -52,7 +57,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
@FXML var channelsTab: Tab = _
|
||||
|
||||
// all nodes tab
|
||||
val networkNodesList:ObservableList[NodeAnnouncement] = FXCollections.observableArrayList[NodeAnnouncement]()
|
||||
val networkNodesList: ObservableList[NodeAnnouncement] = FXCollections.observableArrayList[NodeAnnouncement]()
|
||||
@FXML var networkNodesTab: Tab = _
|
||||
@FXML var networkNodesTable: TableView[NodeAnnouncement] = _
|
||||
@FXML var networkNodesIdColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
|
@ -61,13 +66,42 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
@FXML var networkNodesIPColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
|
||||
// all channels
|
||||
val networkChannelsList:ObservableList[ChannelAnnouncement] = FXCollections.observableArrayList[ChannelAnnouncement]()
|
||||
val networkChannelsList: ObservableList[ChannelAnnouncement] = FXCollections.observableArrayList[ChannelAnnouncement]()
|
||||
@FXML var networkChannelsTab: Tab = _
|
||||
@FXML var networkChannelsTable: TableView[ChannelAnnouncement] = _
|
||||
@FXML var networkChannelsIdColumn: TableColumn[ChannelAnnouncement, Number] = _
|
||||
@FXML var networkChannelsNode1Column: TableColumn[ChannelAnnouncement, String] = _
|
||||
@FXML var networkChannelsNode2Column: TableColumn[ChannelAnnouncement, String] = _
|
||||
|
||||
// payment sent table
|
||||
val paymentSentList = FXCollections.observableArrayList[PaymentSent]()
|
||||
@FXML var paymentSentTab: Tab = _
|
||||
@FXML var paymentSentTable: TableView[PaymentSent] = _
|
||||
@FXML var paymentSentAmountColumn: TableColumn[PaymentSent, Number] = _
|
||||
@FXML var paymentSentFeesColumn: TableColumn[PaymentSent, Number] = _
|
||||
@FXML var paymentSentHashColumn: TableColumn[PaymentSent, String] = _
|
||||
@FXML var paymentSentDateColumn: TableColumn[PaymentSent, String] = _
|
||||
|
||||
// payment received table
|
||||
val paymentReceivedList = FXCollections.observableArrayList[PaymentReceived]()
|
||||
@FXML var paymentReceivedTab: Tab = _
|
||||
@FXML var paymentReceivedTable: TableView[PaymentReceived] = _
|
||||
@FXML var paymentReceivedAmountColumn: TableColumn[PaymentReceived, Number] = _
|
||||
@FXML var paymentReceivedHashColumn: TableColumn[PaymentReceived, String] = _
|
||||
@FXML var paymentReceivedDateColumn: TableColumn[PaymentReceived, String] = _
|
||||
|
||||
// payment relayed table
|
||||
val paymentRelayedList = FXCollections.observableArrayList[PaymentRelayed]()
|
||||
@FXML var paymentRelayedTab: Tab = _
|
||||
@FXML var paymentRelayedTable: TableView[PaymentRelayed] = _
|
||||
@FXML var paymentRelayedAmountColumn: TableColumn[PaymentRelayed, Number] = _
|
||||
@FXML var paymentRelayedFeesColumn: TableColumn[PaymentRelayed, Number] = _
|
||||
@FXML var paymentRelayedHashColumn: TableColumn[PaymentRelayed, String] = _
|
||||
@FXML var paymentRelayedDateColumn: TableColumn[PaymentRelayed, String] = _
|
||||
|
||||
val PAYMENT_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
val moneyFormatter = NumberFormat.getInstance(Locale.getDefault)
|
||||
|
||||
/**
|
||||
* Initialize the main window.
|
||||
*
|
||||
|
@ -90,7 +124,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
// init context
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(
|
||||
new CopyAction("Copy Pubkey", s"${setup.nodeParams.privateKey.publicKey}"),
|
||||
new CopyAction("Copy URI", s"${setup.nodeParams.privateKey.publicKey}@${setup.nodeParams.address.getHostString}:${setup.nodeParams.address.getPort}" )))
|
||||
new CopyAction("Copy URI", s"${setup.nodeParams.privateKey.publicKey}@${setup.nodeParams.address.getHostString}:${setup.nodeParams.address.getPort}")))
|
||||
|
||||
// init channels tab
|
||||
if (channelBox.getChildren.size() > 0) {
|
||||
|
@ -113,11 +147,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
// init all nodes
|
||||
networkNodesTable.setItems(networkNodesList)
|
||||
networkNodesList.addListener(new ListChangeListener[NodeAnnouncement] {
|
||||
override def onChanged(c: Change[_ <: NodeAnnouncement]) = {
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = networkNodesTab.setText(s"All Nodes (${networkNodesList.size})")
|
||||
})
|
||||
}
|
||||
override def onChanged(c: Change[_ <: NodeAnnouncement]) = updateTabHeader(networkNodesTab, "All Nodes", networkNodesList)
|
||||
})
|
||||
networkNodesIdColumn.setCellValueFactory(new Callback[CellDataFeatures[NodeAnnouncement, String], ObservableValue[String]]() {
|
||||
def call(pn: CellDataFeatures[NodeAnnouncement, String]) = new SimpleStringProperty(pn.getValue.nodeId.toString)
|
||||
|
@ -135,7 +165,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
})
|
||||
networkNodesRGBColumn.setCellFactory(new Callback[TableColumn[NodeAnnouncement, String], TableCell[NodeAnnouncement, String]]() {
|
||||
def call(pn: TableColumn[NodeAnnouncement, String]) = {
|
||||
new TableCell[NodeAnnouncement, String] () {
|
||||
new TableCell[NodeAnnouncement, String]() {
|
||||
override def updateItem(item: String, empty: Boolean): Unit = {
|
||||
super.updateItem(item, empty)
|
||||
setStyle("-fx-background-color:" + item)
|
||||
|
@ -150,11 +180,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
// init all channels
|
||||
networkChannelsTable.setItems(networkChannelsList)
|
||||
networkChannelsList.addListener(new ListChangeListener[ChannelAnnouncement] {
|
||||
override def onChanged(c: Change[_ <: ChannelAnnouncement]) = {
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = networkChannelsTab.setText(s"All Channels (${networkChannelsList.size})")
|
||||
})
|
||||
}
|
||||
override def onChanged(c: Change[_ <: ChannelAnnouncement]) = updateTabHeader(networkChannelsTab, "All Channels", networkChannelsList)
|
||||
})
|
||||
networkChannelsIdColumn.setCellValueFactory(new Callback[CellDataFeatures[ChannelAnnouncement, Number], ObservableValue[Number]]() {
|
||||
def call(pc: CellDataFeatures[ChannelAnnouncement, Number]) = new SimpleLongProperty(pc.getValue.shortChannelId)
|
||||
|
@ -168,10 +194,106 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
networkChannelsTable.setRowFactory(new Callback[TableView[ChannelAnnouncement], TableRow[ChannelAnnouncement]]() {
|
||||
override def call(table: TableView[ChannelAnnouncement]): TableRow[ChannelAnnouncement] = setupPeerChannelContextMenu
|
||||
})
|
||||
|
||||
// init payment sent
|
||||
paymentSentTable.setItems(paymentSentList)
|
||||
paymentSentList.addListener(new ListChangeListener[PaymentSent] {
|
||||
override def onChanged(c: Change[_ <: PaymentSent]) = updateTabHeader(paymentSentTab, "Sent", paymentSentList)
|
||||
})
|
||||
paymentSentAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentSent, Number], ObservableValue[Number]]() {
|
||||
def call(p: CellDataFeatures[PaymentSent, Number]) = new SimpleLongProperty(p.getValue.amount.amount)
|
||||
})
|
||||
paymentSentAmountColumn.setCellFactory(new Callback[TableColumn[PaymentSent, Number], TableCell[PaymentSent, Number]]() {
|
||||
def call(pn: TableColumn[PaymentSent, Number]) = buildMoneyTableCell
|
||||
})
|
||||
paymentSentFeesColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentSent, Number], ObservableValue[Number]]() {
|
||||
def call(p: CellDataFeatures[PaymentSent, Number]) = new SimpleLongProperty(p.getValue.feesPaid.amount)
|
||||
})
|
||||
paymentSentFeesColumn.setCellFactory(new Callback[TableColumn[PaymentSent, Number], TableCell[PaymentSent, Number]]() {
|
||||
def call(pn: TableColumn[PaymentSent, Number]) = buildMoneyTableCell
|
||||
})
|
||||
paymentSentHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentSentDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentSentTable.setRowFactory(paymentRowFactory)
|
||||
|
||||
// init payment received
|
||||
paymentReceivedTable.setItems(paymentReceivedList)
|
||||
paymentReceivedList.addListener(new ListChangeListener[PaymentReceived] {
|
||||
override def onChanged(c: Change[_ <: PaymentReceived]) = updateTabHeader(paymentReceivedTab, "Received", paymentReceivedList)
|
||||
})
|
||||
paymentReceivedAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentReceived, Number], ObservableValue[Number]]() {
|
||||
def call(p: CellDataFeatures[PaymentReceived, Number]) = new SimpleLongProperty(p.getValue.amount.amount)
|
||||
})
|
||||
paymentReceivedAmountColumn.setCellFactory(new Callback[TableColumn[PaymentReceived, Number], TableCell[PaymentReceived, Number]]() {
|
||||
def call(pn: TableColumn[PaymentReceived, Number]) = buildMoneyTableCell
|
||||
})
|
||||
paymentReceivedHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentReceivedDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentReceivedTable.setRowFactory(paymentRowFactory)
|
||||
|
||||
// init payment relayed
|
||||
paymentRelayedTable.setItems(paymentRelayedList)
|
||||
paymentRelayedList.addListener(new ListChangeListener[PaymentRelayed] {
|
||||
override def onChanged(c: Change[_ <: PaymentRelayed]) = updateTabHeader(paymentRelayedTab, "Relayed", paymentRelayedList)
|
||||
})
|
||||
paymentRelayedAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentRelayed, Number], ObservableValue[Number]]() {
|
||||
def call(p: CellDataFeatures[PaymentRelayed, Number]) = new SimpleLongProperty(p.getValue.amount.amount)
|
||||
})
|
||||
paymentRelayedAmountColumn.setCellFactory(new Callback[TableColumn[PaymentRelayed, Number], TableCell[PaymentRelayed, Number]]() {
|
||||
def call(pn: TableColumn[PaymentRelayed, Number]) = buildMoneyTableCell
|
||||
})
|
||||
paymentRelayedFeesColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentRelayed, Number], ObservableValue[Number]]() {
|
||||
def call(p: CellDataFeatures[PaymentRelayed, Number]) = new SimpleLongProperty(p.getValue.feesEarned.amount)
|
||||
})
|
||||
paymentRelayedFeesColumn.setCellFactory(new Callback[TableColumn[PaymentRelayed, Number], TableCell[PaymentRelayed, Number]]() {
|
||||
def call(pn: TableColumn[PaymentRelayed, Number]) = buildMoneyTableCell
|
||||
})
|
||||
paymentRelayedHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentRelayedDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentRelayedTable.setRowFactory(paymentRowFactory)
|
||||
}
|
||||
|
||||
private def updateTabHeader(tab: Tab, prefix: String, items: ObservableList[_]) = {
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = tab.setText(s"$prefix (${items.size})")
|
||||
})
|
||||
}
|
||||
|
||||
private def paymentHashCellValueFactory[T <: PaymentEvent] = new Callback[CellDataFeatures[T, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[T, String]) = new SimpleStringProperty(p.getValue.paymentHash.toString)
|
||||
}
|
||||
|
||||
private def buildMoneyTableCell[T <: PaymentEvent] = new TableCell[T, Number]() {
|
||||
override def updateItem(item: Number, empty: Boolean) = {
|
||||
super.updateItem(item, empty)
|
||||
if (item != null && !empty) setText(moneyFormatter.format(item))
|
||||
}
|
||||
}
|
||||
|
||||
private def paymentDateCellValueFactory[T <: PaymentEvent] = new Callback[CellDataFeatures[T, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[T, String]) = new SimpleStringProperty(LocalDateTime.now.format(PAYMENT_DATE_FORMAT))
|
||||
}
|
||||
|
||||
private def paymentRowFactory[T <: PaymentEvent] = new Callback[TableView[T], TableRow[T]]() {
|
||||
override def call(table: TableView[T]): TableRow[T] = {
|
||||
val row = new TableRow[T]
|
||||
val rowContextMenu = new ContextMenu
|
||||
val copyHash = new MenuItem("Copy Payment Hash")
|
||||
copyHash.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = Option(row.getItem) match {
|
||||
case Some(p) => ContextMenuUtils.copyToClipboard(p.paymentHash.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
rowContextMenu.getItems.addAll(copyHash)
|
||||
row.setContextMenu(rowContextMenu)
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a row for a node with context actions (copy node uri and id).
|
||||
*
|
||||
* @return TableRow the created row
|
||||
*/
|
||||
private def setupPeerNodeContextMenu(): TableRow[NodeAnnouncement] = {
|
||||
|
@ -200,6 +322,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
|||
|
||||
/**
|
||||
* Create a row for a PeerChannel with Copy context actions.
|
||||
*
|
||||
* @return TableRow the created row
|
||||
*/
|
||||
private def setupPeerChannelContextMenu(): TableRow[ChannelAnnouncement] = {
|
||||
|
|
|
@ -51,8 +51,8 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
|
|||
if (GUIValidators.validate(fundingSatoshisError, "Funding must be 16 777 216 satoshis (~0.167 BTC) or less", smartFunding.toLong < maxFunding)) {
|
||||
if (!pushMsat.getText.isEmpty) {
|
||||
// pushMsat is optional, so we validate field only if it isn't empty
|
||||
if (GUIValidators.validate(pushMsat.getText, pushMsatError, "Push mSat must be numeric", GUIValidators.amountRegex)
|
||||
&& GUIValidators.validate(pushMsatError, "Push mSat must be 16 777 216 000 mSat (~0.167 BTC) or less", pushMsat.getText.toLong <= maxPushMsat)) {
|
||||
if (GUIValidators.validate(pushMsat.getText, pushMsatError, "Push msat must be numeric", GUIValidators.amountRegex)
|
||||
&& GUIValidators.validate(pushMsatError, "Push msat must be 16 777 216 000 msat (~0.167 BTC) or less", pushMsat.getText.toLong <= maxPushMsat)) {
|
||||
handlers.open(host.getText, smartFunding, MilliSatoshi(pushMsat.getText.toLong))
|
||||
stage.close()
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class ReceivePaymentController(val handlers: Handlers, val stage: Stage, val set
|
|||
@FXML def handleGenerate(event: ActionEvent) = {
|
||||
if ((("milliBTC".equals(unit.getValue) || "Satoshi".equals(unit.getValue))
|
||||
&& GUIValidators.validate(amount.getText, amountError, "Amount must be numeric", GUIValidators.amountDecRegex))
|
||||
|| ("milliSatoshi".equals(unit.getValue) && GUIValidators.validate(amount.getText, amountError, "Amount must be numeric (no decimal mSat)", GUIValidators.amountRegex))) {
|
||||
|| ("milliSatoshi".equals(unit.getValue) && GUIValidators.validate(amount.getText, amountError, "Amount must be numeric (no decimal msat)", GUIValidators.amountRegex))) {
|
||||
try {
|
||||
val Array(parsedInt, parsedDec) = if (amount.getText.contains(".")) amount.getText.split("\\.") else Array(amount.getText, "0")
|
||||
val amountDec = parsedDec.length match {
|
||||
|
@ -48,7 +48,7 @@ class ReceivePaymentController(val handlers: Handlers, val stage: Stage, val set
|
|||
}
|
||||
logger.debug(s"Final amount for payment request = $smartAmount")
|
||||
if (GUIValidators.validate(amountError, "Amount must be greater than 0", smartAmount.amount > 0)
|
||||
&& GUIValidators.validate(amountError, "Must be less than 4 294 967 295 mSat (~0.042 BTC)", smartAmount.amount < 4294967295L)) {
|
||||
&& GUIValidators.validate(amountError, "Must be less than 4 294 967 295 msat (~0.042 BTC)", smartAmount.amount < 4294967295L)) {
|
||||
handlers.getPaymentRequest(smartAmount.amount, paymentRequest)
|
||||
}
|
||||
} catch {
|
||||
|
|
|
@ -68,7 +68,7 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage, val setup:
|
|||
Try(amount.toLong) match {
|
||||
case Success(amountLong) =>
|
||||
if (GUIValidators.validate(paymentRequestError, "Amount must be greater than 0", amountLong > 0)
|
||||
&& GUIValidators.validate(paymentRequestError, "Amount must be less than 4 294 967 295 mSat (~0.042 BTC)", amountLong < 4294967295L)) {
|
||||
&& GUIValidators.validate(paymentRequestError, "Amount must be less than 4 294 967 295 msat (~0.042 BTC)", amountLong < 4294967295L)) {
|
||||
Try (handlers.send(PublicKey(nodeId), BinaryData(hash), amountLong)) match {
|
||||
case Success(s) => stage.close
|
||||
case Failure(f) => GUIValidators.validate(paymentRequestError, s"Invalid Payment Request: ${f.getMessage}", false)
|
||||
|
|
|
@ -26,20 +26,13 @@ class SplashController(hostServices: HostServices) extends Logging {
|
|||
* Start an animation when the splash window is initialized
|
||||
*/
|
||||
@FXML def initialize = {
|
||||
val t = new HBox()
|
||||
t.prefHeightProperty()
|
||||
|
||||
val timeline = new Timeline()
|
||||
|
||||
val startKF = new KeyFrame(Duration.ZERO,
|
||||
val timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(img.opacityProperty, double2Double(0), Interpolator.EASE_IN),
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(1.0), Interpolator.EASE_IN))
|
||||
|
||||
val endKF = new KeyFrame(Duration.millis(1000.0d),
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(1.0), Interpolator.EASE_IN)),
|
||||
new KeyFrame(Duration.millis(1000.0d),
|
||||
new KeyValue(img.opacityProperty, double2Double(1.0), Interpolator.EASE_OUT),
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(0), Interpolator.EASE_OUT))
|
||||
|
||||
timeline.getKeyFrames.addAll(startKF, endKF)
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(0), Interpolator.EASE_OUT)))
|
||||
timeline.play()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.{Actor, ActorLogging}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi}
|
||||
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC}
|
||||
import fr.acinq.eclair.wire.{UnknownPaymentHash, UpdateAddHtlc}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class LocalPaymentHandler extends Actor with ActorLogging {
|
|||
case htlc: UpdateAddHtlc if h2r.contains(htlc.paymentHash) =>
|
||||
val r = h2r(htlc.paymentHash)
|
||||
sender ! CMD_FULFILL_HTLC(htlc.id, r, commit = true)
|
||||
context.system.eventStream.publish(PaymentReceived(self, htlc.paymentHash))
|
||||
context.system.eventStream.publish(PaymentReceived(MilliSatoshi(htlc.amountMsat), htlc.paymentHash))
|
||||
context.become(run(h2r - htlc.paymentHash))
|
||||
|
||||
case htlc: UpdateAddHtlc =>
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
||||
|
||||
/**
|
||||
* Created by PM on 01/02/2017.
|
||||
*/
|
||||
class PaymentEvent
|
||||
sealed trait PaymentEvent {
|
||||
val paymentHash: BinaryData
|
||||
}
|
||||
|
||||
//case class PaymentSent(channel: ActorRef, h: BinaryData) extends PaymentEvent
|
||||
case class PaymentSent(amount: MilliSatoshi, feesPaid: MilliSatoshi, paymentHash: BinaryData) extends PaymentEvent
|
||||
|
||||
//case class PaymentFailed(channel: ActorRef, h: BinaryData, reason: String) extends PaymentEvent
|
||||
case class PaymentRelayed(amount: MilliSatoshi, feesEarned: MilliSatoshi, paymentHash: BinaryData) extends PaymentEvent
|
||||
|
||||
case class PaymentReceived(channel: ActorRef, h: BinaryData) extends PaymentEvent
|
||||
case class PaymentReceived(amount: MilliSatoshi, paymentHash: BinaryData) extends PaymentEvent
|
||||
|
|
|
@ -2,7 +2,7 @@ package fr.acinq.eclair.payment
|
|||
|
||||
import akka.actor.Status.Failure
|
||||
import akka.actor.{ActorRef, FSM, LoggingFSM, Props, Status}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.channel.{CMD_ADD_HTLC, Register}
|
||||
|
@ -23,7 +23,7 @@ case class PaymentFailed(paymentHash: BinaryData, error: Option[ErrorPacket]) ex
|
|||
sealed trait Data
|
||||
case object WaitingForRequest extends Data
|
||||
case class WaitingForRoute(sender: ActorRef, c: CreatePayment, attempts: Int) extends Data
|
||||
case class WaitingForComplete(sender: ActorRef, c: CreatePayment, attempts: Int, sharedSecrets: Seq[(BinaryData, PublicKey)], ignoreNodes: Set[PublicKey], ignoreChannels: Set[Long], hops: Seq[Hop]) extends Data
|
||||
case class WaitingForComplete(sender: ActorRef, c: CreatePayment, cmd: CMD_ADD_HTLC, attempts: Int, sharedSecrets: Seq[(BinaryData, PublicKey)], ignoreNodes: Set[PublicKey], ignoreChannels: Set[Long], hops: Seq[Hop]) extends Data
|
||||
|
||||
sealed trait State
|
||||
case object WAITING_FOR_REQUEST extends State
|
||||
|
@ -52,7 +52,7 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
|
|||
val firstHop = hops.head
|
||||
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, c.paymentHash, hops, Globals.blockCount.get().toInt)
|
||||
register ! Register.ForwardShortId(firstHop.lastUpdate.shortChannelId, cmd)
|
||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, attempts + 1, sharedSecrets, ignoreNodes, ignoreChannels, hops)
|
||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, c, cmd, attempts + 1, sharedSecrets, ignoreNodes, ignoreChannels, hops)
|
||||
|
||||
case Event(f@Failure(t), WaitingForRoute(s, c, _)) =>
|
||||
s ! PaymentFailed(c.paymentHash, error = None)
|
||||
|
@ -68,9 +68,10 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
|
|||
|
||||
case Event(fulfill: UpdateFulfillHtlc, w: WaitingForComplete) =>
|
||||
w.sender ! PaymentSucceeded(fulfill.paymentPreimage)
|
||||
context.system.eventStream.publish(PaymentSent(MilliSatoshi(w.c.amountMsat), MilliSatoshi(w.cmd.amountMsat - w.c.amountMsat), w.cmd.paymentHash))
|
||||
stop(FSM.Normal)
|
||||
|
||||
case Event(fail: UpdateFailHtlc, WaitingForComplete(s, c, attempts, sharedSecrets, ignoreNodes, ignoreChannels, hops)) =>
|
||||
case Event(fail: UpdateFailHtlc, WaitingForComplete(s, c, _, attempts, sharedSecrets, ignoreNodes, ignoreChannels, hops)) =>
|
||||
Sphinx.parseErrorPacket(fail.reason, sharedSecrets) match {
|
||||
case e@Some(ErrorPacket(nodeId, failureMessage)) if nodeId == c.targetNodeId =>
|
||||
// TODO: spec says: that MAY retry the payment in certain conditions, see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#receiving-failure-codes
|
||||
|
|
|
@ -2,7 +2,7 @@ package fr.acinq.eclair.payment
|
|||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, ripemd160, sha256}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, ScriptWitness, Transaction}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, ScriptWitness, Transaction}
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.blockchain.WatchEventSpent
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -20,7 +20,7 @@ case class OutgoingChannel(channelId: BinaryData, channel: ActorRef, nodeAddress
|
|||
|
||||
sealed trait Origin
|
||||
case class Local(sender: ActorRef) extends Origin
|
||||
case class Relayed(upstream: UpdateAddHtlc) extends Origin
|
||||
case class Relayed(upstream: ActorRef, htlcIn: UpdateAddHtlc) extends Origin
|
||||
|
||||
case class AddHtlcSucceeded(add: UpdateAddHtlc, origin: Origin)
|
||||
case class AddHtlcFailed(add: CMD_ADD_HTLC, failure: FailureMessage)
|
||||
|
@ -42,9 +42,7 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
|
|||
|
||||
override def receive: Receive = main(Set(), Map(), Map(), Map())
|
||||
|
||||
case class DownstreamHtlcId(channelId: BinaryData, htlcId: Long)
|
||||
|
||||
def main(channels: Set[OutgoingChannel], bindings: Map[DownstreamHtlcId, Origin], shortIds: Map[BinaryData, Long], channelUpdates: Map[Long, ChannelUpdate]): Receive = {
|
||||
def main(channels: Set[OutgoingChannel], bindings: Map[UpdateAddHtlc, Origin], shortIds: Map[BinaryData, Long], channelUpdates: Map[Long, ChannelUpdate]): Receive = {
|
||||
|
||||
case ChannelStateChanged(channel, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
|
||||
import d.commitments.channelId
|
||||
|
@ -95,7 +93,7 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
|
|||
case _ =>
|
||||
val downstream = outgoingChannel.channel
|
||||
log.info(s"forwarding htlc #${add.id} to downstream=$downstream")
|
||||
downstream ! CMD_ADD_HTLC(perHopPayload.amt_to_forward, add.paymentHash, perHopPayload.outgoing_cltv_value, nextPacket, upstream_opt = Some(add), commit = true)
|
||||
downstream forward CMD_ADD_HTLC(perHopPayload.amt_to_forward, add.paymentHash, perHopPayload.outgoing_cltv_value, nextPacket, upstream_opt = Some(add), commit = true)
|
||||
}
|
||||
case Success((Attempt.Successful(DecodeResult(_, _)), nextNodeAddress, _, sharedSecret)) =>
|
||||
log.warning(s"couldn't resolve downstream node address $nextNodeAddress, failing htlc #${add.id}")
|
||||
|
@ -112,57 +110,50 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
|
|||
case AddHtlcSucceeded(downstream, origin) =>
|
||||
origin match {
|
||||
case Local(_) => log.info(s"we are the origin of htlc ${downstream.channelId}/${downstream.id}")
|
||||
case Relayed(upstream) => log.info(s"relayed htlc ${upstream.channelId}/${upstream.id} to ${downstream}/${downstream.id}")
|
||||
case Relayed(_, upstream) => log.info(s"relayed htlc ${upstream.channelId}/${upstream.id} to ${downstream.channelId}/${downstream.id}")
|
||||
}
|
||||
context become main(channels, bindings + (DownstreamHtlcId(downstream.channelId, downstream.id) -> origin), shortIds, channelUpdates)
|
||||
context become main(channels, bindings + (downstream -> origin), shortIds, channelUpdates)
|
||||
|
||||
case AddHtlcFailed(CMD_ADD_HTLC(_, _, _, onion, Some(updateAddHtlc), _), failure) if channels.exists(_.channelId == updateAddHtlc.channelId) =>
|
||||
val upstream = channels.find(_.channelId == updateAddHtlc.channelId).get.channel
|
||||
upstream ! CMD_FAIL_HTLC(updateAddHtlc.id, Right(failure), commit = true)
|
||||
|
||||
case ForwardFulfill(fulfill) =>
|
||||
val downstream = DownstreamHtlcId(fulfill.channelId, fulfill.id)
|
||||
bindings.get(downstream) match {
|
||||
case Some(Relayed(origin)) if channels.exists(_.channelId == origin.channelId) =>
|
||||
val upstream = channels.find(_.channelId == origin.channelId).get.channel
|
||||
upstream ! CMD_FULFILL_HTLC(origin.id, fulfill.paymentPreimage, commit = true)
|
||||
case Some(Relayed(origin)) =>
|
||||
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
|
||||
case Some(Local(origin)) =>
|
||||
bindings.find(b => b._1.channelId == fulfill.channelId && b._1.id == fulfill.id) match {
|
||||
case Some((htlcOut, Relayed(upstream, htlcIn))) =>
|
||||
upstream ! CMD_FULFILL_HTLC(htlcIn.id, fulfill.paymentPreimage, commit = true)
|
||||
context.system.eventStream.publish(PaymentRelayed(MilliSatoshi(htlcIn.amountMsat), MilliSatoshi(htlcIn.amountMsat - htlcOut.amountMsat), htlcIn.paymentHash))
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case Some((htlcOut, Local(origin))) =>
|
||||
log.info(s"we were the origin payer for htlc #${fulfill.id}")
|
||||
origin ! fulfill
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case None =>
|
||||
log.warning(s"no origin found for htlc ${fulfill.channelId}/${fulfill.id}")
|
||||
}
|
||||
context become (main(channels, bindings - downstream, shortIds, channelUpdates))
|
||||
|
||||
case ForwardFail(fail) =>
|
||||
val downstream = DownstreamHtlcId(fail.channelId, fail.id)
|
||||
bindings.get(downstream) match {
|
||||
case Some(Relayed(origin)) if channels.exists(_.channelId == origin.channelId) =>
|
||||
val upstream = channels.find(_.channelId == origin.channelId).get.channel
|
||||
upstream ! CMD_FAIL_HTLC(origin.id, Left(fail.reason), commit = true)
|
||||
case Some(Relayed(origin)) =>
|
||||
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
|
||||
case Some(Local(origin)) =>
|
||||
bindings.find(b => b._1.channelId == fail.channelId && b._1.id == fail.id) match {
|
||||
case Some((htlcOut, Relayed(upstream, htlcIn))) =>
|
||||
upstream ! CMD_FAIL_HTLC(htlcIn.id, Left(fail.reason), commit = true)
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case Some((htlcOut, Local(origin))) =>
|
||||
log.info(s"we were the origin payer for htlc #${fail.id}")
|
||||
origin ! fail
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case None =>
|
||||
log.warning(s"no origin found for htlc ${fail.channelId}/${fail.id}")
|
||||
}
|
||||
context become (main(channels, bindings - downstream, shortIds, channelUpdates))
|
||||
|
||||
case ForwardFailMalformed(fail) =>
|
||||
val downstream = DownstreamHtlcId(fail.channelId, fail.id)
|
||||
bindings.get(downstream) match {
|
||||
case Some(Relayed(origin)) if channels.exists(_.channelId == origin.channelId) =>
|
||||
val upstream = channels.find(_.channelId == origin.channelId).get.channel
|
||||
upstream ! CMD_FAIL_MALFORMED_HTLC(origin.id, fail.onionHash, fail.failureCode, commit = true)
|
||||
case Some(Relayed(origin)) =>
|
||||
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
|
||||
case Some(Local(origin)) =>
|
||||
bindings.find(b => b._1.channelId == fail.channelId && b._1.id == fail.id) match {
|
||||
case Some((htlcOut, Relayed(upstream, htlcIn))) =>
|
||||
upstream ! CMD_FAIL_MALFORMED_HTLC(htlcIn.id, fail.onionHash, fail.failureCode, commit = true)
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case Some((htlcOut, Local(origin))) =>
|
||||
log.info(s"we were the origin payer for htlc #${fail.id}")
|
||||
origin ! fail
|
||||
context become main(channels, bindings - htlcOut, shortIds, channelUpdates)
|
||||
case None =>
|
||||
log.warning(s"no origin found for htlc ${fail.channelId}/${fail.id}")
|
||||
}
|
||||
|
@ -190,30 +181,19 @@ class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor wi
|
|||
log.warning(s"extracted paymentHash160=$paymentHash160 from tx=${Transaction.write(tx)} (claim-htlc-timeout)")
|
||||
paymentHash160
|
||||
}
|
||||
val downstreams = bindings.collect {
|
||||
case b@(downstream, Relayed(origin)) if origin.paymentHash == sha256(extracted) =>
|
||||
log.warning(s"found a match between preimage=$extracted and origin htlc=$origin")
|
||||
channels.find(_.channelId == origin.channelId) match {
|
||||
case Some(outgoingChannel) =>
|
||||
val upstream = outgoingChannel.channel
|
||||
upstream ! CMD_FULFILL_HTLC(origin.id, extracted, commit = true)
|
||||
case None => log.warning(s"could not find channel for channelId=${origin.channelId}")
|
||||
val htlcsOut = bindings.collect {
|
||||
case b@(htlcOut, Relayed(upstream, htlcIn)) if htlcIn.paymentHash == sha256(extracted) =>
|
||||
log.warning(s"found a match between preimage=$extracted and origin htlc=$htlcIn")
|
||||
upstream ! CMD_FULFILL_HTLC(htlcIn.id, extracted, commit = true)
|
||||
htlcOut
|
||||
case b@(htlcOut, Relayed(upstream, htlcIn)) if ripemd160(htlcIn.paymentHash) == extracted =>
|
||||
log.warning(s"found a match between paymentHash160=$extracted and origin htlc=$htlcIn")
|
||||
upstream ! CMD_FAIL_HTLC(htlcIn.id, Right(PermanentChannelFailure), commit = true)
|
||||
htlcOut
|
||||
}
|
||||
downstream
|
||||
case b@(downstream, Relayed(origin)) if ripemd160(origin.paymentHash) == extracted =>
|
||||
log.warning(s"found a match between paymentHash160=$extracted and origin htlc=$origin")
|
||||
channels.find(_.channelId == origin.channelId) match {
|
||||
case Some(outgoingChannel) =>
|
||||
val upstream = outgoingChannel.channel
|
||||
upstream ! CMD_FAIL_HTLC(origin.id, Right(PermanentChannelFailure), commit = true)
|
||||
case None => log.warning(s"could not find channel for channelId=${origin.channelId}")
|
||||
}
|
||||
downstream
|
||||
}
|
||||
context become (main(channels, bindings -- downstreams, shortIds, channelUpdates))
|
||||
context become main(channels, bindings -- htlcsOut, shortIds, channelUpdates)
|
||||
|
||||
case 'channels
|
||||
=> sender ! channels
|
||||
case 'channels => sender ! channels
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
localNextHtlcId = 1,
|
||||
localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil),
|
||||
unackedMessages = initialState.commitments.unackedMessages :+ htlc)))
|
||||
relayer.expectMsg(AddHtlcSucceeded(htlc, origin = Relayed(originHtlc)))
|
||||
relayer.expectMsg(AddHtlcSucceeded(htlc, origin = Relayed(sender.ref, originHtlc)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package fr.acinq.eclair.payment
|
|||
|
||||
import akka.actor.{ActorSystem, Props}
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi}
|
||||
import fr.acinq.eclair.channel.CMD_FULFILL_HTLC
|
||||
import fr.acinq.eclair.wire.UpdateAddHtlc
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -25,9 +25,12 @@ class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
|
|||
sender.send(handler, 'genh)
|
||||
val paymentHash = sender.expectMsgType[BinaryData]
|
||||
|
||||
sender.send(handler, UpdateAddHtlc("11" * 32, 0, 0, 0, paymentHash, ""))
|
||||
|
||||
val add = UpdateAddHtlc("11" * 32, 0, 42000, 0, paymentHash, "")
|
||||
sender.send(handler, add)
|
||||
|
||||
sender.expectMsgType[CMD_FULFILL_HTLC]
|
||||
eventListener.expectMsgType[PaymentReceived]
|
||||
eventListener.expectMsg(PaymentReceived(MilliSatoshi(add.amountMsat), add.paymentHash))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package fr.acinq.eclair.payment
|
|||
|
||||
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.MilliSatoshi
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.router.BaseRouterSpec
|
||||
|
@ -45,7 +46,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
|||
sender.send(paymentFSM, request)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||
awaitCond(paymentFSM.stateName == WAITING_FOR_PAYMENT_COMPLETE)
|
||||
val WaitingForComplete(_, add, _, sharedSecrets, _, _, _) = paymentFSM.stateData
|
||||
val WaitingForComplete(_, c, cmd, _, sharedSecrets, _, _, _) = paymentFSM.stateData
|
||||
|
||||
sender.send(paymentFSM, UpdateFailHtlc("00" * 32, 0, Sphinx.createErrorPacket(sharedSecrets(0)._1, TemporaryChannelFailure)))
|
||||
|
||||
|
@ -70,6 +71,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
|||
sender.send(paymentFSM, UpdateFulfillHtlc("00" * 32, 0, "42" * 32))
|
||||
|
||||
sender.expectMsgType[PaymentSucceeded]
|
||||
val PaymentSent(MilliSatoshi(request.amountMsat), feesPaid, request.paymentHash) = eventListener.expectMsgType[PaymentSent]
|
||||
assert(feesPaid.amount > 0)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package fr.acinq.eclair.payment
|
|||
import akka.actor.ActorRef
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, OutPoint, Transaction, TxIn}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, OutPoint, Transaction, TxIn}
|
||||
import fr.acinq.eclair.TestkitBaseClass
|
||||
import fr.acinq.eclair.blockchain.WatchEventSpent
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -101,6 +101,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
|
||||
sender.expectNoMsg(1 second)
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
|
||||
paymentHandler.expectNoMsg(1 second)
|
||||
|
||||
assert(cmd_bc.upstream_opt === Some(add_ab))
|
||||
|
@ -198,6 +199,9 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
val sender = TestProbe()
|
||||
val channel_ab = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
val eventListener = TestProbe()
|
||||
|
||||
system.eventStream.subscribe(eventListener.ref, classOf[PaymentEvent])
|
||||
|
||||
val add_ab = {
|
||||
val (cmd, _) = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
|
@ -211,15 +215,16 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(add_ab)))
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(channel_ab.ref, add_ab)))
|
||||
// preimage is wrong, does not matter here
|
||||
val fulfill_cb = UpdateFulfillHtlc(channelId = add_bc.channelId, id = add_bc.id, paymentPreimage = "00" * 32)
|
||||
sender.send(relayer, ForwardFulfill(fulfill_cb))
|
||||
|
||||
val fulfill_ba = channel_ab.expectMsgType[CMD_FULFILL_HTLC]
|
||||
|
||||
assert(fulfill_ba.id === add_ab.id)
|
||||
eventListener.expectMsg(PaymentRelayed(MilliSatoshi(add_ab.amountMsat), MilliSatoshi(add_ab.amountMsat - cmd_bc.amountMsat), add_ab.paymentHash))
|
||||
|
||||
assert(fulfill_ba.id === add_ab.id)
|
||||
}
|
||||
|
||||
test("relay an htlc-fail") { case (relayer, paymentHandler) =>
|
||||
|
@ -239,7 +244,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(add_ab)))
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(channel_ab.ref, add_ab)))
|
||||
val fail_cb = UpdateFailHtlc(channelId = add_bc.channelId, id = add_bc.id, reason = Sphinx.createErrorPacket(BinaryData("01" * 32), TemporaryChannelFailure))
|
||||
sender.send(relayer, ForwardFail(fail_cb))
|
||||
|
||||
|
@ -266,7 +271,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(add_ab)))
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(channel_ab.ref, add_ab)))
|
||||
|
||||
// actual test starts here
|
||||
val tx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessHtlcSuccess("11" * 70, "22" * 70, paymentPreimage, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
||||
|
@ -294,7 +299,7 @@ class RelayerSpec extends TestkitBaseClass {
|
|||
sender.send(relayer, ForwardAdd(add_ab))
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
val add_bc = UpdateAddHtlc(channelId = channelId_bc, id = 987451, amountMsat = cmd_bc.amountMsat, expiry = cmd_bc.expiry, paymentHash = cmd_bc.paymentHash, onionRoutingPacket = cmd_bc.onion)
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(add_ab)))
|
||||
sender.send(relayer, AddHtlcSucceeded(add_bc, Relayed(channel_ab.ref, add_ab)))
|
||||
|
||||
// actual test starts here
|
||||
val tx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessClaimHtlcSuccessFromCommitTx("11" * 70, paymentPreimage, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
||||
|
|
Loading…
Add table
Reference in a new issue