mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 06:21:42 +01:00
Merge branch 'wip-bolt7' into wip-bolts
This commit is contained in:
commit
5efa71eb5d
94 changed files with 1975 additions and 2331 deletions
BIN
.readme/logo.png
BIN
.readme/logo.png
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 16 KiB |
|
@ -82,26 +82,6 @@
|
|||
<version>${bitcoinlib.version}</version>
|
||||
</dependency>
|
||||
<!-- SERIALIZATION -->
|
||||
<dependency>
|
||||
<groupId>fr.acinq.eclair</groupId>
|
||||
<artifactId>lightning-types_${scala.version.short}</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-java</artifactId>
|
||||
<version>2.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.trueaccord.scalapb</groupId>
|
||||
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
|
||||
<version>${scalapb.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.trueaccord.lenses</groupId>
|
||||
<artifactId>lenses_${scala.version.short}</artifactId>
|
||||
<version>0.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.scodec</groupId>
|
||||
<artifactId>scodec-core_${scala.version.short}</artifactId>
|
||||
|
@ -125,20 +105,15 @@
|
|||
<version>2.5.10</version>
|
||||
</dependency>
|
||||
<!-- OTHER -->
|
||||
<dependency>
|
||||
<groupId>org.kitteh.irc</groupId>
|
||||
<artifactId>client-lib</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
<artifactId>jgrapht-core</artifactId>
|
||||
<version>0.9.2</version>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jgrapht</groupId>
|
||||
<artifactId>jgrapht-ext</artifactId>
|
||||
<version>0.9.2</version>
|
||||
<version>1.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- This is to get rid of '[WARNING] warning: Class javax.annotation.Nonnull not found - continuing with a stub.' compile errors -->
|
||||
|
|
|
@ -17,11 +17,19 @@ eclair {
|
|||
}
|
||||
node {
|
||||
seed = 0102030405060708010203040506070801020304050607080102030405060708
|
||||
alias = "eclair"
|
||||
color {
|
||||
r = 73
|
||||
g = 218
|
||||
b = 170
|
||||
}
|
||||
}
|
||||
delay-blocks = 144
|
||||
mindepth-blocks = 3
|
||||
base-fee = 546000
|
||||
proportional-fee = 10
|
||||
expiry-delta-blocks = 144
|
||||
htlc-minimum-msat = 1000000
|
||||
fee-base-msat = 546000
|
||||
fee-proportional-msat = 10
|
||||
payment-handler = "local"
|
||||
}
|
||||
akka {
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
-fx-padding: 1em;
|
||||
}
|
||||
|
||||
/* ------------- TEXT FIELD ------------- */
|
||||
|
||||
.text-field, .text-area {
|
||||
-fx-padding: .6em;
|
||||
-fx-background-insets: 0;
|
||||
|
@ -12,16 +14,33 @@
|
|||
-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;
|
||||
}
|
||||
|
||||
.text-field:focused {
|
||||
-fx-border-color: rgb(63,179,234);
|
||||
/* ------------- NOT EDITABLE ------------- */
|
||||
/* Java FX text can only be selected if in a TextField or TextArea */
|
||||
/* Make it look like a standard label with the editable = false prop and a special styling */
|
||||
.text-area.noteditable,
|
||||
.text-field.noteditable,
|
||||
.text-field.noteditable:hover,
|
||||
.text-field.noteditable:focused,
|
||||
.noteditable {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0;
|
||||
}
|
|
@ -21,13 +21,17 @@
|
|||
-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: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 .text {
|
||||
-fx-fill: white;
|
||||
|
@ -57,10 +61,12 @@
|
|||
}
|
||||
.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);
|
||||
|
@ -76,4 +82,23 @@
|
|||
}
|
||||
.text-error.text-error-upward {
|
||||
-fx-translate-y: -6px;
|
||||
}
|
||||
}
|
||||
|
||||
.label-description {
|
||||
-fx-text-fill: rgb(156,159,161);
|
||||
-fx-font-size: 10px;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
-fx-padding: 4px;
|
||||
-fx-font-weight: normal;
|
||||
}
|
||||
.context-menu .menu-item:focused {
|
||||
-fx-background-color: rgb(63,179,234);
|
||||
}
|
||||
.context-menu .menu-item:focused .label {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
.context-menu .separator {
|
||||
-fx-padding: 2px 0;
|
||||
}
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
-fx-background-color: white;
|
||||
-fx-background-image: url("../commons/images/close.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: .7em .7em;
|
||||
-fx-background-position: 1em center;
|
||||
-fx-padding: .5em 1em .5em 2.2em;
|
||||
-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 {
|
||||
|
|
|
@ -3,43 +3,47 @@
|
|||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<VBox styleClass="channel" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<VBox styleClass="channel" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
|
||||
onContextMenuRequested="#openChannelContext" onMouseClicked="#closeChannelContext">
|
||||
<children>
|
||||
<GridPane maxWidth="1.7976931348623157E308" styleClass="grid">
|
||||
<GridPane styleClass="grid" prefWidth="400.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="125.0" minWidth="10.0" prefWidth="125.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="114.0" minWidth="10.0" prefWidth="114.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="70.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="100.0" prefWidth="140.0" maxWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="30.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="40.0" prefWidth="60.0" maxWidth="60.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="40.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="15.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="8.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label styleClass="text-muted" text="Node id" GridPane.rowIndex="2"/>
|
||||
<Label styleClass="text-muted" text="Capacity (millibits)" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Capacity (milliBTC)" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Funder" GridPane.columnIndex="2" GridPane.rowIndex="3"/>
|
||||
<Label styleClass="text-muted" text="State" GridPane.columnIndex="2" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Your balance (milliBTC)" GridPane.rowIndex="3"/>
|
||||
<Button fx:id="close" mnemonicParsing="false" styleClass="close-channel" text="Close"
|
||||
GridPane.columnIndex="4" GridPane.halignment="RIGHT"/>
|
||||
<Label fx:id="nodeId" onContextMenuRequested="#handleTheirNodeIdContext" text="N/A"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="4" GridPane.rowIndex="2"/>
|
||||
<Label fx:id="capacity" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<Label fx:id="funder" text="N/A" GridPane.columnIndex="3" GridPane.columnSpan="2"
|
||||
GridPane.rowIndex="3"/>
|
||||
<Label fx:id="state" text="N/A" GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="4"/>
|
||||
<Label fx:id="channelId" onContextMenuRequested="#handleChannelIdContext" styleClass="text-strong"
|
||||
text="N/A" GridPane.columnSpan="4"/>
|
||||
<Label fx:id="amountUs" prefWidth="100.0" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<ProgressBar fx:id="balanceBar" maxWidth="1.7976931348623157E308" minHeight="5.0" prefHeight="8.0"
|
||||
prefWidth="1045.0" progress="0.0" snapToPixel="false" GridPane.columnSpan="5"
|
||||
<TextField fx:id="nodeId" text="N/A" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="4" GridPane.rowIndex="2"/>
|
||||
<TextField fx:id="capacity" text="N/A" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="funder" text="N/A" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="3"/>
|
||||
<TextField fx:id="state" text="N/A" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="3" GridPane.columnSpan="2" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="channelId" text="N/A" editable="false" styleClass="noteditable, text-strong"
|
||||
GridPane.columnSpan="4"/>
|
||||
<TextField fx:id="amountUs" text="N/A" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<ProgressBar fx:id="balanceBar" minHeight="4.0" prefHeight="4.0" maxWidth="1.7976931348623157E308"
|
||||
progress="0.0" snapToPixel="false" GridPane.columnSpan="5"
|
||||
GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
|
||||
<Label styleClass="text-muted" text="Your balance (millibits)" GridPane.rowIndex="3"/>
|
||||
</children>
|
||||
</GridPane>
|
||||
<HBox styleClass="channel-separator"/>
|
||||
|
|
|
@ -74,5 +74,5 @@
|
|||
}
|
||||
.tab-content-area {
|
||||
-fx-background-color: white;
|
||||
-fx-padding: .5em 1em;
|
||||
-fx-padding: .5em 1em 0;
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
prefHeight="346.0" prefWidth="650.0" xmlns="http://javafx.com/javafx/8.0.60"
|
||||
<BorderPane fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minWidth="-Infinity"
|
||||
prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1">
|
||||
<center>
|
||||
<TabPane prefHeight="250.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab closable="false" text="Channels">
|
||||
<Tab fx:id="channelsTab" closable="false" text="Channels">
|
||||
<content>
|
||||
<StackPane>
|
||||
<children>
|
||||
|
@ -51,7 +51,7 @@
|
|||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT">
|
||||
<HBox alignment="CENTER_LEFT" onContextMenuRequested="#openNodeIdContext" onMouseClicked="#closeNodeIdContext">
|
||||
<children>
|
||||
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true"
|
||||
preserveRatio="true">
|
||||
|
@ -59,7 +59,7 @@
|
|||
<Image url="@../commons/images/eclair-shape.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
<Label fx:id="labelNodeId" onContextMenuRequested="#handleNodeIdContext" text="N/A"/>
|
||||
<Label fx:id="labelNodeId" text="N/A"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_RIGHT" GridPane.columnIndex="1">
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<?import java.lang.*?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
|
||||
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0"/>
|
||||
|
|
|
@ -10,11 +10,13 @@
|
|||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="216.0" prefWidth="982.0" styleClass="grid" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="528.0"
|
||||
styleClass="grid" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="358.0" minWidth="10.0" prefWidth="137.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="618.0" minWidth="10.0" prefWidth="618.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="258.0" minWidth="10.0" prefWidth="157.0" halignment="RIGHT" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="263.0" minWidth="10.0" prefWidth="176.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="171.0" minWidth="10.0" prefWidth="145.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
|
@ -22,11 +24,13 @@
|
|||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="50.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<TextField fx:id="host" prefHeight="38.0" prefWidth="742.0" promptText="pubkey@host:port1" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="amount" prefHeight="31.0" prefWidth="123.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||
<TextField fx:id="host" prefWidth="313.0" promptText="pubkey@host:port1" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="fundingSatoshis" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||
<ComboBox fx:id="unit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
|
@ -36,12 +40,35 @@
|
|||
</FXCollections>
|
||||
</items>
|
||||
</ComboBox>
|
||||
<Button fx:id="button" defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect" GridPane.columnIndex="1" GridPane.rowIndex="4" GridPane.valignment="BOTTOM" />
|
||||
<Label text="Target Node URL" GridPane.rowIndex="1" />
|
||||
<Label text="Amount" GridPane.rowIndex="3" />
|
||||
<Label fx:id="hostError" opacity="0.0" styleClass="text-error" text="Please use a valid url (pubkey@host:port)" GridPane.columnIndex="1" GridPane.columnSpan="2" />
|
||||
<Label fx:id="amountError" opacity="0.0" styleClass="text-error" text="Please use a valid amount" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2" />
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="4" GridPane.valignment="BOTTOM" />
|
||||
<TextField fx:id="pushMsat" layoutX="193.0" layoutY="112.0" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="5" />
|
||||
<Button fx:id="button" defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="6" GridPane.valignment="BOTTOM" />
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
|
||||
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="6" GridPane.valignment="BOTTOM" />
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="3">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Capacity *" />
|
||||
<Label styleClass="label-description" text="Funding capacity of the channel." textAlignment="RIGHT" wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
<Label fx:id="hostError" opacity="0.0" styleClass="text-error" text="Generic Invalid URI"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" />
|
||||
<Label fx:id="fundingSatoshisError" opacity="0.0" styleClass="text-error" text="Generic Invalid Funding"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2" />
|
||||
<Label fx:id="pushMsatError" layoutX="193.0" layoutY="89.0" opacity="0.0" styleClass="text-error" text="Generic Invalid Push"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4" />
|
||||
<VBox alignment="CENTER_RIGHT" layoutX="24.0" layoutY="112.0" GridPane.rowIndex="5">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Push Amount" />
|
||||
<Label styleClass="label-description" text="Optional amount in millisatoshi. Sent when opening channel" textAlignment="RIGHT" wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
<VBox alignment="CENTER_RIGHT" layoutX="24.0" layoutY="112.0" GridPane.rowIndex="1">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Target Node URI *" />
|
||||
<Label styleClass="label-description" text="Address of the node" textAlignment="RIGHT" wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<stylesheets>
|
||||
<URL value="@modals.css" />
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<?import javafx.scene.text.Text?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" styleClass="grid"
|
||||
xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
|
||||
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="274.0" minWidth="10.0" prefWidth="242.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
|
||||
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="210.0" minWidth="10.0" prefWidth="200.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" maxWidth="100.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
|
@ -19,19 +19,24 @@
|
|||
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label text="Amount (mSat)" GridPane.rowIndex="1"/>
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="1">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Amount (mSat)" />
|
||||
<Label styleClass="label-description" text="Amount to send, in milli satoshi." />
|
||||
</children>
|
||||
</VBox>
|
||||
<TextField fx:id="amount" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
|
||||
<Button defaultButton="true" mnemonicParsing="false" onAction="#handleGenerate" prefHeight="29.0"
|
||||
prefWidth="89.0" text="Generate" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
prefWidth="95.0" text="Generate" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
<TextArea fx:id="paymentRequest" editable="false" prefHeight="150.0" prefWidth="275.0" styleClass="ta"
|
||||
wrapText="true" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="4"/>
|
||||
<Separator prefHeight="1.0" GridPane.columnSpan="3" GridPane.rowIndex="3"/>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0"
|
||||
text="Copy the generated Payment Request and send it to the person owing you the amount above"
|
||||
text="Send this Payment Request to the person owing you the amount above"
|
||||
textAlignment="RIGHT" wrappingWidth="188.9560546875" GridPane.rowIndex="4"/>
|
||||
<Label fx:id="amountError" opacity="0.0" styleClass="text-error" text="Please use a valid payment request"
|
||||
GridPane.columnIndex="1"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
|
||||
<Label fx:id="amountError" opacity="0.0" styleClass="text-error" text="Generic Invalid Amount"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Close"
|
||||
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
|
||||
</children>
|
||||
<stylesheets>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane fx:id="nodeId" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
prefWidth="395.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
|
||||
prefWidth="395.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="150.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" maxWidth="292.0" minWidth="10.0" prefWidth="241.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="110.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="250.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
|
@ -27,7 +27,7 @@
|
|||
GridPane.columnSpan="2" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS"/>
|
||||
<Button fx:id="handleSend" defaultButton="true" mnemonicParsing="false" onAction="#handleSend" text="Send"
|
||||
GridPane.rowIndex="7"/>
|
||||
<Label fx:id="paymentRequestError" opacity="0.0" text="Please use a valid payment request"
|
||||
<Label fx:id="paymentRequestError" opacity="0.0" text="Generic Invalid Payment Request"
|
||||
GridPane.columnSpan="2" GridPane.rowIndex="2">
|
||||
<styleClass>
|
||||
<String fx:value="text-error"/>
|
||||
|
@ -37,10 +37,10 @@
|
|||
<Label styleClass="text-muted" text="Amount (mSat)" GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
|
||||
<Separator GridPane.columnSpan="2" GridPane.rowIndex="6"/>
|
||||
<Label styleClass="text-muted" text="Node Id" GridPane.halignment="RIGHT" GridPane.rowIndex="4"/>
|
||||
<Label fx:id="amountLabel" text="0" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<Label fx:id="nodeIdLabel" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="amountField" editable="false" styleClass="noteditable" text="0" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<TextField fx:id="nodeIdField" editable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="hashField" editable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
<Label styleClass="text-muted" text="hash" GridPane.halignment="RIGHT" GridPane.rowIndex="5"/>
|
||||
<Label fx:id="hashLabel" text="N/A" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
|
||||
GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="7"/>
|
||||
</children>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<Pane fx:id="splash" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
prefHeight="457.0" prefWidth="760.0" styleClass="transparent" xmlns="http://javafx.com/javafx/8.0.60"
|
||||
prefHeight="457.0" prefWidth="760.0" styleClass="transparent" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.acinq.eclair.gui.controllers.SplashController">
|
||||
<children>
|
||||
<ImageView fx:id="imgBlurred" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="115.0" pickOnBounds="true"
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<encoder>
|
||||
<pattern>${HOSTNAME} %d %-5level %logger{36} %X{akkaSource} - %msg%ex{12}%n</pattern>
|
||||
<pattern>${HOSTNAME} %d %-5level %logger{36} %X{akkaSource} - %msg%ex{24}%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="fr.acinq.eclair.channel" level="DEBUG"/>
|
||||
<logger name="fr.acinq.eclair.channel.Register" level="DEBUG"/>
|
||||
<logger name="fr.acinq.eclair.crypto.TransportHandler" level="INFO"/>
|
||||
<logger name="fr.acinq.eclair.router" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package fr.acinq.eclair
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import javafx.application.{Application, Platform}
|
||||
|
||||
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
|
||||
|
@ -7,7 +8,7 @@ import akka.http.scaladsl.Http
|
|||
import akka.stream.ActorMaterializer
|
||||
import akka.util.Timeout
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin.{Base58Check, BinaryData, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi}
|
||||
import fr.acinq.bitcoin.{Base58Check, BinaryData, MilliSatoshi, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi}
|
||||
import fr.acinq.eclair.api.Service
|
||||
import fr.acinq.eclair.blockchain.peer.PeerClient
|
||||
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
|
||||
|
@ -15,7 +16,7 @@ import fr.acinq.eclair.blockchain.{ExtendedBitcoinClient, PeerWatcher}
|
|||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.gui.FxApp
|
||||
import fr.acinq.eclair.io.{Client, Server}
|
||||
import fr.acinq.eclair.payment.{LocalPaymentHandler, NoopPaymentHandler, PaymentInitiator}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router._
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.JsonAST.JString
|
||||
|
@ -42,7 +43,7 @@ object Boot extends App with Logging {
|
|||
class Setup() extends Logging {
|
||||
|
||||
logger.info(s"hello!")
|
||||
logger.info(s"nodeid=${Globals.Node.publicKey.toBin}")
|
||||
logger.info(s"nodeid=${Globals.Node.publicKey.toBin} alias=${Globals.Node.alias}")
|
||||
val config = ConfigFactory.load()
|
||||
|
||||
implicit lazy val system = ActorSystem()
|
||||
|
@ -66,6 +67,7 @@ class Setup() extends Logging {
|
|||
// TODO: we should use p2wpkh instead of p2pkh as soon as bitcoind supports it
|
||||
//val finalScriptPubKey = OP_0 :: OP_PUSHDATA(Base58Check.decode(finalAddress)._2) :: Nil
|
||||
val finalScriptPubKey = OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Base58Check.decode(finalAddress)._2) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil
|
||||
val socket = new InetSocketAddress(config.getString("eclair.server.host"), config.getInt("eclair.server.port"))
|
||||
|
||||
val fatalEventPromise = Promise[FatalEvent]()
|
||||
system.actorOf(Props(new Actor {
|
||||
|
@ -84,11 +86,10 @@ class Setup() extends Logging {
|
|||
case "local" => system.actorOf(Props[LocalPaymentHandler], name = "payment-handler")
|
||||
case "noop" => system.actorOf(Props[NoopPaymentHandler], name = "payment-handler")
|
||||
}
|
||||
val register = system.actorOf(Register.props(watcher, paymentHandler, finalScriptPubKey), name = "register")
|
||||
val selector = system.actorOf(Props[ChannelSelector], name = "selector")
|
||||
val router = system.actorOf(Props[Router], name = "router")
|
||||
val ircWatcher = system.actorOf(Props[IRCWatcher], "irc")
|
||||
val paymentInitiator = system.actorOf(PaymentInitiator.props(router, selector, blockCount), "payment-spawner")
|
||||
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler), name = "propagator")
|
||||
val router = system.actorOf(Router.props(watcher, Globals.Node.announcement), name = "router")
|
||||
val paymentInitiator = system.actorOf(PaymentInitiator.props(Globals.Node.publicKey, router, blockCount), "payment-initiator")
|
||||
val register = system.actorOf(Register.props(watcher, router, relayer, finalScriptPubKey), name = "register")
|
||||
val server = system.actorOf(Server.props(config.getString("eclair.server.host"), config.getInt("eclair.server.port"), register), "server")
|
||||
|
||||
val _setup = this
|
||||
|
@ -98,7 +99,7 @@ class Setup() extends Logging {
|
|||
override val paymentHandler: ActorRef = _setup.paymentHandler
|
||||
override val paymentInitiator: ActorRef = _setup.paymentInitiator
|
||||
|
||||
override def connect(host: String, port: Int, pubkey: BinaryData, amount: Satoshi): Unit = system.actorOf(Client.props(host, port, pubkey, amount, register))
|
||||
override def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit = system.actorOf(Client.props(host, port, pubkey, fundingSatoshis, pushMsat, register))
|
||||
}
|
||||
Http().bindAndHandle(api.route, config.getString("eclair.api.host"), config.getInt("eclair.api.port")) onFailure {
|
||||
case t: Throwable => system.eventStream.publish(HTTPBindError)
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package fr.acinq.eclair
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet}
|
||||
import fr.acinq.eclair.router.Router
|
||||
|
||||
import scala.compat.Platform
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
||||
|
@ -20,13 +24,20 @@ object Globals {
|
|||
val extendedPublicKey = DeterministicWallet.publicKey(extendedPrivateKey)
|
||||
val publicKey = extendedPublicKey.publicKey
|
||||
val id = publicKey.toBin.toString()
|
||||
val alias = config.getString("node.alias").take(32)
|
||||
val color: (Byte, Byte, Byte) = (config.getInt("node.color.r").toByte, config.getInt("node.color.g").toByte, config.getInt("node.color.b").toByte)
|
||||
val address = new InetSocketAddress(config.getString("server.host"), config.getInt("server.port"))
|
||||
val announcement = Router.makeNodeAnnouncement(privateKey, alias, color, address :: Nil, Platform.currentTime / 1000)
|
||||
}
|
||||
|
||||
val default_delay_blocks = config.getInt("delay-blocks")
|
||||
val default_mindepth_blocks = config.getInt("mindepth-blocks")
|
||||
val default_feeratePerKw = 10000
|
||||
val base_fee = config.getInt("base-fee")
|
||||
val proportional_fee = config.getInt("proportional-fee")
|
||||
val expiry_delta_blocks = config.getInt("expiry-delta-blocks")
|
||||
val htlc_minimum_msat = config.getInt("htlc-minimum-msat")
|
||||
val delay_blocks = config.getInt("delay-blocks")
|
||||
val mindepth_blocks = config.getInt("mindepth-blocks")
|
||||
val feeratePerKw = 10000
|
||||
val fee_base_msat = config.getInt("fee-base-msat")
|
||||
val fee_proportional_msat = config.getInt("fee-proportional-msat")
|
||||
|
||||
val default_anchor_amount = 1000000
|
||||
val autosign_interval = 300 milliseconds
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import akka.pattern.ask
|
|||
import akka.util.Timeout
|
||||
import de.heikoseeberger.akkahttpjson4s.Json4sSupport
|
||||
import de.heikoseeberger.akkahttpjson4s.Json4sSupport.ShouldWritePretty
|
||||
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.channel.Register.{ListChannels, SendCommand}
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -47,7 +47,7 @@ trait Service extends Logging {
|
|||
|
||||
import Json4sSupport.{json4sMarshaller, json4sUnmarshaller}
|
||||
|
||||
def connect(host: String, port: Int, pubkey: BinaryData, amount: Satoshi): Unit
|
||||
def connect(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi): Unit
|
||||
|
||||
def register: ActorRef
|
||||
|
||||
|
@ -71,7 +71,7 @@ trait Service extends Logging {
|
|||
req =>
|
||||
val f_res: Future[AnyRef] = req match {
|
||||
case JsonRPCBody(_, _, "connect", JString(host) :: JInt(port) :: JString(pubkey) :: JInt(anchor_amount) :: Nil) =>
|
||||
connect(host, port.toInt, BinaryData(pubkey), Satoshi(anchor_amount.toLong))
|
||||
connect(host, port.toInt, BinaryData(pubkey), Satoshi(anchor_amount.toLong), MilliSatoshi(0))
|
||||
Future.successful("ok")
|
||||
case JsonRPCBody(_, _, "info", _) =>
|
||||
Future.successful(Status(Globals.Node.id))
|
||||
|
@ -79,19 +79,19 @@ trait Service extends Logging {
|
|||
(register ? ListChannels).mapTo[Iterable[ActorRef]]
|
||||
.flatMap(l => Future.sequence(l.map(c => c ? CMD_GETINFO)))
|
||||
case JsonRPCBody(_, _, "network", _) =>
|
||||
(router ? 'network).mapTo[Iterable[ChannelDesc]]
|
||||
(router ? 'channels).mapTo[Iterable[ChannelDesc]]
|
||||
case JsonRPCBody(_, _, "addhtlc", JInt(amount) :: JString(rhash) :: JString(nodeId) :: Nil) =>
|
||||
(paymentInitiator ? CreatePayment(amount.toInt, BinaryData(rhash), BinaryData(nodeId))).mapTo[ChannelEvent]
|
||||
case JsonRPCBody(_, _, "genh", _) =>
|
||||
(paymentHandler ? 'genh).mapTo[BinaryData]
|
||||
case JsonRPCBody(_, _, "sign", JString(channel) :: Nil) =>
|
||||
(register ? SendCommand(channel, CMD_SIGN)).mapTo[ActorRef].map(_ => "ok")
|
||||
case JsonRPCBody(_, _, "sign", JInt(channel) :: Nil) =>
|
||||
(register ? SendCommand(channel.toLong, CMD_SIGN)).mapTo[ActorRef].map(_ => "ok")
|
||||
case JsonRPCBody(_, _, "fulfillhtlc", JString(channel) :: JDouble(id) :: JString(r) :: Nil) =>
|
||||
(register ? SendCommand(channel, CMD_FULFILL_HTLC(id.toLong, BinaryData(r), commit = true))).mapTo[ActorRef].map(_ => "ok")
|
||||
(register ? SendCommand(channel.toLong, CMD_FULFILL_HTLC(id.toLong, BinaryData(r), commit = true))).mapTo[ActorRef].map(_ => "ok")
|
||||
case JsonRPCBody(_, _, "close", JString(channel) :: JString(scriptPubKey) :: Nil) =>
|
||||
(register ? SendCommand(channel, CMD_CLOSE(Some(scriptPubKey)))).mapTo[ActorRef].map(_ => "ok")
|
||||
(register ? SendCommand(channel.toLong, CMD_CLOSE(Some(scriptPubKey)))).mapTo[ActorRef].map(_ => "ok")
|
||||
case JsonRPCBody(_, _, "close", JString(channel) :: Nil) =>
|
||||
(register ? SendCommand(channel, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")
|
||||
(register ? SendCommand(channel.toLong, CMD_CLOSE(None))).mapTo[ActorRef].map(_ => "ok")
|
||||
case JsonRPCBody(_, _, "help", _) =>
|
||||
Future.successful(List(
|
||||
"info: display basic node information",
|
||||
|
|
|
@ -28,7 +28,7 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
|
|||
val triggeredWatches = watches.collect {
|
||||
case w@WatchSpent(channel, txid, outputIndex, minDepth, event)
|
||||
if tx.txIn.exists(i => i.outPoint.txid == txid && i.outPoint.index == outputIndex) =>
|
||||
channel ! (BITCOIN_FUNDING_SPENT, tx)
|
||||
channel ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
||||
w
|
||||
}
|
||||
context.become(watching(watches -- triggeredWatches, block2tx, currentBlockCount))
|
||||
|
@ -38,19 +38,23 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
|
|||
// TODO: beware of the herd effect
|
||||
watches.collect {
|
||||
case w@WatchConfirmed(channel, txId, minDepth, event) =>
|
||||
client.getTxConfirmations(txId.toString).collect {
|
||||
// TODO: this is a workaround to not have WatchConfirmed triggered multiple times in testing
|
||||
// the reason is that we cannot fo a become(watches - w, ...) because it happens in the future callback
|
||||
case Some(confirmations) if confirmations >= minDepth => self ! ('trigger, w)
|
||||
client.getTxConfirmations(txId.toString).map {
|
||||
case Some(confirmations) if confirmations >= minDepth =>
|
||||
client.getTransactionShortId(txId.toString).map {
|
||||
// TODO: this is a workaround to not have WatchConfirmed triggered multiple times in testing
|
||||
// the reason is that we cannot do a become(watches - w, ...) because it happens in the future callback
|
||||
case (height, index) => self ! ('trigger, w, WatchEventConfirmed(w.event, height, index))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case ('trigger, w: WatchConfirmed) if watches.contains(w) =>
|
||||
case ('trigger, w: WatchConfirmed, e: WatchEvent) if watches.contains(w) =>
|
||||
log.info(s"triggering $w")
|
||||
w.channel ! w.event
|
||||
w.channel ! e
|
||||
context.become(watching(watches - w, block2tx, currentBlockCount))
|
||||
|
||||
case ('trigger, w: WatchConfirmed) if !watches.contains(w) => {}
|
||||
case ('trigger, w: WatchConfirmed, e: WatchEvent) if !watches.contains(w) => {}
|
||||
|
||||
case CurrentBlockCount(count) => {
|
||||
val toPublish = block2tx.filterKeys(_ <= count)
|
||||
|
|
|
@ -16,9 +16,16 @@ trait Watch {
|
|||
}
|
||||
final case class WatchConfirmed(channel: ActorRef, txId: BinaryData, minDepth: Long, event: BitcoinEvent) extends Watch
|
||||
final case class WatchSpent(channel: ActorRef, txId: BinaryData, outputIndex: Int, minDepth: Int, event: BitcoinEvent) extends Watch
|
||||
// notify me if confirmation number gets below minDepth
|
||||
// TODO: notify me if confirmation number gets below minDepth?
|
||||
final case class WatchLost(channel: ActorRef, txId: BinaryData, minDepth: Long, event: BitcoinEvent) extends Watch
|
||||
|
||||
trait WatchEvent {
|
||||
def event: BitcoinEvent
|
||||
}
|
||||
final case class WatchEventConfirmed(event: BitcoinEvent, blockHeight: Int, txIndex: Int) extends WatchEvent
|
||||
final case class WatchEventSpent(event: BitcoinEvent, tx: Transaction) extends WatchEvent
|
||||
final case class WatchEventLost(event: BitcoinEvent) extends WatchEvent
|
||||
|
||||
/**
|
||||
* Publish the provided tx as soon as possible depending on locktime and csv
|
||||
*/
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import akka.actor.{ActorRef, FSM, LoggingFSM, Props}
|
||||
import fr.acinq.bitcoin.Crypto.Point
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.Helpers.{Closing, Funding}
|
||||
import fr.acinq.eclair.crypto.{Generators, ShaChain}
|
||||
import fr.acinq.eclair.payment.Binding
|
||||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire._
|
||||
|
||||
|
@ -21,10 +22,10 @@ import scala.util.{Failure, Success, Try}
|
|||
*/
|
||||
|
||||
object Channel {
|
||||
def props(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef, localParams: LocalParams, theirNodeId: String, autoSignInterval: Option[FiniteDuration] = None) = Props(new Channel(them, blockchain, paymentHandler, localParams, theirNodeId, autoSignInterval))
|
||||
def props(them: ActorRef, blockchain: ActorRef, router: ActorRef, relayer: ActorRef, localParams: LocalParams, theirNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None) = Props(new Channel(them, blockchain, router, relayer, localParams, theirNodeId, autoSignInterval))
|
||||
}
|
||||
|
||||
class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: ActorRef, val localParams: LocalParams, theirNodeId: String, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
|
||||
class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, relayer: ActorRef, val localParams: LocalParams, theirNodeId: PublicKey, autoSignInterval: Option[FiniteDuration] = None)(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends LoggingFSM[State, Data] {
|
||||
|
||||
context.system.eventStream.publish(ChannelCreated(self, localParams, theirNodeId))
|
||||
|
||||
|
@ -95,7 +96,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Event(open: OpenChannel, DATA_WAIT_FOR_OPEN_CHANNEL(localParams, autoSignInterval)) =>
|
||||
// TODO: here we should check if remote parameters suit us
|
||||
// TODO: maybe also check uniqueness of temporary channel id
|
||||
val minimumDepth = Globals.default_mindepth_blocks
|
||||
val minimumDepth = Globals.mindepth_blocks
|
||||
val firstPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 0)
|
||||
them ! AcceptChannel(temporaryChannelId = Platform.currentTime,
|
||||
dustLimitSatoshis = localParams.dustLimitSatoshis,
|
||||
|
@ -229,7 +230,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil),
|
||||
localCurrentHtlcId = 0L,
|
||||
remoteNextCommitInfo = Right(null), // TODO: we will receive their next per-commitment point in the next message, so we temporarily put an empty byte array
|
||||
commitInput, ShaChain.init, channelId = 0)
|
||||
commitInput, ShaChain.init, channelId = 0) // TODO: we will compute the channelId at the next step, so we temporarily put 0
|
||||
context.system.eventStream.publish(ChannelIdAssigned(self, commitments.anchorId, Satoshi(params.fundingSatoshis)))
|
||||
goto(WAIT_FOR_FUNDING_LOCKED_INTERNAL) using DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, None)
|
||||
}
|
||||
|
@ -280,23 +281,23 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
log.info(s"received their FundingLocked, deferring message")
|
||||
stay using d.copy(deferred = Some(msg))
|
||||
|
||||
case Event(BITCOIN_FUNDING_DEPTHOK, d@DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, deferred)) =>
|
||||
// TODO: set channelId
|
||||
val channelId = 0L
|
||||
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, blockHeight, txIndex), d@DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId, params, commitments, deferred)) =>
|
||||
val channelId = toShortId(blockHeight, txIndex, commitments.commitInput.outPoint.index.toInt)
|
||||
blockchain ! WatchLost(self, commitments.anchorId, params.minimumDepth, BITCOIN_FUNDING_LOST)
|
||||
val nextPerCommitmentPoint = Generators.perCommitPoint(localParams.shaSeed, 1)
|
||||
them ! FundingLocked(channelId, 0L, None, None, nextPerCommitmentPoint) // TODO: routing announcements disabled
|
||||
them ! FundingLocked(temporaryChannelId, channelId, None, None, nextPerCommitmentPoint) // TODO: routing announcements disabled
|
||||
deferred.map(self ! _)
|
||||
// TODO: htlcIdx should not be 0 when resuming connection
|
||||
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_NORMAL(channelId, params, commitments.copy(channelId = channelId), None, Map())
|
||||
goto(WAIT_FOR_FUNDING_LOCKED) using DATA_NORMAL(params, commitments.copy(channelId = channelId), None)
|
||||
|
||||
// TODO: not implemented, maybe should be done with a state timer and not a blockchain watch?
|
||||
case Event(BITCOIN_FUNDING_TIMEOUT, _) =>
|
||||
them ! Error(0, "Funding tx timed out".getBytes)
|
||||
goto(CLOSED)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, _), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) => handleInformationLeak(d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) => handleInformationLeak(d)
|
||||
|
||||
case Event(cmd: CMD_CLOSE, d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) =>
|
||||
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
|
||||
|
@ -316,14 +317,18 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
})
|
||||
|
||||
when(WAIT_FOR_FUNDING_LOCKED)(handleExceptions {
|
||||
case Event(FundingLocked(temporaryChannelId, channelId, _, _, nextPerCommitmentPoint), d: DATA_NORMAL) =>
|
||||
// TODO: check channelId matches ours
|
||||
Register.create_alias(theirNodeId, d.commitments.anchorId)
|
||||
case Event(FundingLocked(_, remoteChannelId, _, _, nextPerCommitmentPoint), d: DATA_NORMAL) if remoteChannelId != d.channelId =>
|
||||
// TODO: channel id mismatch, can happen if minDepth is to low, negotiation not suported yet
|
||||
handleLocalError(new RuntimeException(s"channel id mismatch local=${d.channelId} remote=$remoteChannelId"), d)
|
||||
|
||||
case Event(FundingLocked(_, remoteChannelId, _, _, nextPerCommitmentPoint), d: DATA_NORMAL) =>
|
||||
log.info(s"channel ready with channelId=${java.lang.Long.toUnsignedString(d.channelId)}")
|
||||
Register.createAlias(theirNodeId.hash160, d.channelId)
|
||||
goto(NORMAL) using d.copy(commitments = d.commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)))
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, _), d: DATA_NORMAL) => handleInformationLeak(d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, _), d: DATA_NORMAL) => handleInformationLeak(d)
|
||||
|
||||
case Event(cmd: CMD_CLOSE, d: DATA_NORMAL) =>
|
||||
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
|
||||
|
@ -353,15 +358,16 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Event(c: CMD_ADD_HTLC, d: DATA_NORMAL) if d.localShutdown.isDefined =>
|
||||
handleCommandError(sender, new RuntimeException("cannot send new htlcs, closing in progress"))
|
||||
|
||||
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, origin, id_opt, do_commit), d@DATA_NORMAL(channelId, params, commitments, _, downstreams)) =>
|
||||
case Event(c@CMD_ADD_HTLC(amountMsat, rHash, expiry, route, origin_opt, id_opt, do_commit), d@DATA_NORMAL(params, commitments, _)) =>
|
||||
Try(Commitments.sendAdd(commitments, c)) match {
|
||||
case Success((commitments1, add)) =>
|
||||
origin_opt.map(origin => relayer ! Binding(origin, add))
|
||||
if (do_commit) self ! CMD_SIGN
|
||||
handleCommandSuccess(sender, add, d.copy(commitments = commitments1, downstreams = downstreams + (add.id -> origin)))
|
||||
handleCommandSuccess(sender, add, d.copy(commitments = commitments1))
|
||||
case Failure(cause) => handleCommandError(sender, cause)
|
||||
}
|
||||
|
||||
case Event(add: UpdateAddHtlc, d@DATA_NORMAL(_, params, commitments, _, _)) =>
|
||||
case Event(add: UpdateAddHtlc, d@DATA_NORMAL(params, commitments, _)) =>
|
||||
Try(Commitments.receiveAdd(commitments, add)) match {
|
||||
case Success(commitments1) =>
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
@ -378,13 +384,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Failure(cause) => handleCommandError(sender, cause)
|
||||
}
|
||||
|
||||
case Event(fulfill@UpdateFulfillHtlc(_, id, r), d@DATA_NORMAL(channelId, params, commitments, _, downstreams)) =>
|
||||
case Event(fulfill@UpdateFulfillHtlc(_, id, r), d@DATA_NORMAL(params, commitments, _)) =>
|
||||
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
|
||||
case Success((commitments1, htlc)) =>
|
||||
propagateDownstream(htlc, Right(fulfill), downstreams(id))
|
||||
relayer ! (htlc, fulfill)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
params.autoSignInterval.map(interval => context.system.scheduler.scheduleOnce(interval, self, CMD_SIGN))
|
||||
stay using d.copy(commitments = commitments1, downstreams = downstreams - id)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
|
@ -396,13 +402,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Failure(cause) => handleCommandError(sender, cause)
|
||||
}
|
||||
|
||||
case Event(fail@UpdateFailHtlc(_, id, reason), d@DATA_NORMAL(channelId, params, commitments, _, downstreams)) =>
|
||||
case Event(fail@UpdateFailHtlc(_, id, reason), d@DATA_NORMAL(params, commitments, _)) =>
|
||||
Try(Commitments.receiveFail(d.commitments, fail)) match {
|
||||
case Success((commitments1, htlc)) =>
|
||||
propagateDownstream(htlc, Left(fail), downstreams(id))
|
||||
relayer ! (htlc, fail)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
params.autoSignInterval.map(interval => context.system.scheduler.scheduleOnce(interval, self, CMD_SIGN))
|
||||
stay using d.copy(commitments = commitments1, downstreams = downstreams - id)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
|
@ -430,7 +436,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
// now that we have their sig, we should propagate the htlcs newly received
|
||||
(commitments1.localCommit.spec.htlcs -- d.commitments.localCommit.spec.htlcs)
|
||||
.filter(_.direction == IN)
|
||||
.foreach(htlc => propagateUpstream(htlc.add, d.commitments.anchorId))
|
||||
.foreach(htlc => relayer ! htlc.add)
|
||||
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1))
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
|
@ -460,10 +466,10 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case _ => handleCommandError(sender, new RuntimeException("invalid final script"))
|
||||
}
|
||||
|
||||
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(channelId, params, commitments, ourShutdownOpt, downstreams)) if commitments.remoteChanges.proposed.size > 0 =>
|
||||
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(params, commitments, ourShutdownOpt)) if commitments.remoteChanges.proposed.size > 0 =>
|
||||
handleLocalError(new RuntimeException("it is illegal to send a shutdown while having unsigned changes"), d)
|
||||
|
||||
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(channelId, params, commitments, ourShutdownOpt, downstreams)) =>
|
||||
case Event(remoteShutdown@Shutdown(_, remoteScriptPubKey), d@DATA_NORMAL(params, commitments, ourShutdownOpt)) =>
|
||||
Try(ourShutdownOpt.map(s => (s, commitments)).getOrElse {
|
||||
require(Closing.isValidFinalScriptPubkey(remoteScriptPubKey), "invalid final script")
|
||||
// first if we have pending changes, we need to commit them
|
||||
|
@ -472,7 +478,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
them ! commit
|
||||
commitments1
|
||||
} else commitments
|
||||
val shutdown = Shutdown(channelId, Script.write(params.localParams.defaultFinalScriptPubKey))
|
||||
val shutdown = Shutdown(d.channelId, Script.write(params.localParams.defaultFinalScriptPubKey))
|
||||
them ! shutdown
|
||||
(shutdown, commitments2)
|
||||
}) match {
|
||||
|
@ -481,15 +487,15 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
|| (commitments3.remoteNextCommitInfo.isLeft && commitments3.localCommit.spec.htlcs.size == 0 && commitments3.remoteNextCommitInfo.left.get.spec.htlcs.size == 0) =>
|
||||
val closingSigned = Closing.makeFirstClosingTx(params, commitments3, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
them ! closingSigned
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(channelId, params, commitments3, localShutdown, remoteShutdown, closingSigned)
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments3, localShutdown, remoteShutdown, closingSigned)
|
||||
case Success((localShutdown, commitments3)) =>
|
||||
goto(SHUTDOWN) using DATA_SHUTDOWN(channelId, params, commitments3, localShutdown, remoteShutdown, downstreams)
|
||||
goto(SHUTDOWN) using DATA_SHUTDOWN(params, commitments3, localShutdown, remoteShutdown)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NORMAL) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d)
|
||||
|
||||
|
@ -516,8 +522,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Event(fulfill@UpdateFulfillHtlc(_, id, r), d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
|
||||
case Success((commitments1, htlc)) =>
|
||||
propagateDownstream(htlc, Right(fulfill), d.downstreams(id))
|
||||
stay using d.copy(commitments = commitments1, downstreams = d.downstreams - id)
|
||||
relayer ! (htlc, fulfill)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
|
@ -530,8 +536,8 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Event(fail@UpdateFailHtlc(_, id, reason), d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFail(d.commitments, fail)) match {
|
||||
case Success((commitments1, htlc)) =>
|
||||
propagateDownstream(htlc, Left(fail), d.downstreams(id))
|
||||
stay using d.copy(commitments = commitments1, downstreams = d.downstreams - id)
|
||||
relayer ! (htlc, fail)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
|
@ -552,36 +558,36 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case Failure(cause) => handleCommandError(sender, cause)
|
||||
}
|
||||
|
||||
case Event(msg@CommitSig(_, theirSig, theirHtlcSigs), d@DATA_SHUTDOWN(channelId, params, commitments, localShutdown, remoteShutdown, _)) =>
|
||||
case Event(msg@CommitSig(_, theirSig, theirHtlcSigs), d@DATA_SHUTDOWN(params, commitments, localShutdown, remoteShutdown)) =>
|
||||
// TODO: we might have to propagate htlcs upstream depending on the outcome of https://github.com/ElementsProject/lightning/issues/29
|
||||
Try(Commitments.receiveCommit(d.commitments, msg)) match {
|
||||
case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs =>
|
||||
them ! revocation
|
||||
val closingSigned = Closing.makeFirstClosingTx(params, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
them ! closingSigned
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(channelId, params, commitments1, localShutdown, remoteShutdown, closingSigned)
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
|
||||
case Success((commitments1, revocation)) =>
|
||||
them ! revocation
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
case Event(msg: RevokeAndAck, d@DATA_SHUTDOWN(channelId, params, commitments, localShutdown, remoteShutdown, _)) =>
|
||||
case Event(msg: RevokeAndAck, d@DATA_SHUTDOWN(params, commitments, localShutdown, remoteShutdown)) =>
|
||||
// we received a revocation because we sent a signature
|
||||
// => all our changes have been acked
|
||||
Try(Commitments.receiveRevocation(d.commitments, msg)) match {
|
||||
case Success(commitments1) if commitments1.hasNoPendingHtlcs =>
|
||||
val closingSigned = Closing.makeFirstClosingTx(params, commitments, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
them ! closingSigned
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(channelId, params, commitments1, localShutdown, remoteShutdown, closingSigned)
|
||||
goto(NEGOTIATING) using DATA_NEGOTIATING(params, commitments1, localShutdown, remoteShutdown, closingSigned)
|
||||
case Success(commitments1) =>
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Failure(cause) => handleLocalError(cause, d)
|
||||
}
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_SHUTDOWN) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(e: Error, d: DATA_SHUTDOWN) => handleRemoteError(e, d)
|
||||
}
|
||||
|
@ -614,13 +620,13 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
throw new RuntimeException("cannot verify their close signature", cause)
|
||||
}
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == Closing.makeClosingTx(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(d.localClosingSigned.feeSatoshis))._1.tx.txid =>
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == Closing.makeClosingTx(d.params, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(d.localClosingSigned.feeSatoshis))._1.tx.txid =>
|
||||
// happens when we agreed on a closeSig, but we don't know it yet: we receive the watcher notification before their ClosingSigned (which will match ours)
|
||||
stay()
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) if tx.txid == d.commitments.remoteCommit.txid => handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d)
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_NEGOTIATING) => handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(e: Error, d: DATA_NEGOTIATING) => handleRemoteError(e, d)
|
||||
|
||||
|
@ -628,29 +634,29 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
|
||||
when(CLOSING) {
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.localCommit.publishableTxs.commitTx.tx.txid =>
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.localCommit.publishableTxs.commitTx.tx.txid =>
|
||||
// we just initiated a uniclose moments ago and are now receiving the blockchain notification, there is nothing to do
|
||||
stay()
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if Some(tx.txid) == d.mutualClosePublished.map(_.txid) =>
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if Some(tx.txid) == d.mutualClosePublished.map(_.txid) =>
|
||||
// we just published a mutual close tx, we are notified but it's alright
|
||||
stay()
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.remoteCommit.txid =>
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) if tx.txid == d.commitments.remoteCommit.txid =>
|
||||
// counterparty may attempt to spend its last commit tx at any time
|
||||
handleRemoteSpentCurrent(tx, d)
|
||||
|
||||
case Event((BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) =>
|
||||
case Event(WatchEventSpent(BITCOIN_FUNDING_SPENT, tx: Transaction), d: DATA_CLOSING) =>
|
||||
// counterparty may attempt to spend a revoked commit tx at any time
|
||||
handleRemoteSpentOther(tx, d)
|
||||
|
||||
case Event(BITCOIN_CLOSE_DONE, d: DATA_CLOSING) if d.mutualClosePublished.isDefined => goto(CLOSED)
|
||||
case Event(WatchEventConfirmed(BITCOIN_CLOSE_DONE, _, _), d: DATA_CLOSING) if d.mutualClosePublished.isDefined => goto(CLOSED)
|
||||
|
||||
case Event(BITCOIN_SPEND_OURS_DONE, d: DATA_CLOSING) if d.localCommitPublished.isDefined => goto(CLOSED)
|
||||
case Event(WatchEventConfirmed(BITCOIN_SPEND_OURS_DONE, _, _), d: DATA_CLOSING) if d.localCommitPublished.isDefined => goto(CLOSED)
|
||||
|
||||
case Event(BITCOIN_SPEND_THEIRS_DONE, d: DATA_CLOSING) if d.remoteCommitPublished.isDefined => goto(CLOSED)
|
||||
case Event(WatchEventConfirmed(BITCOIN_SPEND_THEIRS_DONE, _, _), d: DATA_CLOSING) if d.remoteCommitPublished.isDefined => goto(CLOSED)
|
||||
|
||||
case Event(BITCOIN_PUNISHMENT_DONE, d: DATA_CLOSING) if d.revokedCommitPublished.size > 0 => goto(CLOSED)
|
||||
case Event(WatchEventConfirmed(BITCOIN_PUNISHMENT_DONE, _, _), d: DATA_CLOSING) if d.revokedCommitPublished.size > 0 => goto(CLOSED)
|
||||
|
||||
case Event(e: Error, d: DATA_CLOSING) => stay // nothing to do, there is already a spending tx published
|
||||
}
|
||||
|
@ -669,7 +675,11 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
|
||||
whenUnhandled {
|
||||
|
||||
case Event(BITCOIN_FUNDING_LOST, _) => goto(ERR_FUNDING_LOST)
|
||||
case Event(msg: RoutingMessage, _) =>
|
||||
router forward msg
|
||||
stay
|
||||
|
||||
case Event(WatchEventLost(BITCOIN_FUNDING_LOST), _) => goto(ERR_FUNDING_LOST)
|
||||
|
||||
case Event(CMD_GETSTATE, _) =>
|
||||
sender ! stateName
|
||||
|
@ -686,7 +696,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
case c: DATA_WAIT_FOR_ACCEPT_CHANNEL => c.temporaryChannelId
|
||||
case c: DATA_WAIT_FOR_FUNDING_CREATED => c.temporaryChannelId
|
||||
case c: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL => c.temporaryChannelId
|
||||
case c: DATA_NORMAL => c.channelId
|
||||
case c: DATA_NORMAL => c.commitments.channelId
|
||||
case c: DATA_SHUTDOWN => c.channelId
|
||||
case c: DATA_NEGOTIATING => c.channelId
|
||||
case c: DATA_CLOSING => 0L
|
||||
|
@ -699,7 +709,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
}
|
||||
|
||||
onTransition {
|
||||
case previousState -> currentState => context.system.eventStream.publish(ChannelChangedState(self, theirNodeId, previousState, currentState, stateData))
|
||||
case previousState -> currentState => context.system.eventStream.publish(ChannelChangedState(self, context.parent, theirNodeId, previousState, currentState, stateData))
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -713,55 +723,6 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, paymentHandler: Acto
|
|||
888 888 d88P 888 888 Y888 8888888P" 88888888 8888888888 888 T88b "Y8888P"
|
||||
*/
|
||||
|
||||
def propagateUpstream(add: UpdateAddHtlc, anchorId: BinaryData) = {
|
||||
// TODO: relaying of payments is disabled
|
||||
/*val r = route.parseFrom(add.onionRoutingPacket)
|
||||
r.steps match {
|
||||
case route_step(amountMsat, Next.Bitcoin(nextNodeId)) +: rest =>
|
||||
log.debug(s"propagating htlc #${add.id} to $nextNodeId")
|
||||
import ExecutionContext.Implicits.global
|
||||
context.system.actorSelection(Register.actorPathToNodeId(nextNodeId))
|
||||
.resolveOne(3 seconds)
|
||||
.onComplete {
|
||||
case Success(upstream) =>
|
||||
log.info(s"forwarding htlc #${add.id} to upstream=$upstream")
|
||||
val upstream_route = route(rest)
|
||||
// TODO: we should decrement expiry !!
|
||||
upstream ! CMD_ADD_HTLC(amountMsat, add.paymentHash, add.expiry, upstream_route, Some(Origin(anchorId, add.id)))
|
||||
upstream ! CMD_SIGN
|
||||
case Failure(t: Throwable) =>
|
||||
// TODO: send "fail route error"
|
||||
log.warning(s"couldn't resolve upstream node, htlc #${add.id} will timeout", t)
|
||||
}
|
||||
case route_step(amount, Next.End(true)) +: rest =>*/
|
||||
log.info(s"we are the final recipient of htlc #${add.id}")
|
||||
context.system.eventStream.publish(PaymentReceived(self, add.paymentHash))
|
||||
paymentHandler ! add
|
||||
//}
|
||||
}
|
||||
|
||||
def propagateDownstream(htlc: UpdateAddHtlc, fail_or_fulfill: Either[UpdateFailHtlc, UpdateFulfillHtlc], origin_opt: Option[Origin]) = {
|
||||
(origin_opt, fail_or_fulfill) match {
|
||||
// TODO: relaying of payments is disabled
|
||||
/*case (Some(origin), Left(fail)) =>
|
||||
val downstream = context.system.actorSelection(Register.actorPathToChannelId(origin.channelId))
|
||||
downstream ! CMD_SIGN
|
||||
downstream ! CMD_FAIL_HTLC(origin.htlc_id, fail.reason.toStringUtf8)
|
||||
downstream ! CMD_SIGN
|
||||
case (Some(origin), Right(fulfill)) =>
|
||||
val downstream = context.system.actorSelection(Register.actorPathToChannelId(origin.channelId))
|
||||
downstream ! CMD_SIGN
|
||||
downstream ! CMD_FULFILL_HTLC(origin.htlc_id, fulfill.paymentPreimage)
|
||||
downstream ! CMD_SIGN*/
|
||||
case (None, Left(fail)) =>
|
||||
log.info(s"we were the origin payer for htlc #${htlc.id}")
|
||||
context.system.eventStream.publish(PaymentFailed(self, htlc.paymentHash, fail.reason.toStringUtf8))
|
||||
case (None, Right(fulfill)) =>
|
||||
log.info(s"we were the origin payer for htlc #${htlc.id}")
|
||||
context.system.eventStream.publish(PaymentSent(self, htlc.paymentHash))
|
||||
}
|
||||
}
|
||||
|
||||
def handleCommandSuccess(sender: ActorRef, msg: LightningMessage, newData: Data) = {
|
||||
them ! msg
|
||||
sender ! "ok"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package fr.acinq.eclair.channel
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
||||
|
||||
/**
|
||||
|
@ -9,16 +10,10 @@ import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
|||
|
||||
trait ChannelEvent
|
||||
|
||||
case class ChannelCreated(channel: ActorRef, params: LocalParams, theirNodeId: String) extends ChannelEvent
|
||||
case class ChannelCreated(channel: ActorRef, params: LocalParams, remoteNodeId: PublicKey) extends ChannelEvent
|
||||
|
||||
case class ChannelIdAssigned(channel: ActorRef, channelId: BinaryData, amount: Satoshi) extends ChannelEvent
|
||||
|
||||
case class ChannelChangedState(channel: ActorRef, theirNodeId: BinaryData, previousState: State, currentState: State, currentData: Data) extends ChannelEvent
|
||||
case class ChannelChangedState(channel: ActorRef, transport: ActorRef, remoteNodeId: PublicKey, previousState: State, currentState: State, currentData: Data) extends ChannelEvent
|
||||
|
||||
case class ChannelSignatureReceived(channel: ActorRef, Commitments: Commitments) extends ChannelEvent
|
||||
|
||||
case class PaymentSent(channel: ActorRef, h: BinaryData) extends ChannelEvent
|
||||
|
||||
case class PaymentFailed(channel: ActorRef, h: BinaryData, reason: String) extends ChannelEvent
|
||||
|
||||
case class PaymentReceived(channel: ActorRef, h: BinaryData) extends ChannelEvent
|
||||
|
|
|
@ -4,8 +4,7 @@ import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
|||
import fr.acinq.bitcoin.{BinaryData, ScriptElt, Transaction}
|
||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||
import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown}
|
||||
import lightning.{route, route_step}
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown, UpdateAddHtlc}
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
|
@ -85,14 +84,10 @@ case object BITCOIN_CLOSE_DONE extends BitcoinEvent
|
|||
|
||||
sealed trait Command
|
||||
|
||||
/*
|
||||
Used when relaying payments
|
||||
*/
|
||||
final case class Origin(channelId: BinaryData, htlc_id: Long)
|
||||
/**
|
||||
* @param id should only be provided in tests otherwise it will be assigned automatically
|
||||
*/
|
||||
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: BinaryData, expiry: Long, payment_route: route = route(route_step(0, next = route_step.Next.End(true)) :: Nil), origin: Option[Origin] = None, id: Option[Long] = None, commit: Boolean = false) extends Command
|
||||
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: BinaryData, expiry: Long, onion: BinaryData = BinaryData("00" * 1254), origin: Option[UpdateAddHtlc] = None, id: Option[Long] = None, commit: Boolean = false) extends Command
|
||||
final case class CMD_FULFILL_HTLC(id: Long, r: BinaryData, commit: Boolean = false) extends Command
|
||||
final case class CMD_FAIL_HTLC(id: Long, reason: String, commit: Boolean = false) extends Command
|
||||
case object CMD_SIGN extends Command
|
||||
|
@ -119,6 +114,7 @@ case object Nothing extends Data
|
|||
|
||||
trait HasCommitments extends Data {
|
||||
def commitments: Commitments
|
||||
def channelId = commitments.channelId
|
||||
}
|
||||
|
||||
case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: Seq[Transaction], htlcTimeoutTxs: Seq[Transaction], claimHtlcDelayedTx: Seq[Transaction])
|
||||
|
@ -131,11 +127,10 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, params
|
|||
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: Point) extends Data
|
||||
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, localSpec: CommitmentSpec, localCommitTx: CommitTx, remoteCommit: RemoteCommit) extends Data
|
||||
final case class DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL(temporaryChannelId: Long, params: ChannelParams, commitments: Commitments, deferred: Option[FundingLocked]) extends Data with HasCommitments
|
||||
final case class DATA_NORMAL(channelId: Long, params: ChannelParams, commitments: Commitments, localShutdown: Option[Shutdown], downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
|
||||
final case class DATA_SHUTDOWN(channelId: Long, params: ChannelParams, commitments: Commitments,
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown,
|
||||
downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
|
||||
final case class DATA_NEGOTIATING(channelId: Long, params: ChannelParams, commitments: Commitments,
|
||||
final case class DATA_NORMAL(params: ChannelParams, commitments: Commitments, localShutdown: Option[Shutdown]) extends Data with HasCommitments
|
||||
final case class DATA_SHUTDOWN(params: ChannelParams, commitments: Commitments,
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown) extends Data with HasCommitments
|
||||
final case class DATA_NEGOTIATING(params: ChannelParams, commitments: Commitments,
|
||||
localShutdown: Shutdown, remoteShutdown: Shutdown, localClosingSigned: ClosingSigned) extends Data with HasCommitments
|
||||
final case class DATA_CLOSING(commitments: Commitments,
|
||||
ourSignature: Option[ClosingSigned] = None,
|
||||
|
|
|
@ -67,8 +67,7 @@ object Commitments extends Logging {
|
|||
throw new RuntimeException(s"insufficient funds (available=$available msat)")
|
||||
} else {
|
||||
val id = cmd.id.getOrElse(commitments.localCurrentHtlcId + 1)
|
||||
// TODO: fix routing
|
||||
val add: UpdateAddHtlc = UpdateAddHtlc(commitments.channelId, id, cmd.amountMsat, cmd.expiry, cmd.paymentHash, "" /*routing(ByteString.copyFrom(cmd.payment_route.toByteArray))*/)
|
||||
val add = UpdateAddHtlc(commitments.channelId, id, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
val commitments1 = addLocalProposal(commitments, add).copy(localCurrentHtlcId = id)
|
||||
(commitments1, add)
|
||||
}
|
||||
|
|
|
@ -224,10 +224,10 @@ object Helpers {
|
|||
* When an unexpected transaction spending the funding tx is detected:
|
||||
* 1) we find out if the published transaction is one of remote's revoked txs
|
||||
* 2) and then:
|
||||
* a) if it is a revoked tx we build a set of transactions that will punish them by stealing all their funds
|
||||
* b) otherwise there is nothing we can do
|
||||
* a) if it is a revoked tx we build a set of transactions that will punish them by stealing all their funds
|
||||
* b) otherwise there is nothing we can do
|
||||
*
|
||||
* @return a list of transactions (one per HTLC that we can claim) if the tx is a revoked commitment, [[None]] otherwise
|
||||
* @return a [[RevokedCommitPublished]] object containing punishment transactions if the tx is a revoked commitment
|
||||
*/
|
||||
def claimRevokedRemoteCommitTxOutputs(commitments: Commitments, tx: Transaction): Option[RevokedCommitPublished] = {
|
||||
import commitments._
|
||||
|
|
|
@ -2,8 +2,8 @@ package fr.acinq.eclair.channel
|
|||
|
||||
import akka.actor.{Props, _}
|
||||
import akka.util.Timeout
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, Satoshi, ScriptElt}
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, MilliSatoshi, Satoshi, ScriptElt}
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.crypto.Noise.KeyPair
|
||||
import fr.acinq.eclair.crypto.TransportHandler
|
||||
|
@ -32,14 +32,14 @@ import scala.concurrent.duration._
|
|||
* ├── client (0..m, transient)
|
||||
* └── api
|
||||
*/
|
||||
class Register(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends Actor with ActorLogging {
|
||||
class Register(watcher: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) extends Actor with ActorLogging {
|
||||
|
||||
import Register._
|
||||
|
||||
def receive: Receive = main(0L)
|
||||
|
||||
def main(counter: Long): Receive = {
|
||||
case CreateChannel(connection, pubkey, amount_opt) =>
|
||||
case CreateChannel(connection, pubkey, funding_opt, pushmsat_opt) =>
|
||||
def generateKey(index: Long): PrivateKey = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, index :: counter :: Nil).privateKey
|
||||
|
||||
val localParams = LocalParams(
|
||||
|
@ -47,8 +47,8 @@ class Register(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScrip
|
|||
maxHtlcValueInFlightMsat = Long.MaxValue,
|
||||
channelReserveSatoshis = 0,
|
||||
htlcMinimumMsat = 0,
|
||||
feeratePerKw = Globals.default_feeratePerKw,
|
||||
toSelfDelay = Globals.default_delay_blocks,
|
||||
feeratePerKw = Globals.feeratePerKw,
|
||||
toSelfDelay = Globals.delay_blocks,
|
||||
maxAcceptedHtlcs = 100,
|
||||
fundingPrivKey = generateKey(0),
|
||||
revocationSecret = generateKey(1),
|
||||
|
@ -56,13 +56,17 @@ class Register(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScrip
|
|||
delayedPaymentKey = generateKey(3),
|
||||
defaultFinalScriptPubKey = defaultFinalScriptPubKey,
|
||||
shaSeed = Globals.Node.seed,
|
||||
isFunder = amount_opt.isDefined
|
||||
isFunder = funding_opt.isDefined
|
||||
)
|
||||
|
||||
def makeChannel(conn: ActorRef, publicKey: BinaryData): ActorRef = {
|
||||
val channel = context.actorOf(Channel.props(conn, blockchain, paymentHandler, localParams, publicKey.toString(), Some(Globals.autosign_interval)), s"channel-$counter")
|
||||
amount_opt match {
|
||||
case Some(amount) => channel ! INPUT_INIT_FUNDER(amount.amount, 0)
|
||||
def makeChannel(conn: ActorRef, publicKey: PublicKey, ctx: ActorContext): ActorRef = {
|
||||
// note that we use transport's context and not register's context
|
||||
val channel = ctx.actorOf(Channel.props(conn, watcher, router, relayer, localParams, publicKey, Some(Globals.autosign_interval)), "channel")
|
||||
funding_opt match {
|
||||
case Some(funding) => pushmsat_opt match {
|
||||
case Some(pushmsat) => channel ! INPUT_INIT_FUNDER(funding.amount, pushmsat.amount)
|
||||
case None => channel ! INPUT_INIT_FUNDER(funding.amount, 0)
|
||||
}
|
||||
case None => channel ! INPUT_INIT_FUNDEE()
|
||||
}
|
||||
channel
|
||||
|
@ -72,7 +76,7 @@ class Register(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScrip
|
|||
new TransportHandler[LightningMessage](
|
||||
KeyPair(Globals.Node.publicKey.toBin, Globals.Node.privateKey.toBin),
|
||||
pubkey,
|
||||
isWriter = amount_opt.isDefined,
|
||||
isWriter = funding_opt.isDefined,
|
||||
them = connection,
|
||||
listenerFactory = makeChannel,
|
||||
serializer = LightningMessageSerializer)),
|
||||
|
@ -95,14 +99,14 @@ class Register(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScrip
|
|||
|
||||
object Register {
|
||||
|
||||
def props(blockchain: ActorRef, paymentHandler: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Register], blockchain, paymentHandler, defaultFinalScriptPubKey)
|
||||
def props(blockchain: ActorRef, router: ActorRef, relayer: ActorRef, defaultFinalScriptPubKey: Seq[ScriptElt]) = Props(classOf[Register], blockchain, router, relayer, defaultFinalScriptPubKey)
|
||||
|
||||
// @formatter:off
|
||||
case class CreateChannel(connection: ActorRef, pubkey: Option[BinaryData], anchorAmount: Option[Satoshi])
|
||||
case class CreateChannel(connection: ActorRef, pubkey: Option[BinaryData], fundingSatoshis: Option[Satoshi], pushMsat: Option[MilliSatoshi])
|
||||
|
||||
case class ListChannels()
|
||||
|
||||
case class SendCommand(channelId: String, cmd: Command)
|
||||
case class SendCommand(channelId: Long, cmd: Command)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
|
@ -110,22 +114,22 @@ object Register {
|
|||
* Once it reaches NORMAL state, channel creates a [[fr.acinq.eclair.channel.AliasActor]]
|
||||
* which name is counterparty_id-anchor_id
|
||||
*/
|
||||
def create_alias(node_id: BinaryData, anchor_id: BinaryData)(implicit context: ActorContext) =
|
||||
context.actorOf(Props(new AliasActor(context.self)), name = s"$node_id-$anchor_id")
|
||||
def createAlias(nodeAddress: BinaryData, channelId: Long)(implicit context: ActorContext) =
|
||||
context.actorOf(Props(new AliasActor(context.self)), name = s"$nodeAddress-${java.lang.Long.toUnsignedString(channelId)}")
|
||||
|
||||
def actorPathToNodeId(system: ActorSystem, nodeId: BinaryData): ActorPath =
|
||||
system / "register" / "auth-handler-*" / "channel" / s"${nodeId}-*"
|
||||
def actorPathToNodeAddress(system: ActorSystem, nodeAddress: BinaryData): ActorPath =
|
||||
system / "register" / "transport-handler-*" / "channel" / s"$nodeAddress-*"
|
||||
|
||||
def actorPathToNodeId(nodeId: BinaryData)(implicit context: ActorContext): ActorPath = actorPathToNodeId(context.system, nodeId)
|
||||
def actorPathToNodeAddress(nodeAddress: BinaryData)(implicit context: ActorContext): ActorPath = actorPathToNodeAddress(context.system, nodeAddress)
|
||||
|
||||
def actorPathToChannelId(system: ActorSystem, channelId: BinaryData): ActorPath =
|
||||
system / "register" / "auth-handler-*" / "channel" / s"*-${channelId}"
|
||||
def actorPathToChannelId(system: ActorSystem, channelId: Long): ActorPath =
|
||||
system / "register" / "transport-handler-*" / "channel" / s"*-${channelId}"
|
||||
|
||||
def actorPathToChannelId(channelId: BinaryData)(implicit context: ActorContext): ActorPath = actorPathToChannelId(context.system, channelId)
|
||||
def actorPathToChannelId(channelId: Long)(implicit context: ActorContext): ActorPath = actorPathToChannelId(context.system, channelId)
|
||||
|
||||
def actorPathToChannels()(implicit context: ActorContext): ActorPath =
|
||||
context.system / "register" / "auth-handler-*" / "channel"
|
||||
context.system / "register" / "transport-handler-*" / "channel"
|
||||
|
||||
def actorPathToHandlers()(implicit context: ActorContext): ActorPath =
|
||||
context.system / "register" / "auth-handler-*"
|
||||
def actorPathToTransportHandlers()(implicit context: ActorContext): ActorPath =
|
||||
context.system / "register" / "transport-handler-*"
|
||||
}
|
|
@ -101,7 +101,6 @@ object ChaCha20Poly1305 extends Logging {
|
|||
* @param aad additional authentication data. can be empty
|
||||
* @param mac authentication mac
|
||||
* @return the decrypted plaintext if the mac is valid.
|
||||
* @throws IllegalArgumentException error if the mac is invalid
|
||||
*/
|
||||
def decrypt(key: BinaryData, nonce: BinaryData, ciphertext: BinaryData, aad: BinaryData, mac: BinaryData): BinaryData = {
|
||||
val polykey: BinaryData = ChaCha20.encrypt(new Array[Byte](32), key, nonce)
|
||||
|
|
|
@ -107,7 +107,7 @@ object Sphinx {
|
|||
* @param associatedData associated data
|
||||
* @param packet packet received by this node
|
||||
* @return a (payload, address, packet) tuple where:
|
||||
* - payload is the teh per-hop payload for this node
|
||||
* - payload is the per-hop payload for this node
|
||||
* - address is the next destination. 0x0000000000000000000000000000000000000000 means this node was the final
|
||||
* destination
|
||||
* - packet is the next packet, to be forwarded to address
|
||||
|
|
|
@ -2,9 +2,11 @@ package fr.acinq.eclair.crypto
|
|||
|
||||
import java.nio.ByteOrder
|
||||
|
||||
import akka.actor.{Actor, ActorContext, ActorRef, LoggingFSM, Terminated}
|
||||
import akka.actor.{Actor, ActorRef, LoggingFSM, OneForOneStrategy, SupervisorStrategy, Terminated}
|
||||
import akka.io.Tcp.{PeerClosed, _}
|
||||
import akka.util.ByteString
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.{BinaryData, Protocol}
|
||||
import fr.acinq.eclair.channel.{CMD_CLOSE, Command}
|
||||
import fr.acinq.eclair.crypto.Noise._
|
||||
|
@ -29,7 +31,7 @@ import scala.util.{Failure, Success, Try}
|
|||
* @param listenerFactory factory that will be used to create the listener that will receive decrypted messages once the
|
||||
* handshake phase as been completed. Its parameters are a tuple (transport handler, remote public key)
|
||||
*/
|
||||
class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], them: ActorRef, isWriter: Boolean, listenerFactory: (ActorRef, BinaryData) => ActorRef, serializer: TransportHandler.Serializer[T]) extends Actor with LoggingFSM[TransportHandler.State, TransportHandler.Data] {
|
||||
class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], them: ActorRef, isWriter: Boolean, listenerFactory: (ActorRef, PublicKey, ActorContext) => ActorRef, serializer: TransportHandler.Serializer[T]) extends Actor with LoggingFSM[TransportHandler.State, TransportHandler.Data] {
|
||||
|
||||
import TransportHandler._
|
||||
|
||||
|
@ -70,7 +72,7 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], th
|
|||
|
||||
reader.read(payload) match {
|
||||
case (writer, _, Some((dec, enc, ck))) =>
|
||||
val listener = listenerFactory(self, writer.rs)
|
||||
val listener = listenerFactory(self, PublicKey(writer.rs), context)
|
||||
context.watch(listener)
|
||||
val (nextStateData, plaintextMessages) = WaitingForCyphertextData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), None, remainder, listener).decrypt
|
||||
sendToListener(listener, plaintextMessages)
|
||||
|
@ -87,7 +89,7 @@ class TransportHandler[T: ClassTag](keyPair: KeyPair, rs: Option[BinaryData], th
|
|||
}
|
||||
case (_, message, Some((enc, dec, ck))) => {
|
||||
them ! Write(TransportHandler.prefix +: message)
|
||||
val listener = listenerFactory(self, writer.rs)
|
||||
val listener = listenerFactory(self, PublicKey(writer.rs), context)
|
||||
context.watch(listener)
|
||||
val (nextStateData, plaintextMessages) = WaitingForCyphertextData(ExtendedCipherState(enc, ck), ExtendedCipherState(dec, ck), None, remainder, listener).decrypt
|
||||
sendToListener(listener, plaintextMessages)
|
||||
|
|
|
@ -15,7 +15,6 @@ import fr.acinq.eclair.Setup
|
|||
import fr.acinq.eclair.channel.ChannelEvent
|
||||
import fr.acinq.eclair.gui.controllers.MainController
|
||||
import fr.acinq.eclair.gui.stages.SplashStage
|
||||
import fr.acinq.eclair.router.NetworkEvent
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
/**
|
||||
|
@ -40,7 +39,6 @@ class FxApp extends Application with Logging {
|
|||
val controller = new MainController(handlers, primaryStage, setup)
|
||||
val guiUpdater = setup.system.actorOf(Props(classOf[GUIUpdater], primaryStage, controller, setup), "gui-updater")
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
setup.fatalEventFuture onSuccess {
|
||||
|
|
|
@ -7,14 +7,10 @@ import javafx.scene.layout.VBox
|
|||
import javafx.stage.Stage
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||
import com.mxgraph.layout.mxCircleLayout
|
||||
import com.mxgraph.swing.mxGraphComponent
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.gui.controllers.{ChannelPaneController, MainController}
|
||||
import fr.acinq.eclair.router.{ChannelDesc, ChannelDiscovered}
|
||||
import fr.acinq.eclair.{Globals, Setup}
|
||||
import org.jgrapht.ext.JGraphXAdapter
|
||||
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
|
||||
|
||||
|
||||
|
@ -38,7 +34,7 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
|
|||
log.info(s"new channel: $channel")
|
||||
|
||||
val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
|
||||
val channelPaneController = new ChannelPaneController(theirNodeId)
|
||||
val channelPaneController = new ChannelPaneController(theirNodeId.toBin.toString())
|
||||
loader.setController(channelPaneController)
|
||||
val root = loader.load[VBox]
|
||||
|
||||
|
@ -69,7 +65,7 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
|
|||
}
|
||||
})
|
||||
|
||||
case ChannelChangedState(channel, _, previousState, currentState, currentData) =>
|
||||
case ChannelChangedState(channel, _, _, previousState, currentState, currentData) =>
|
||||
val channelPane = m(channel)
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run(): Unit = {
|
||||
|
@ -87,7 +83,8 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
|
|||
}
|
||||
})
|
||||
|
||||
case ChannelDiscovered(ChannelDesc(id, a, b)) =>
|
||||
// TODO
|
||||
/*case ChannelDiscovered(ChannelDesc(id, a, b)) =>
|
||||
graph.addVertex(BinaryData(a))
|
||||
graph.addVertex(BinaryData(b))
|
||||
graph.addEdge(a, b, new NamedEdge(id))
|
||||
|
@ -100,7 +97,7 @@ class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Set
|
|||
lay.execute(jgxAdapter.getDefaultParent())
|
||||
mainController.swingNode.setContent(component)
|
||||
}
|
||||
})
|
||||
})*/
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ package fr.acinq.eclair.gui
|
|||
import javafx.application.Platform
|
||||
import javafx.scene.control.{TextArea, TextField}
|
||||
|
||||
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.io.Client
|
||||
import fr.acinq.eclair.payment.CreatePayment
|
||||
|
@ -17,12 +17,12 @@ class Handlers(setup: Setup) extends Logging {
|
|||
|
||||
import setup._
|
||||
|
||||
def open(hostPort: String, amount: Satoshi) = {
|
||||
def open(hostPort: String, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi) = {
|
||||
val regex = "([a-fA-F0-9]+)@([a-zA-Z0-9\\.\\-_]+):([0-9]+)".r
|
||||
hostPort match {
|
||||
case regex(pubkey, host, port) =>
|
||||
logger.info(s"connecting to $host:$port")
|
||||
system.actorOf(Client.props(host, port.toInt, pubkey, amount, register))
|
||||
system.actorOf(Client.props(host, port.toInt, pubkey, fundingSatoshis, pushMsat, register))
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{Button, ContextMenu, Label, ProgressBar}
|
||||
import javafx.scene.input.ContextMenuEvent
|
||||
import javafx.scene.control.{Button, ContextMenu, TextField, ProgressBar}
|
||||
import javafx.scene.input.{ContextMenuEvent, MouseEvent}
|
||||
|
||||
import fr.acinq.eclair.gui.utils.ContextMenuUtils
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
/**
|
||||
|
@ -13,26 +13,27 @@ import grizzled.slf4j.Logging
|
|||
class ChannelPaneController(theirNodeIdValue: String) extends Logging {
|
||||
|
||||
var channelIdValue = ""
|
||||
@FXML var channelId: Label = _
|
||||
@FXML var channelId: TextField = _
|
||||
@FXML var balanceBar: ProgressBar = _
|
||||
@FXML var amountUs: Label = _
|
||||
@FXML var nodeId: Label = _
|
||||
@FXML var capacity: Label = _
|
||||
@FXML var funder: Label = _
|
||||
@FXML var state: Label = _
|
||||
@FXML var amountUs: TextField = _
|
||||
@FXML var nodeId: TextField = _
|
||||
@FXML var capacity: TextField = _
|
||||
@FXML var funder: TextField = _
|
||||
@FXML var state: TextField = _
|
||||
@FXML var close: Button = _
|
||||
|
||||
var contextMenu: ContextMenu = _
|
||||
|
||||
@FXML def handleChannelIdContext(event: ContextMenuEvent): Unit = {
|
||||
@FXML def openChannelContext(event: ContextMenuEvent): Unit = {
|
||||
if (contextMenu != null) contextMenu.hide()
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(channelIdValue)
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(
|
||||
new CopyAction("Copy Channel Id", channelIdValue),
|
||||
new CopyAction("Copy Node Pubkey", theirNodeIdValue)
|
||||
))
|
||||
contextMenu.show(channelId, event.getScreenX, event.getScreenY)
|
||||
}
|
||||
|
||||
@FXML def handleTheirNodeIdContext(event: ContextMenuEvent): Unit = {
|
||||
@FXML def closeChannelContext(event: MouseEvent): Unit = {
|
||||
if (contextMenu != null) contextMenu.hide()
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(theirNodeIdValue)
|
||||
contextMenu.show(nodeId, event.getScreenX, event.getScreenY)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import javafx.beans.value.{ChangeListener, ObservableValue}
|
|||
import javafx.embed.swing.SwingNode
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{ContextMenu, Label, MenuItem, Tab}
|
||||
import javafx.scene.input.ContextMenuEvent
|
||||
import javafx.scene.input.{ContextMenuEvent, MouseEvent}
|
||||
import javafx.scene.layout.{BorderPane, TilePane, VBox}
|
||||
import javafx.stage.Stage
|
||||
|
||||
import com.mxgraph.swing.mxGraphComponent
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.stages.{AboutStage, OpenChannelStage, ReceivePaymentStage, SendPaymentStage}
|
||||
import fr.acinq.eclair.gui.utils.ContextMenuUtils
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import fr.acinq.eclair.{Globals, Setup}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
|
@ -33,6 +33,7 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup)
|
|||
@FXML var channelInfo: VBox = _
|
||||
@FXML var channelBox: VBox = _
|
||||
@FXML var tilePane: TilePane = _
|
||||
@FXML var channelsTab: Tab = _
|
||||
@FXML var graphTab: Tab = _
|
||||
|
||||
val swingNode: SwingNode = new SwingNode()
|
||||
|
@ -49,7 +50,9 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup)
|
|||
bitcoinChain.getStyleClass.add(setup.chain)
|
||||
|
||||
graphTab.setContent(swingNode)
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(Globals.Node.id)
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(
|
||||
new CopyAction("Copy Pubkey", Globals.Node.id),
|
||||
new CopyAction("Copy URI", s"${Globals.Node.id}@${Globals.Node.address.getHostString}:${Globals.Node.address.getPort}" )))
|
||||
|
||||
if (channelBox.getChildren.size() > 0) {
|
||||
channelInfo.setScaleY(0)
|
||||
|
@ -61,7 +64,11 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup)
|
|||
if (channelBox.getChildren.size() > 0) {
|
||||
channelInfo.setScaleY(0)
|
||||
channelInfo.setOpacity(0)
|
||||
} else {
|
||||
channelInfo.setScaleY(1)
|
||||
channelInfo.setOpacity(1)
|
||||
}
|
||||
channelsTab.setText(s"Channels (${channelBox.getChildren.size})")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -107,10 +114,14 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup)
|
|||
}
|
||||
}
|
||||
|
||||
@FXML def handleNodeIdContext(event: ContextMenuEvent): Unit = {
|
||||
@FXML def openNodeIdContext(event: ContextMenuEvent): Unit = {
|
||||
contextMenu.show(labelNodeId, event.getScreenX, event.getScreenY)
|
||||
}
|
||||
|
||||
@FXML def closeNodeIdContext(event: MouseEvent): Unit = {
|
||||
contextMenu.hide()
|
||||
}
|
||||
|
||||
def positionAtCenter(childStage: Stage): Unit = {
|
||||
childStage.setX(stage.getX() + stage.getWidth() / 2 - childStage.getWidth() / 2)
|
||||
childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2)
|
||||
|
|
|
@ -5,7 +5,7 @@ import javafx.fxml.FXML
|
|||
import javafx.scene.control.{Button, ComboBox, Label, TextField}
|
||||
import javafx.stage.Stage
|
||||
|
||||
import fr.acinq.bitcoin.Satoshi
|
||||
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair.Setup
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.utils.GUIValidators
|
||||
|
@ -18,8 +18,10 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
|
|||
|
||||
@FXML var host: TextField = _
|
||||
@FXML var hostError: Label = _
|
||||
@FXML var amount: TextField = _
|
||||
@FXML var amountError: Label = _
|
||||
@FXML var fundingSatoshis: TextField = _
|
||||
@FXML var fundingSatoshisError: Label = _
|
||||
@FXML var pushMsat: TextField = _
|
||||
@FXML var pushMsatError: Label = _
|
||||
@FXML var unit: ComboBox[String] = _
|
||||
@FXML var button: Button = _
|
||||
|
||||
|
@ -28,16 +30,28 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
|
|||
}
|
||||
|
||||
@FXML def handleOpen(event: ActionEvent): Unit = {
|
||||
if (GUIValidators.validate(host.getText, hostError, GUIValidators.hostRegex)
|
||||
&& GUIValidators.validate(amount.getText, amountError, GUIValidators.amountRegex)) {
|
||||
val raw = amount.getText.toLong
|
||||
val smartAmount = unit.getValue match {
|
||||
case "milliBTC" => Satoshi(raw * 100000L)
|
||||
case "Satoshi" => Satoshi(raw)
|
||||
case "milliSatoshi" => Satoshi(raw / 1000L)
|
||||
if (GUIValidators.validate(host.getText, hostError, "Please use a valid url (pubkey@host:port)", GUIValidators.hostRegex)
|
||||
& GUIValidators.validate(fundingSatoshis.getText, fundingSatoshisError, "Funding must be numeric", GUIValidators.amountRegex)
|
||||
&& GUIValidators.validate(fundingSatoshisError, "Funding must be greater than 0", fundingSatoshis.getText.toLong > 0)) {
|
||||
val rawFunding = fundingSatoshis.getText.toLong
|
||||
val smartFunding = unit.getValue match {
|
||||
case "milliBTC" => Satoshi(rawFunding * 100000L)
|
||||
case "Satoshi" => Satoshi(rawFunding)
|
||||
case "milliSatoshi" => Satoshi(rawFunding / 1000L)
|
||||
}
|
||||
|
||||
if (GUIValidators.validate(fundingSatoshisError, "Funding must be 0.1 BTC or less", smartFunding.toLong <= 10000000)) {
|
||||
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)) {
|
||||
handlers.open(host.getText, smartFunding, MilliSatoshi(pushMsat.getText.toLong))
|
||||
stage.close()
|
||||
}
|
||||
} else {
|
||||
handlers.open(host.getText, smartFunding, Satoshi(0))
|
||||
stage.close()
|
||||
}
|
||||
}
|
||||
handlers.open(host.getText, smartAmount)
|
||||
stage.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ class ReceivePaymentController(val handlers: Handlers, val stage: Stage, val set
|
|||
}
|
||||
|
||||
@FXML def handleGenerate(event: ActionEvent): Unit = {
|
||||
if (GUIValidators.validate(amount.getText, amountError, GUIValidators.amountRegex)) {
|
||||
if (GUIValidators.validate(amount.getText, amountError, "Amount must be numeric", GUIValidators.amountRegex)
|
||||
&& GUIValidators.validate(amountError, "Amount must be greater than 0", amount.getText.toLong > 0)
|
||||
&& GUIValidators.validate(amountError, "Amount must be 4200000 satoshis or less", amount.getText.toLong <= 4200000)) {
|
||||
Try(amount.getText.toLong) match {
|
||||
case Success(amountMsat) => handlers.getPaymentRequest(amount.getText.toLong, paymentRequest)
|
||||
case _ => {}
|
||||
|
|
|
@ -3,7 +3,7 @@ package fr.acinq.eclair.gui.controllers
|
|||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{Label, TextArea}
|
||||
import javafx.scene.control.{Label, TextArea, TextField}
|
||||
import javafx.stage.Stage
|
||||
|
||||
import fr.acinq.eclair.Setup
|
||||
|
@ -19,37 +19,36 @@ class SendPaymentController(val handlers: Handlers, val stage: Stage, val setup:
|
|||
|
||||
@FXML var paymentRequest: TextArea = _
|
||||
@FXML var paymentRequestError: Label = _
|
||||
@FXML var nodeIdLabel: Label = _
|
||||
@FXML var amountLabel: Label = _
|
||||
@FXML var hashLabel: Label = _
|
||||
@FXML var nodeIdField: TextField = _
|
||||
@FXML var amountField: TextField = _
|
||||
@FXML var hashField: TextField = _
|
||||
|
||||
@FXML def initialize(): Unit = {
|
||||
|
||||
paymentRequest.textProperty().addListener(new ChangeListener[String] {
|
||||
def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String): Unit = {
|
||||
if (GUIValidators.validate(paymentRequest.getText, paymentRequestError, GUIValidators.paymentRequestRegex)) {
|
||||
if (GUIValidators.validate(paymentRequest.getText, paymentRequestError, "Please use a valid payment request", GUIValidators.paymentRequestRegex)) {
|
||||
val Array(nodeId, amount, hash) = paymentRequest.getText.split(":")
|
||||
amountLabel.setText(amount)
|
||||
nodeIdLabel.setText(nodeId)
|
||||
hashLabel.setText(hash)
|
||||
amountField.setText(amount)
|
||||
nodeIdField.setText(nodeId)
|
||||
hashField.setText(hash)
|
||||
} else {
|
||||
amountLabel.setText("0")
|
||||
nodeIdLabel.setText("N/A")
|
||||
hashLabel.setText("N/A")
|
||||
amountField.setText("0")
|
||||
nodeIdField.setText("N/A")
|
||||
hashField.setText("N/A")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@FXML def handleSend(event: ActionEvent): Unit = {
|
||||
if (GUIValidators.validate(paymentRequest.getText, paymentRequestError, GUIValidators.paymentRequestRegex)) {
|
||||
if (GUIValidators.validate(paymentRequest.getText, paymentRequestError, "Please use a valid payment request", GUIValidators.paymentRequestRegex)) {
|
||||
val Array(nodeId, amount, hash) = paymentRequest.getText.split(":")
|
||||
handlers.send(nodeId, hash, amount)
|
||||
stage.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML def handleClose(event: ActionEvent): Unit = {
|
||||
stage.close()
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ class OpenChannelStage(handlers: Handlers, setup: Setup) extends Stage() {
|
|||
initStyle(StageStyle.DECORATED)
|
||||
getIcons().add(new Image("/gui/commons/images/eclair02.png", false))
|
||||
setTitle("Open a new channel")
|
||||
setWidth(1100)
|
||||
setHeight(250)
|
||||
setWidth(550)
|
||||
setHeight(350)
|
||||
setResizable(false)
|
||||
|
||||
// get fxml/controller
|
||||
|
|
|
@ -4,29 +4,43 @@ import javafx.event.{ActionEvent, EventHandler}
|
|||
import javafx.scene.control.{ContextMenu, MenuItem}
|
||||
import javafx.scene.input.{Clipboard, ClipboardContent}
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
/**
|
||||
* Created by DPA on 28/09/2016.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Action to copy a value
|
||||
*
|
||||
* @param label label of the copy action in the context menu, defaults to copy value
|
||||
* @param value the value to copy
|
||||
*/
|
||||
case class CopyAction(label: String = "Copy Value", value: String)
|
||||
|
||||
object ContextMenuUtils {
|
||||
val clip = Clipboard.getSystemClipboard
|
||||
|
||||
/**
|
||||
* Builds a Context Menu with a single Copy action.
|
||||
* Builds a Context Menu containing a list of copy actions.
|
||||
*
|
||||
* @param valueToCopy the value to copy to clipboard
|
||||
* @param actions list of copy action (label + value)
|
||||
* @return javafx context menu
|
||||
*/
|
||||
def buildCopyContext(valueToCopy: String): ContextMenu = {
|
||||
def buildCopyContext (actions: List[CopyAction]): ContextMenu = {
|
||||
val context = new ContextMenu()
|
||||
val copyItem = new MenuItem("Copy Value")
|
||||
copyItem.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = {
|
||||
val clipContent = new ClipboardContent
|
||||
clipContent.putString(valueToCopy)
|
||||
clip.setContent(clipContent)
|
||||
}
|
||||
})
|
||||
context.getItems.addAll(copyItem)
|
||||
return context
|
||||
for (action <- actions ) {
|
||||
val copyItem = new MenuItem(action.label)
|
||||
copyItem.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = {
|
||||
val clipContent = new ClipboardContent
|
||||
clipContent.putString(action.value)
|
||||
clip.setContent(clipContent)
|
||||
}
|
||||
})
|
||||
context.getItems.addAll(copyItem)
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,22 +17,34 @@ object GUIValidators {
|
|||
/**
|
||||
* Validate a field against a regex. If field does not match regex, validatorLabel is shown.
|
||||
*
|
||||
* @param field String content of the field to validate
|
||||
* @param validatorLabel JFX label associated to the field
|
||||
* @param regex Scala regex that the field must match
|
||||
* @return true if field is valid, false otherwise
|
||||
* @param field String content of the field to validate
|
||||
* @param validatorLabel JFX label associated to the field.
|
||||
* @param validatorMessage Message displayed if the field is invalid. It should describe the cause of
|
||||
* the validation failure
|
||||
* @param regex Scala regex that the field must match
|
||||
* @return true if field is valid, false otherwise
|
||||
*/
|
||||
def validate(field: String, validatorLabel: Label, regex: Regex): Boolean = {
|
||||
|
||||
def validate(field: String, validatorLabel: Label, validatorMessage: String, regex: Regex): Boolean = {
|
||||
return field match {
|
||||
case regex() => {
|
||||
validatorLabel.setOpacity(0)
|
||||
return true
|
||||
return validate(validatorLabel, validatorMessage, true)
|
||||
}
|
||||
case _ => {
|
||||
validatorLabel.setOpacity(1)
|
||||
return false
|
||||
return validate(validatorLabel, validatorMessage, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a label with an error message.
|
||||
*
|
||||
* @param errorLabel JFX label containing an error messsage
|
||||
* @param validCondition if true the label is hidden, else it is shown
|
||||
* @return true if field is valid, false otherwise
|
||||
*/
|
||||
def validate(errorLabel: Label, errorMessage: String, validCondition: Boolean): Boolean = {
|
||||
errorLabel.setOpacity( if (validCondition) 0 else 1 )
|
||||
errorLabel.setText(errorMessage)
|
||||
validCondition
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ import java.net.InetSocketAddress
|
|||
|
||||
import akka.actor._
|
||||
import akka.io.{IO, Tcp}
|
||||
import fr.acinq.bitcoin.{BinaryData, Satoshi}
|
||||
import fr.acinq.bitcoin.{BinaryData, MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair.channel.Register.CreateChannel
|
||||
|
||||
/**
|
||||
* Created by PM on 27/10/2015.
|
||||
*/
|
||||
class Client(remote: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, register: ActorRef) extends Actor with ActorLogging {
|
||||
class Client(remote: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef) extends Actor with ActorLogging {
|
||||
|
||||
import Tcp._
|
||||
import context.system
|
||||
|
@ -23,15 +23,15 @@ class Client(remote: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, reg
|
|||
case c@Connected(remote, local) =>
|
||||
log.info(s"connected to $remote")
|
||||
val connection = sender()
|
||||
register ! CreateChannel(connection, Some(pubkey), Some(amount))
|
||||
register ! CreateChannel(connection, Some(pubkey), Some(fundingSatoshis), Some(pushMsat))
|
||||
// TODO: kill this actor ?
|
||||
}
|
||||
}
|
||||
|
||||
object Client extends App {
|
||||
|
||||
def props(address: InetSocketAddress, pubkey: BinaryData, amount: Satoshi, register: ActorRef): Props = Props(classOf[Client], address, pubkey, amount, register)
|
||||
def props(address: InetSocketAddress, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], address, pubkey, fundingSatoshis, pushMsat, register)
|
||||
|
||||
def props(host: String, port: Int, pubkey: BinaryData, amount: Satoshi, register: ActorRef): Props = Props(classOf[Client], new InetSocketAddress(host, port), pubkey, amount, register)
|
||||
def props(host: String, port: Int, pubkey: BinaryData, fundingSatoshis: Satoshi, pushMsat: MilliSatoshi, register: ActorRef): Props = Props(classOf[Client], new InetSocketAddress(host, port), pubkey, fundingSatoshis, pushMsat, register)
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class Server(address: InetSocketAddress, register: ActorRef) extends Actor with
|
|||
case c@Connected(remote, local) =>
|
||||
log.info(s"connected to $remote")
|
||||
val connection = sender()
|
||||
register ! CreateChannel(connection, None, None)
|
||||
register ! CreateChannel(connection, None, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
package fr.acinq
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin._
|
||||
import lightning.bitcoin_pubkey
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
package object eclair {
|
||||
|
||||
implicit def bin2pubkey(in: BinaryData) = bitcoin_pubkey(ByteString.copyFrom(in))
|
||||
def toShortId(blockHeight: Int, txIndex: Int, outputIndex: Int): Long =
|
||||
((blockHeight & 0xFFFFFFL) << 40) | ((txIndex & 0xFFFFFFL) << 16) | (outputIndex & 0xFFFFL)
|
||||
|
||||
implicit def array2pubkey(in: Array[Byte]) = bin2pubkey(in)
|
||||
def fromShortId(id: Long): (Int, Int, Int) =
|
||||
(((id >> 40) & 0xFFFFFF).toInt, ((id >> 16) & 0xFFFFFF).toInt, (id & 0xFFFF).toInt)
|
||||
|
||||
implicit def pubkey2bin(in: bitcoin_pubkey): BinaryData = in.key.toByteArray
|
||||
def randomKey: PrivateKey = PrivateKey({
|
||||
val bin = Array.fill[Byte](32)(0)
|
||||
// TODO: use secure random
|
||||
Random.nextBytes(bin)
|
||||
bin
|
||||
}, compressed = true)
|
||||
|
||||
implicit def bytestring2bin(in: ByteString): BinaryData = in.toByteArray
|
||||
|
||||
implicit def bin2bytestring(in: BinaryData): ByteString = ByteString.copyFrom(in)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param base fixed fee
|
||||
* @param proportional proportional fee
|
||||
* @param msat amount in millisatoshi
|
||||
* @return the fee (in msat) that a node should be paid to forward an HTLC of 'amount' millisatoshis
|
||||
*/
|
||||
def nodeFee(base: Long, proportional: Long, msat: Long): Long = base + (proportional * msat) / 1000000
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
|
||||
/**
|
||||
* Created by PM on 01/02/2017.
|
||||
*/
|
||||
class PaymentEvent
|
||||
|
||||
case class PaymentSent(channel: ActorRef, h: BinaryData) extends PaymentEvent
|
||||
|
||||
case class PaymentFailed(channel: ActorRef, h: BinaryData, reason: String) extends PaymentEvent
|
||||
|
||||
case class PaymentReceived(channel: ActorRef, h: BinaryData) extends PaymentEvent
|
|
@ -1,26 +1,27 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.eclair.blockchain.peer.CurrentBlockCount
|
||||
|
||||
/**
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
class PaymentInitiator(router: ActorRef, selector: ActorRef, initialBlockCount: Long) extends Actor with ActorLogging {
|
||||
class PaymentInitiator(sourceNodeId: BinaryData, router: ActorRef, initialBlockCount: Long) extends Actor with ActorLogging {
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
|
||||
|
||||
override def receive: Receive = main(initialBlockCount)
|
||||
|
||||
def main(currentBlockCount: Long): Receive = {
|
||||
case CurrentBlockCount(count) => context.become(main(currentBlockCount))
|
||||
case CurrentBlockCount(count) => context.become(main(count))
|
||||
case c: CreatePayment =>
|
||||
val payFsm = context.actorOf(PaymentLifecycle.props(router, selector, initialBlockCount))
|
||||
val payFsm = context.actorOf(PaymentLifecycle.props(sourceNodeId, router, initialBlockCount))
|
||||
payFsm forward c
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object PaymentInitiator {
|
||||
def props(router: ActorRef, selector: ActorRef, initialBlockCount: Long) = Props(classOf[PaymentInitiator], router, selector, initialBlockCount)
|
||||
def props(sourceNodeId: BinaryData, router: ActorRef, initialBlockCount: Long) = Props(classOf[PaymentInitiator], sourceNodeId, router, initialBlockCount)
|
||||
}
|
||||
|
|
|
@ -3,27 +3,26 @@ 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.Crypto.PublicKey
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.blockchain.peer.CurrentBlockCount
|
||||
import fr.acinq.eclair.channel.{CMD_ADD_HTLC, PaymentFailed, PaymentSent}
|
||||
import fr.acinq.eclair.channel.{CMD_ADD_HTLC, Register}
|
||||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.router._
|
||||
import lightning.route_step
|
||||
import lightning.route_step.Next
|
||||
import fr.acinq.eclair.wire.{Codecs, PerHopPayload}
|
||||
import scodec.Attempt
|
||||
|
||||
// @formatter:off
|
||||
|
||||
case class CreatePayment(amountMsat: Int, h: BinaryData, targetNodeId: BinaryData)
|
||||
case class CreatePayment(amountMsat: Int, paymentHash: BinaryData, targetNodeId: BinaryData)
|
||||
|
||||
sealed trait Data
|
||||
case class WaitingForRequest(currentBlockCount: Long) extends Data
|
||||
case class WaitingForRoute(sender: ActorRef, c: CreatePayment, currentBlockCount: Long) extends Data
|
||||
case class WaitingForChannel(sender: ActorRef,c: CreatePayment, r: Seq[BinaryData], currentBlockCount: Long) extends Data
|
||||
case class WaitingForComplete(sender: ActorRef,c: CMD_ADD_HTLC, channel: ActorRef) extends Data
|
||||
case class WaitingForComplete(sender: ActorRef,c: CMD_ADD_HTLC) extends Data
|
||||
|
||||
sealed trait State
|
||||
case object WAITING_FOR_REQUEST extends State
|
||||
case object WAITING_FOR_ROUTE extends State
|
||||
case object WAITING_FOR_CHANNEL extends State
|
||||
case object WAITING_FOR_PAYMENT_COMPLETE extends State
|
||||
|
||||
// @formatter:on
|
||||
|
@ -31,84 +30,101 @@ case object WAITING_FOR_PAYMENT_COMPLETE extends State
|
|||
/**
|
||||
* Created by PM on 26/08/2016.
|
||||
*/
|
||||
class PaymentLifecycle(router: ActorRef, selector: ActorRef, initialBlockCount: Long) extends LoggingFSM[State, Data] {
|
||||
class PaymentLifecycle(sourceNodeId: BinaryData, router: ActorRef, currentBlockCount: Long) extends LoggingFSM[State, Data] {
|
||||
|
||||
import PaymentLifecycle._
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[CurrentBlockCount])
|
||||
|
||||
startWith(WAITING_FOR_REQUEST, WaitingForRequest(initialBlockCount))
|
||||
startWith(WAITING_FOR_REQUEST, WaitingForRequest(currentBlockCount))
|
||||
|
||||
when(WAITING_FOR_REQUEST) {
|
||||
case Event(c: CreatePayment, WaitingForRequest(currentBlockCount)) =>
|
||||
router ! RouteRequest(Globals.Node.publicKey, c.targetNodeId)
|
||||
router ! RouteRequest(sourceNodeId, c.targetNodeId)
|
||||
goto(WAITING_FOR_ROUTE) using WaitingForRoute(sender, c, currentBlockCount)
|
||||
|
||||
case Event(CurrentBlockCount(currentBlockCount), d: WaitingForRequest) =>
|
||||
stay using d.copy(currentBlockCount = currentBlockCount)
|
||||
}
|
||||
|
||||
when(WAITING_FOR_ROUTE) {
|
||||
case Event(RouteResponse(r), WaitingForRoute(s, c, currentBlockCount)) =>
|
||||
selector ! SelectChannelRequest(r.drop(1).head)
|
||||
goto(WAITING_FOR_CHANNEL) using WaitingForChannel(s, c, r, currentBlockCount)
|
||||
case Event(RouteResponse(hops), WaitingForRoute(s, c, currentBlockCount)) =>
|
||||
val firstHop = hops.head
|
||||
val cmd = buildCommand(c.amountMsat, c.paymentHash, hops, currentBlockCount.toInt)
|
||||
context.system.eventStream.subscribe(self, classOf[PaymentEvent])
|
||||
context.actorSelection(Register.actorPathToChannelId(firstHop.lastUpdate.channelId)) ! cmd
|
||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, cmd)
|
||||
|
||||
case Event(f@Failure(t), WaitingForRoute(s, _, _)) =>
|
||||
s ! f
|
||||
stop(FSM.Failure(t))
|
||||
|
||||
case Event(CurrentBlockCount(currentBlockCount), d: WaitingForRoute) =>
|
||||
stay using d.copy(currentBlockCount = currentBlockCount)
|
||||
}
|
||||
|
||||
when(WAITING_FOR_CHANNEL) {
|
||||
case Event(SelectChannelResponse(Some(channel)), WaitingForChannel(s, c, r, currentBlockCount)) =>
|
||||
val next = r.drop(1).head
|
||||
val others = r.drop(2)
|
||||
val route = buildRoute(c.amountMsat, next +: others)
|
||||
val cmd = CMD_ADD_HTLC(route.steps(0).amount, c.h, currentBlockCount.toInt + 100 + route.steps.size - 2, route.copy(steps = route.steps.tail), commit = true)
|
||||
context.system.eventStream.subscribe(self, classOf[PaymentSent])
|
||||
context.system.eventStream.subscribe(self, classOf[PaymentFailed])
|
||||
context.system.eventStream.unsubscribe(self, classOf[CurrentBlockCount])
|
||||
channel ! cmd
|
||||
goto(WAITING_FOR_PAYMENT_COMPLETE) using WaitingForComplete(s, cmd, channel)
|
||||
|
||||
case Event(CurrentBlockCount(currentBlockCount), d: WaitingForChannel) =>
|
||||
stay using d.copy(currentBlockCount = currentBlockCount)
|
||||
}
|
||||
|
||||
when(WAITING_FOR_PAYMENT_COMPLETE) {
|
||||
case Event("ok", _) => stay()
|
||||
|
||||
case Event(e@PaymentSent(_, h), WaitingForComplete(s, cmd, channel)) if h == cmd.paymentHash =>
|
||||
case Event(e@PaymentSent(_, h), WaitingForComplete(s, cmd)) if h == cmd.paymentHash =>
|
||||
s ! "sent"
|
||||
stop(FSM.Normal)
|
||||
|
||||
case Event(e@PaymentFailed(_, h, reason), WaitingForComplete(s, cmd, channel)) if h == cmd.paymentHash =>
|
||||
case Event(e@PaymentFailed(_, h, reason), WaitingForComplete(s, cmd)) if h == cmd.paymentHash =>
|
||||
s ! Status.Failure(new RuntimeException(reason))
|
||||
stop(FSM.Failure(reason))
|
||||
|
||||
case Event(CurrentBlockCount(_), _) =>
|
||||
stay
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object PaymentLifecycle {
|
||||
|
||||
def props(router: ActorRef, selector: ActorRef, initialBlockCount: Long) = Props(classOf[PaymentLifecycle], router, selector, initialBlockCount)
|
||||
def props(sourceNodeId: BinaryData, router: ActorRef, initialBlockCount: Long) = Props(classOf[PaymentLifecycle], sourceNodeId, router, initialBlockCount)
|
||||
|
||||
def buildRoute(finalAmountMsat: Int, nodeIds: Seq[BinaryData]): lightning.route = {
|
||||
/**
|
||||
*
|
||||
* @param baseMsat fixed fee
|
||||
* @param proportional proportional fee
|
||||
* @param msat amount in millisatoshi
|
||||
* @return the fee (in msat) that a node should be paid to forward an HTLC of 'amount' millisatoshis
|
||||
*/
|
||||
def nodeFee(baseMsat: Long, proportional: Long, msat: Long): Long = baseMsat + (proportional * msat) / 1000000
|
||||
|
||||
// TODO: use actual fee parameters that are specific to each node
|
||||
def fee(amountMsat: Int) = nodeFee(Globals.base_fee, Globals.proportional_fee, amountMsat).toInt
|
||||
def buildOnion(nodes: Seq[BinaryData], payloads: Seq[PerHopPayload], associatedData: BinaryData): BinaryData = {
|
||||
require(nodes.size == payloads.size + 1, s"count mismatch: there should be one less payload than nodes (nodes=${nodes.size} payloads=${payloads.size})")
|
||||
|
||||
var amountMsat = finalAmountMsat
|
||||
val steps = nodeIds.reverse.map(nodeId => {
|
||||
val step = route_step(amountMsat, next = Next.Bitcoin(nodeId))
|
||||
amountMsat = amountMsat + fee(amountMsat)
|
||||
step
|
||||
})
|
||||
lightning.route(steps.reverse :+ route_step(0, next = route_step.Next.End(true)))
|
||||
val pubkeys = nodes.map(PublicKey(_))
|
||||
|
||||
val sessionKey = randomKey
|
||||
|
||||
val payloadsbin: Seq[BinaryData] = payloads
|
||||
.map(Codecs.perHopPayloadCodec.encode(_))
|
||||
.map {
|
||||
case Attempt.Successful(bitVector) => BinaryData(bitVector.toByteArray)
|
||||
case Attempt.Failure(cause) => throw new RuntimeException(s"serialization error: $cause")
|
||||
} :+ BinaryData("00" * 20)
|
||||
|
||||
Sphinx.makePacket(sessionKey, pubkeys, payloadsbin, associatedData)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param finalAmountMsat the final htlc amount in millisatoshis
|
||||
* @param hops the hops as computed by the router
|
||||
* @return a (firstAmountMsat, firstExpiry, payloads) tuple where:
|
||||
* - firstAmountMsat is the amount for the first htlc in the route
|
||||
* - firstExpiry is the cltv expiry for the first htlc in the route
|
||||
* - a sequence of payloads that will be used to build the onion
|
||||
*/
|
||||
def buildRoute(finalAmountMsat: Long, hops: Seq[Hop], currentBlockCount: Int): (Long, Int, Seq[PerHopPayload]) =
|
||||
hops.reverse.foldLeft((finalAmountMsat, currentBlockCount + defaultHtlcExpiry, Seq.empty[PerHopPayload])) {
|
||||
case ((msat, expiry, payloads), hop) =>
|
||||
val feeMsat = nodeFee(hop.lastUpdate.feeBaseMsat, hop.lastUpdate.feeProportionalMillionths, msat)
|
||||
val expiryDelta = hop.lastUpdate.cltvExpiryDelta
|
||||
(msat + feeMsat, expiry + expiryDelta, PerHopPayload(msat, expiry) +: payloads)
|
||||
}
|
||||
|
||||
// TODO: set correct initial expiry
|
||||
val defaultHtlcExpiry = 10
|
||||
|
||||
def buildCommand(finalAmountMsat: Long, paymentHash: BinaryData, hops: Seq[Hop], currentBlockCount: Int): CMD_ADD_HTLC = {
|
||||
val (firstAmountMsat, firstExpiry, payloads) = buildRoute(finalAmountMsat, hops.drop(1), currentBlockCount)
|
||||
val nodes = hops.map(_.nextNodeId)
|
||||
// BOLT 2 requires that associatedData == paymentHash
|
||||
val onion = buildOnion(nodes, payloads, paymentHash)
|
||||
CMD_ADD_HTLC(firstAmountMsat, paymentHash, firstExpiry, onion, commit = true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
116
eclair-node/src/main/scala/fr/acinq/eclair/payment/Relayer.scala
Normal file
116
eclair-node/src/main/scala/fr/acinq/eclair/payment/Relayer.scala
Normal file
|
@ -0,0 +1,116 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.wire._
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, DecodeResult}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
// @formatter:off
|
||||
|
||||
case class OutgoingChannel(channelId: Long, channel: ActorRef, nodeAddress: BinaryData)
|
||||
case class Binding(downstream: UpdateAddHtlc, upstream: UpdateAddHtlc)
|
||||
|
||||
// @formatter:on
|
||||
|
||||
|
||||
/**
|
||||
* Created by PM on 01/02/2017.
|
||||
*/
|
||||
class Relayer(nodeSecret: PrivateKey, paymentHandler: ActorRef) extends Actor with ActorLogging {
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
|
||||
|
||||
override def receive: Receive = main(Set(), Map())
|
||||
|
||||
def main(upstreams: Set[OutgoingChannel], bindings: Map[UpdateAddHtlc, UpdateAddHtlc]): Receive = {
|
||||
|
||||
case ChannelChangedState(channel, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
|
||||
import d.commitments.channelId
|
||||
log.info(s"adding channel $channelId to available upstreams")
|
||||
context become main(upstreams + OutgoingChannel(channelId, channel, remoteNodeId.hash160), bindings)
|
||||
|
||||
case ChannelChangedState(channel, _, remoteNodeId, _, NEGOTIATING, d: DATA_NEGOTIATING) =>
|
||||
import d.commitments.channelId
|
||||
log.info(s"removing channel $channelId from upstreams/downstreams (mutual close)")
|
||||
// TODO: cleanup bindings
|
||||
context become main(upstreams - OutgoingChannel(channelId, channel, remoteNodeId.hash160), bindings)
|
||||
|
||||
case ChannelChangedState(channel, _, remoteNodeId, _, CLOSING, d: DATA_CLOSING) =>
|
||||
import d.commitments.channelId
|
||||
log.info(s"removing channel $channelId from upstreams/downstreams (unilateral close)")
|
||||
// TODO: cleanup bindings
|
||||
context become main(upstreams - OutgoingChannel(channelId, channel, remoteNodeId.hash160), bindings)
|
||||
|
||||
case add: UpdateAddHtlc =>
|
||||
Try(Sphinx.parsePacket(nodeSecret, add.paymentHash, add.onionRoutingPacket))
|
||||
.map {
|
||||
case (payload, nextNodeAddress, nextPacket) => (Codecs.perHopPayloadCodec.decode(BitVector(payload.data)), nextNodeAddress, nextPacket)
|
||||
} match {
|
||||
case Success((_, nextNodeAddress, _)) if nextNodeAddress.forall(_ == 0) =>
|
||||
log.info(s"we are the final recipient of htlc #${add.id}")
|
||||
context.system.eventStream.publish(PaymentReceived(self, add.paymentHash))
|
||||
paymentHandler forward add
|
||||
case Success((Attempt.Successful(DecodeResult(payload, _)), nextNodeAddress, nextPacket)) if upstreams.exists(_.nodeAddress == nextNodeAddress) =>
|
||||
val upstream = upstreams.find(_.nodeAddress == nextNodeAddress).get.channel
|
||||
log.info(s"forwarding htlc #${add.id} to upstream=$upstream")
|
||||
upstream ! CMD_ADD_HTLC(payload.amt_to_forward, add.paymentHash, payload.outgoing_cltv_value, nextPacket, origin = Some(add), commit = true)
|
||||
context become main(upstreams, bindings)
|
||||
case Success((Attempt.Successful(DecodeResult(_, _)), nextNodeAddress, _)) =>
|
||||
log.warning(s"couldn't resolve upstream node address $nextNodeAddress, failing htlc #${add.id}")
|
||||
sender ! CMD_FAIL_HTLC(add.id, "route error", commit = true)
|
||||
case Success((Attempt.Failure(cause), _, _)) =>
|
||||
log.error(s"couldn't parse payload: $cause")
|
||||
sender ! CMD_FAIL_HTLC(add.id, "payload parsing error", commit = true)
|
||||
case Failure(t) =>
|
||||
log.error(t, "couldn't parse onion: ")
|
||||
sender ! CMD_FAIL_HTLC(add.id, "onion parsing error", commit = true)
|
||||
}
|
||||
|
||||
case Binding(downstream: UpdateAddHtlc, upstream: UpdateAddHtlc) =>
|
||||
log.info(s"relayed htlc ${downstream.channelId}/${downstream.id} to ${upstream.channelId}/${upstream.id}")
|
||||
context become main(upstreams, bindings + (upstream -> downstream))
|
||||
|
||||
case (add: UpdateAddHtlc, fulfill: UpdateFulfillHtlc) =>
|
||||
bindings.get(add) match {
|
||||
case Some(origin) if upstreams.exists(_.channelId == origin.channelId) =>
|
||||
val downstream = upstreams.find(_.channelId == origin.channelId).get.channel
|
||||
downstream ! CMD_SIGN
|
||||
downstream ! CMD_FULFILL_HTLC(origin.id, fulfill.paymentPreimage)
|
||||
downstream ! CMD_SIGN
|
||||
case Some(origin) =>
|
||||
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
|
||||
case None =>
|
||||
log.info(s"we were the origin payer for htlc #${fulfill.id}")
|
||||
context.system.eventStream.publish(PaymentSent(self, add.paymentHash))
|
||||
}
|
||||
|
||||
case (add: UpdateAddHtlc, fail: UpdateFailHtlc) =>
|
||||
bindings.get(add) match {
|
||||
case Some(origin) if upstreams.exists(_.channelId == origin.channelId) =>
|
||||
val downstream = upstreams.find(_.channelId == origin.channelId).get.channel
|
||||
downstream ! CMD_SIGN
|
||||
// TODO: fix new String(fail.reason)
|
||||
downstream ! CMD_FAIL_HTLC(origin.id, new String(fail.reason))
|
||||
downstream ! CMD_SIGN
|
||||
case Some(origin) =>
|
||||
log.warning(s"origin channel ${origin.channelId} has disappeared in the meantime")
|
||||
case None =>
|
||||
log.info(s"we were the origin payer for htlc #${fail.id}")
|
||||
// TODO: fix new String(fail.reason)
|
||||
context.system.eventStream.publish(PaymentFailed(self, add.paymentHash, new String(fail.reason)))
|
||||
}
|
||||
|
||||
case 'upstreams => sender ! upstreams
|
||||
}
|
||||
}
|
||||
|
||||
object Relayer {
|
||||
def props(nodeSecret: PrivateKey, paymentHandler: ActorRef) = Props(classOf[Relayer], nodeSecret: PrivateKey, paymentHandler)
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.eclair.channel.{ChannelChangedState, ChannelSignatureReceived, DATA_NORMAL, NORMAL}
|
||||
|
||||
|
||||
/**
|
||||
* Created by PM on 26/08/2016.
|
||||
*/
|
||||
class ChannelSelector extends Actor with ActorLogging {
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
|
||||
context.system.eventStream.subscribe(self, classOf[ChannelSignatureReceived])
|
||||
|
||||
override def receive: Receive = main(Map(), Map())
|
||||
|
||||
def main(node2channels: Map[BinaryData, Set[ActorRef]], channel2balance: Map[ActorRef, Long]): Receive = {
|
||||
|
||||
case ChannelChangedState(channel, theirNodeId, _, NORMAL, d: DATA_NORMAL) =>
|
||||
val bal = d.commitments.remoteCommit.spec.toRemoteMsat
|
||||
log.info(s"new channel to $theirNodeId with availableMsat=$bal")
|
||||
val channels = node2channels.get(theirNodeId).getOrElse(Set()) + channel
|
||||
context become main(node2channels + (theirNodeId -> channels), channel2balance + (channel -> bal))
|
||||
|
||||
case ChannelSignatureReceived(channel, commitments) =>
|
||||
val bal = commitments.remoteCommit.spec.toRemoteMsat
|
||||
log.info(s"channel $channel now has availableMsat=$bal")
|
||||
context become main(node2channels, channel2balance + (channel -> bal))
|
||||
|
||||
case SelectChannelRequest(targetNodeId) if node2channels.contains(targetNodeId) =>
|
||||
// getting a set of all channels pointing to targetNodeId
|
||||
val candidates = node2channels(targetNodeId)
|
||||
// selecting the channel with the highest available balance
|
||||
val selected = candidates.map(c => (c, channel2balance(c))).toList.sortBy(_._2).last._1
|
||||
sender ! SelectChannelResponse(Some(selected))
|
||||
|
||||
case SelectChannelRequest(targetNodeId) =>
|
||||
sender ! SelectChannelResponse(None)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case class SelectChannelRequest(targetNodeId: BinaryData)
|
||||
|
||||
case class SelectChannelResponse(channel_opt: Option[ActorRef])
|
|
@ -1,111 +0,0 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef}
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.channel.{ChannelChangedState, DATA_NORMAL, NORMAL}
|
||||
import grizzled.slf4j.Logging
|
||||
import org.kitteh.irc.client.library.Client
|
||||
import org.kitteh.irc.client.library.event.channel.ChannelUsersUpdatedEvent
|
||||
import org.kitteh.irc.client.library.event.client.ClientConnectedEvent
|
||||
import org.kitteh.irc.client.library.event.helper.ChannelUserListChangeEvent
|
||||
import org.kitteh.irc.client.library.event.helper.ChannelUserListChangeEvent.Change
|
||||
import org.kitteh.irc.client.library.event.user.PrivateMessageEvent
|
||||
import org.kitteh.irc.lib.net.engio.mbassy.listener.Handler
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.util.Random
|
||||
|
||||
/**
|
||||
* Created by PM on 25/08/2016.
|
||||
*/
|
||||
class IRCWatcher extends Actor with ActorLogging {
|
||||
|
||||
val ircChannel = "#eclair-gossip"
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
|
||||
|
||||
val client = Client.builder().nick(s"node-${Globals.Node.id.take(8)}").serverHost("irc.freenode.net").build()
|
||||
client.getEventManager().registerEventListener(new NodeIRCListener())
|
||||
client.addChannel(ircChannel)
|
||||
|
||||
// we can't use channel id as nickname because there would be conflicts (a channel has two ends)
|
||||
val rand = new Random()
|
||||
|
||||
override def receive: Receive = main(Map(), Map())
|
||||
|
||||
def main(channels: Map[String, ChannelDesc], localChannels: Map[ActorRef, Client]): Receive = {
|
||||
case ChannelChangedState(channel, theirNodeId, _, NORMAL, d: DATA_NORMAL) =>
|
||||
val channelDesc = ChannelDesc(d.commitments.anchorId, Globals.Node.publicKey, theirNodeId)
|
||||
val channelClient = Client.builder().nick(f"chan-${rand.nextInt(1000000)}%06d").realName(channelDesc.id.toString()).serverHost("irc.freenode.net").build()
|
||||
channelClient.getEventManager().registerEventListener(new ChannelIRCListener(channelDesc))
|
||||
channelClient.addChannel(ircChannel)
|
||||
context become main(channels, localChannels + (channel -> channelClient))
|
||||
|
||||
case ChannelChangedState(channel, theirNodeId, NORMAL, _, _) if localChannels.contains(channel) =>
|
||||
localChannels(channel).shutdown()
|
||||
context become main(channels, localChannels - channel)
|
||||
|
||||
case ('add, nick: String, desc: ChannelDesc) if !channels.contains(nick) && !channels.values.map(_.id).contains(desc.id) =>
|
||||
context.system.eventStream.publish(ChannelDiscovered(desc))
|
||||
context become main(channels + (nick -> desc), localChannels)
|
||||
|
||||
case ('remove, nick: String) if channels.contains(nick) =>
|
||||
context.system.eventStream.publish(ChannelLost(channels(nick)))
|
||||
context become main(channels - nick, localChannels)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NodeIRCListener(implicit context: ActorContext) extends Logging {
|
||||
@Handler
|
||||
def onClientConnected(event: ClientConnectedEvent) {
|
||||
logger.info(s"connected to IRC: ${event.getServerInfo}")
|
||||
}
|
||||
|
||||
@Handler
|
||||
def onChannelUsersUpdated(event: ChannelUsersUpdatedEvent) {
|
||||
logger.debug(s"users updated: $event")
|
||||
event.getChannel.getUsers
|
||||
.filter(_.getNick.startsWith("chan"))
|
||||
.map(chanUser => chanUser.sendMessage("desc"))
|
||||
}
|
||||
|
||||
@Handler
|
||||
def onChannelUserListChangeEvent(event: ChannelUserListChangeEvent) {
|
||||
logger.debug(s"${event.getChange} ${event.getUser}")
|
||||
event.getChange match {
|
||||
case Change.JOIN if event.getUser.getNick.startsWith("chan") => event.getUser.sendMessage("desc")
|
||||
case Change.LEAVE if event.getUser.getNick.startsWith("chan") => context.self ! ('remove, event.getUser.getNick)
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
|
||||
val r = """([0-9a-f]{64}): ([0-9a-f]{66})-([0-9a-f]{66})""".r
|
||||
|
||||
@Handler
|
||||
def onPrivateMessage(event: PrivateMessageEvent) {
|
||||
logger.debug(s"got private message: ${event.getMessage}")
|
||||
event.getMessage match {
|
||||
case r(id, a, b) => context.self ! ('add, event.getActor.getNick, ChannelDesc(id, a, b))
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelIRCListener(channelDesc: ChannelDesc) extends Logging {
|
||||
@Handler
|
||||
def onClientConnected(event: ClientConnectedEvent) {
|
||||
logger.info(s"channel=${channelDesc.id} connected to IRC: ${event.getServerInfo}")
|
||||
}
|
||||
|
||||
@Handler
|
||||
def onPrivateMessage(event: PrivateMessageEvent) {
|
||||
logger.debug(s"got private message: ${event.getMessage}")
|
||||
event.getMessage match {
|
||||
case "desc" => event.sendReply(s"${channelDesc.id}: ${channelDesc.a}-${channelDesc.b}")
|
||||
case "kill" => event.getClient.shutdown()
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
/**
|
||||
* Created by PM on 26/08/2016.
|
||||
*/
|
||||
trait NetworkEvent
|
||||
|
||||
case class ChannelDiscovered(c: ChannelDesc) extends NetworkEvent
|
||||
|
||||
case class ChannelLost(c: ChannelDesc) extends NetworkEvent
|
||||
|
|
@ -1,69 +1,237 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.{Actor, ActorLogging}
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
|
||||
import akka.pattern.pipe
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import org.jgrapht.alg.DijkstraShortestPath
|
||||
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin.{BinaryData, LexicographicalOrdering}
|
||||
import fr.acinq.eclair.Globals
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.wire._
|
||||
import org.jgrapht.alg.shortestpath.DijkstraShortestPath
|
||||
import org.jgrapht.graph.{DefaultDirectedGraph, DefaultEdge}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.compat.Platform
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
// @formatter:off
|
||||
|
||||
case class ChannelDesc(id: Long, a: BinaryData, b: BinaryData)
|
||||
case class Hop(nodeId: BinaryData, nextNodeId: BinaryData, lastUpdate: ChannelUpdate)
|
||||
case class RouteRequest(source: BinaryData, target: BinaryData)
|
||||
case class RouteResponse(hops: Seq[Hop]) { require(hops.size > 0, "route cannot be empty") }
|
||||
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
* Created by PM on 24/05/2016.
|
||||
*/
|
||||
|
||||
class Router extends Actor with ActorLogging {
|
||||
|
||||
context.system.eventStream.subscribe(self, classOf[NetworkEvent])
|
||||
class Router(watcher: ActorRef, announcement: NodeAnnouncement) extends Actor with ActorLogging {
|
||||
|
||||
import Router._
|
||||
|
||||
import ExecutionContext.Implicits.global
|
||||
|
||||
def receive: Receive = main(Map())
|
||||
context.system.eventStream.subscribe(self, classOf[ChannelChangedState])
|
||||
context.system.scheduler.schedule(10 seconds, 60 seconds, self, 'tick_broadcast)
|
||||
|
||||
def main(channels: Map[BinaryData, ChannelDesc]): Receive = {
|
||||
case ChannelDiscovered(c) =>
|
||||
log.info(s"added channel ${c.id} to available routes")
|
||||
context become main(channels + (c.id -> c))
|
||||
case ChannelLost(c) =>
|
||||
log.info(s"removed channel ${c.id} from available routes")
|
||||
context become main(channels - c.id)
|
||||
case 'network => sender ! channels.values
|
||||
case RouteRequest(start, end) => findRoute(start, end, channels) map (RouteResponse(_)) pipeTo sender
|
||||
def receive: Receive = main(local = announcement, nodes = Map(announcement.nodeId -> announcement), channels = Map(), updates = Map(), rebroadcast = Nil)
|
||||
|
||||
def main(
|
||||
local: NodeAnnouncement,
|
||||
nodes: Map[BinaryData, NodeAnnouncement],
|
||||
channels: Map[Long, ChannelAnnouncement],
|
||||
updates: Map[ChannelDesc, ChannelUpdate],
|
||||
rebroadcast: Seq[RoutingMessage]): Receive = {
|
||||
|
||||
case ChannelChangedState(_, transport, _, WAIT_FOR_INIT_INTERNAL, _, _) =>
|
||||
// we send all known announcements to the new peer as soon as the connection is opened
|
||||
channels.values.foreach(transport ! _)
|
||||
nodes.values.foreach(transport ! _)
|
||||
updates.values.foreach(transport ! _)
|
||||
|
||||
case ChannelChangedState(_, _, remoteNodeId, _, NORMAL, d: DATA_NORMAL) =>
|
||||
val (c, u) = if (LexicographicalOrdering.isLessThan(local.nodeId, remoteNodeId.toBin)) {
|
||||
(
|
||||
makeChannelAnnouncement(d.commitments.channelId, local.nodeId, remoteNodeId, d.params.localParams.fundingPrivKey.publicKey.toBin, d.params.remoteParams.fundingPubKey.toBin),
|
||||
makeChannelUpdate(Globals.Node.privateKey, d.commitments.channelId, true, Platform.currentTime / 1000)
|
||||
)
|
||||
} else {
|
||||
(
|
||||
makeChannelAnnouncement(d.commitments.channelId, remoteNodeId, local.nodeId, d.params.remoteParams.fundingPubKey.toBin, d.params.localParams.fundingPrivKey.publicKey.toBin),
|
||||
makeChannelUpdate(Globals.Node.privateKey, d.commitments.channelId, false, Platform.currentTime / 1000)
|
||||
)
|
||||
}
|
||||
log.info(s"added channel channelId=${c.channelId} (nodes=${nodes.size} channels=${channels.size + 1})")
|
||||
// let's trigger the broadcast immediately so that we don't wait for 60 seconds to announce our newly created channel
|
||||
self ! 'tick_broadcast
|
||||
context become main(local, nodes, channels + (c.channelId -> c), updates, rebroadcast :+ c :+ local :+ u)
|
||||
|
||||
case s: ChannelChangedState =>
|
||||
// other channel changed state messages are ignored
|
||||
|
||||
case c: ChannelAnnouncement if channels.containsKey(c.channelId) =>
|
||||
log.debug(s"ignoring $c (duplicate)")
|
||||
|
||||
case c: ChannelAnnouncement =>
|
||||
// TODO: check channel output = P2WSH(nodeid1, nodeid2)
|
||||
// TODO: check sigs
|
||||
// TODO: blacklist if already received same channel id and different node ids
|
||||
// TODO: check feature bit set
|
||||
// TODO: forget channel once funding tx spent (add watch)
|
||||
//watcher ! WatchSpent(self, txId: BinaryData, outputIndex: Int, minDepth: Int, event: BitcoinEvent)
|
||||
log.info(s"added channel channelId=${c.channelId} (nodes=${nodes.size} channels=${channels.size + 1})")
|
||||
context become main(local, nodes, channels + (c.channelId -> c), updates, rebroadcast :+ c)
|
||||
|
||||
case n: NodeAnnouncement if !checkSig(n) =>
|
||||
// TODO: fail connection (should probably be done in the auth handler or channel)
|
||||
|
||||
case n: NodeAnnouncement if !channels.values.exists(c => c.nodeId1 == n.nodeId || c.nodeId2 == n.nodeId) =>
|
||||
log.debug(s"ignoring $n (no related channel found)")
|
||||
|
||||
case n: NodeAnnouncement if nodes.containsKey(n.nodeId) && nodes(n.nodeId).timestamp >= n.timestamp =>
|
||||
log.debug(s"ignoring announcement $n (old timestamp or duplicate)")
|
||||
|
||||
case n: NodeAnnouncement =>
|
||||
log.info(s"added/replaced node nodeId=${n.nodeId} (nodes=${nodes.size + 1} channels=${channels.size})")
|
||||
context become main(local, nodes + (n.nodeId -> n), channels, updates, rebroadcast :+ n)
|
||||
|
||||
case u: ChannelUpdate if !channels.contains(u.channelId) =>
|
||||
log.debug(s"ignoring $u (no related channel found)")
|
||||
|
||||
case u: ChannelUpdate if !checkSig(u, getDesc(u, channels(u.channelId)).a) =>
|
||||
// TODO: fail connection (should probably be done in the auth handler or channel)
|
||||
|
||||
case u: ChannelUpdate =>
|
||||
val channel = channels(u.channelId)
|
||||
val desc = getDesc(u, channel)
|
||||
if (updates.contains(desc) && updates(desc).timestamp >= u.timestamp) {
|
||||
log.debug(s"ignoring $u (old timestamp or duplicate)")
|
||||
} else {
|
||||
context become main(local, nodes, channels, updates + (desc -> u), rebroadcast :+ u)
|
||||
}
|
||||
|
||||
case 'tick_broadcast if rebroadcast.size == 0 =>
|
||||
// no-op
|
||||
|
||||
case 'tick_broadcast =>
|
||||
log.info(s"broadcasting ${rebroadcast.size} routing messages")
|
||||
rebroadcast.foreach(context.actorSelection(Register.actorPathToTransportHandlers) ! _)
|
||||
context become main(local, nodes, channels, updates, Nil)
|
||||
|
||||
case 'nodes => sender ! nodes.values
|
||||
|
||||
case 'channels => sender ! channels.values
|
||||
|
||||
case 'updates => sender ! updates.values
|
||||
|
||||
case RouteRequest(start, end) => findRoute(start, end, updates).map(RouteResponse(_)) pipeTo sender
|
||||
|
||||
case other => log.warning(s"unhandled message $other")
|
||||
}
|
||||
}
|
||||
|
||||
object Router {
|
||||
|
||||
def findRouteDijkstra(myNodeId: BinaryData, targetNodeId: BinaryData, channels: Map[BinaryData, ChannelDesc]): Seq[BinaryData] = {
|
||||
class NamedEdge(val id: BinaryData) extends DefaultEdge
|
||||
val g = new SimpleGraph[BinaryData, NamedEdge](classOf[NamedEdge])
|
||||
channels.values.foreach(x => {
|
||||
g.addVertex(x.a)
|
||||
g.addVertex(x.b)
|
||||
g.addEdge(x.a, x.b, new NamedEdge(x.id))
|
||||
def props(watcher: ActorRef, announcement: NodeAnnouncement) = Props(classOf[Router], watcher, announcement)
|
||||
|
||||
// TODO: placeholder for signatures, we don't actually sign for now
|
||||
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
|
||||
|
||||
def makeChannelAnnouncement(channelId: Long, nodeId1: BinaryData, nodeId2: BinaryData, fundingKey1: BinaryData, fundingKey2: BinaryData): ChannelAnnouncement = {
|
||||
val unsigned = ChannelAnnouncement(
|
||||
nodeSignature1 = DUMMY_SIG,
|
||||
nodeSignature2 = DUMMY_SIG,
|
||||
channelId = channelId,
|
||||
bitcoinSignature1 = DUMMY_SIG,
|
||||
bitcoinSignature2 = DUMMY_SIG,
|
||||
nodeId1 = nodeId1,
|
||||
nodeId2 = nodeId2,
|
||||
bitcoinKey1 = fundingKey1,
|
||||
bitcoinKey2 = fundingKey2
|
||||
)
|
||||
unsigned
|
||||
}
|
||||
|
||||
def makeNodeAnnouncement(secret: PrivateKey, alias: String, color: (Byte, Byte, Byte), addresses: List[InetSocketAddress], timestamp: Long): NodeAnnouncement = {
|
||||
require(alias.size <= 32)
|
||||
val unsigned = NodeAnnouncement(
|
||||
signature = DUMMY_SIG,
|
||||
timestamp = timestamp,
|
||||
nodeId = secret.publicKey.toBin,
|
||||
rgbColor = color,
|
||||
alias = alias,
|
||||
features = "",
|
||||
addresses = addresses
|
||||
)
|
||||
unsigned
|
||||
/*val bin = Codecs.nodeAnnouncementCodec.encode(unsigned).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $unsigned"))
|
||||
val hash = sha256(sha256(bin.drop(64)))
|
||||
val sig = encodeSignature(sign(hash, secret))
|
||||
unsigned.copy(signature = sig)*/
|
||||
}
|
||||
|
||||
def makeChannelUpdate(secret: PrivateKey, channelId: Long, isNodeId1: Boolean, timestamp: Long): ChannelUpdate = {
|
||||
val unsigned = ChannelUpdate(
|
||||
signature = DUMMY_SIG,
|
||||
channelId = channelId,
|
||||
timestamp = timestamp,
|
||||
flags = if (isNodeId1) "0000" else "0001",
|
||||
cltvExpiryDelta = Globals.expiry_delta_blocks,
|
||||
htlcMinimumMsat = Globals.htlc_minimum_msat,
|
||||
feeBaseMsat = Globals.fee_base_msat,
|
||||
feeProportionalMillionths = Globals.fee_proportional_msat
|
||||
)
|
||||
unsigned
|
||||
/*val bin = Codecs.channelUpdateCodec.encode(unsigned).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $unsigned"))
|
||||
val hash = sha256(sha256(bin.drop(64)))
|
||||
val sig = encodeSignature(sign(hash, secret))
|
||||
unsigned.copy(signature = sig)*/
|
||||
}
|
||||
|
||||
def checkSig(ann: NodeAnnouncement): Boolean = true
|
||||
|
||||
/*{
|
||||
val bin = Codecs.nodeAnnouncementCodec.encode(ann).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $ann"))
|
||||
val hash = sha256(sha256(bin.drop(64)))
|
||||
verifySignature(hash, ann.signature, PublicKey(ann.nodeId))
|
||||
}*/
|
||||
|
||||
def checkSig(ann: ChannelUpdate, nodeId: BinaryData): Boolean = true
|
||||
|
||||
/*{
|
||||
val bin = Codecs.channelUpdateCodec.encode(ann).toOption.map(_.toByteArray).getOrElse(throw new RuntimeException(s"cannot encode $ann"))
|
||||
val hash = sha256(sha256(bin.drop(64)))
|
||||
verifySignature(hash, ann.signature, PublicKey(nodeId))
|
||||
}*/
|
||||
|
||||
def getDesc(u: ChannelUpdate, channel: ChannelAnnouncement): ChannelDesc = {
|
||||
require(u.flags.data.size == 2, s"invalid flags length ${u.flags.data.size} != 2")
|
||||
// the least significant bit tells us if it is node1 or node2
|
||||
if (u.flags.data(1) % 2 == 0) ChannelDesc(u.channelId, channel.nodeId1, channel.nodeId2) else ChannelDesc(u.channelId, channel.nodeId2, channel.nodeId1)
|
||||
}
|
||||
|
||||
def findRouteDijkstra(localNodeId: BinaryData, targetNodeId: BinaryData, channels: Iterable[ChannelDesc]): Seq[ChannelDesc] = {
|
||||
require(localNodeId != targetNodeId, "cannot route to self")
|
||||
case class DescEdge(desc: ChannelDesc) extends DefaultEdge
|
||||
val g = new DefaultDirectedGraph[BinaryData, DescEdge](classOf[DescEdge])
|
||||
channels.foreach(d => {
|
||||
g.addVertex(d.a)
|
||||
g.addVertex(d.b)
|
||||
g.addEdge(d.a, d.b, new DescEdge(d))
|
||||
})
|
||||
Option(new DijkstraShortestPath(g, myNodeId, targetNodeId).getPath) match {
|
||||
case Some(path) => {
|
||||
val vertices = path.getEdgeList.foldLeft(List(path.getStartVertex)) {
|
||||
case (rest :+ v, edge) if g.getEdgeSource(edge) == v => rest :+ v :+ g.getEdgeTarget(edge)
|
||||
case (rest :+ v, edge) if g.getEdgeTarget(edge) == v => rest :+ v :+ g.getEdgeSource(edge)
|
||||
}
|
||||
vertices
|
||||
}
|
||||
Option(DijkstraShortestPath.findPathBetween(g, localNodeId, targetNodeId)) match {
|
||||
case Some(path) => path.getEdgeList.map(_.desc)
|
||||
case None => throw new RuntimeException("route not found")
|
||||
}
|
||||
}
|
||||
|
||||
def findRoute(myNodeId: BinaryData, targetNodeId: BinaryData, channels: Map[BinaryData, ChannelDesc])(implicit ec: ExecutionContext): Future[Seq[BinaryData]] = Future {
|
||||
findRouteDijkstra(myNodeId, targetNodeId, channels)
|
||||
def findRoute(localNodeId: BinaryData, targetNodeId: BinaryData, updates: Map[ChannelDesc, ChannelUpdate])(implicit ec: ExecutionContext): Future[Seq[Hop]] = Future {
|
||||
findRouteDijkstra(localNodeId, targetNodeId, updates.keys)
|
||||
.map(desc => Hop(desc.a, desc.b, updates(desc)))
|
||||
}
|
||||
}
|
||||
|
||||
case class ChannelDesc(id: BinaryData, a: BinaryData, b: BinaryData)
|
||||
|
||||
case class RouteRequest(source: BinaryData, target: BinaryData)
|
||||
|
||||
case class RouteResponse(route: Seq[BinaryData])
|
|
@ -1,13 +1,13 @@
|
|||
package fr.acinq.eclair.wire
|
||||
|
||||
import java.math.BigInteger
|
||||
import java.net.{Inet4Address, Inet6Address, InetAddress}
|
||||
import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress}
|
||||
|
||||
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.eclair.crypto.Generators
|
||||
import fr.acinq.eclair.wire
|
||||
import scodec.bits.{BitVector, ByteVector, HexStringSyntax}
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
import scodec.codecs._
|
||||
import scodec.{Attempt, Codec, Err}
|
||||
|
||||
|
@ -25,17 +25,19 @@ object Codecs {
|
|||
|
||||
def varsizebinarydata: Codec[BinaryData] = variableSizeBytes(uint16, bytes.xmap(d => BinaryData(d.toSeq), d => ByteVector(d.data)))
|
||||
|
||||
def listofbinarydata(size: Int): Codec[List[BinaryData]] = listOfN(uint16, binarydata(size))
|
||||
|
||||
def listofsignatures: Codec[List[BinaryData]] = listOfN(uint16, signature)
|
||||
|
||||
def ipv6: Codec[InetAddress] = Codec[InetAddress](
|
||||
(ia: InetAddress) => ia match {
|
||||
case a: Inet4Address => bytes(16).encode(hex"00 00 00 00 00 00 00 00 00 00 FF FF" ++ ByteVector(a.getAddress))
|
||||
case a: Inet6Address => bytes(16).encode(ByteVector(a.getAddress))
|
||||
},
|
||||
(buf: BitVector) => bytes(16).decode(buf).map(_.map(b => InetAddress.getByAddress(b.toArray)))
|
||||
)
|
||||
def ipv4address: Codec[Inet4Address] = bytes(4).xmap(b => InetAddress.getByAddress(b.toArray).asInstanceOf[Inet4Address], a => ByteVector(a.getAddress))
|
||||
|
||||
def ipv6address: Codec[Inet6Address] = bytes(16).xmap(b => InetAddress.getByAddress(b.toArray).asInstanceOf[Inet6Address], a => ByteVector(a.getAddress))
|
||||
|
||||
def socketaddress: Codec[InetSocketAddress] =
|
||||
(discriminated[InetAddress].by(uint8)
|
||||
.typecase(1, ipv4address)
|
||||
.typecase(2, ipv6address) ~ uint16)
|
||||
.xmap(x => new InetSocketAddress(x._1, x._2), x => (x.getAddress, x.getPort))
|
||||
|
||||
def listofsocketaddresses: Codec[List[InetSocketAddress]] = listOfN(uint16, socketaddress)
|
||||
|
||||
def signature: Codec[BinaryData] = Codec[BinaryData](
|
||||
(der: BinaryData) => bytes(64).encode(ByteVector(der2wire(der).toArray)),
|
||||
|
@ -128,7 +130,7 @@ object Codecs {
|
|||
val fundingCreatedCodec: Codec[FundingCreated] = (
|
||||
("temporaryChannelId" | int64) ::
|
||||
("txid" | binarydata(32)) ::
|
||||
("outputIndex" | uint16) ::
|
||||
("outputIndex" | uint8) ::
|
||||
("signature" | signature)).as[FundingCreated]
|
||||
|
||||
val fundingSignedCodec: Codec[FundingSigned] = (
|
||||
|
@ -178,7 +180,7 @@ object Codecs {
|
|||
("channelId" | int64) ::
|
||||
("perCommitmentSecret" | scalar) ::
|
||||
("nextPerCommitmentPoint" | point) ::
|
||||
("padding" | ignore(3)) ::
|
||||
("padding" | ignore(8 * 3)) ::
|
||||
("htlcTimeoutSignature" | listofsignatures)
|
||||
).as[RevokeAndAck]
|
||||
|
||||
|
@ -200,18 +202,18 @@ object Codecs {
|
|||
val nodeAnnouncementCodec: Codec[NodeAnnouncement] = (
|
||||
("signature" | signature) ::
|
||||
("timestamp" | uint32) ::
|
||||
("ip" | ipv6) ::
|
||||
("port" | uint16) ::
|
||||
("nodeId" | binarydata(33)) ::
|
||||
("rgbColor" | rgb) ::
|
||||
("alias" | zeropaddedstring(32))).as[NodeAnnouncement]
|
||||
("alias" | zeropaddedstring(32)) ::
|
||||
("features" | varsizebinarydata) ::
|
||||
("addresses" | listofsocketaddresses)).as[NodeAnnouncement]
|
||||
|
||||
val channelUpdateCodec: Codec[ChannelUpdate] = (
|
||||
("signature" | signature) ::
|
||||
("channelId" | int64) ::
|
||||
("timestamp" | uint32) ::
|
||||
("flags" | binarydata(2)) ::
|
||||
("expiry" | uint16) ::
|
||||
("cltvExpiryDelta" | uint16) ::
|
||||
("htlcMinimumMsat" | uint32) ::
|
||||
("feeBaseMsat" | uint32) ::
|
||||
("feeProportionalMillionths" | uint32)).as[ChannelUpdate]
|
||||
|
@ -236,4 +238,10 @@ object Codecs {
|
|||
.typecase(257, nodeAnnouncementCodec)
|
||||
.typecase(258, channelUpdateCodec)
|
||||
|
||||
val perHopPayloadCodec: Codec[PerHopPayload] = (
|
||||
("realm" | ignore(8 * 1)) ::
|
||||
("amt_to_forward" | uint64) ::
|
||||
("outgoing_cltv_value" | int32) :: // we use a signed int32, it is enough to store cltv for 40 000 years
|
||||
("unused_with_v0_version_on_header" | ignore(8 * 7))).as[PerHopPayload]
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package fr.acinq.eclair.wire
|
||||
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar}
|
||||
|
@ -114,17 +114,21 @@ case class ChannelAnnouncement(nodeSignature1: BinaryData,
|
|||
|
||||
case class NodeAnnouncement(signature: BinaryData,
|
||||
timestamp: Long,
|
||||
ip: InetAddress,
|
||||
port: Int,
|
||||
nodeId: BinaryData,
|
||||
rgbColor: (Byte, Byte, Byte),
|
||||
alias: String) extends RoutingMessage
|
||||
alias: String,
|
||||
features: BinaryData,
|
||||
// TODO: check address order + support padding data (type 0)
|
||||
addresses: List[InetSocketAddress]) extends RoutingMessage
|
||||
|
||||
case class ChannelUpdate(signature: BinaryData,
|
||||
channelId: Long,
|
||||
timestamp: Long,
|
||||
flags: BinaryData,
|
||||
expiry: Int,
|
||||
cltvExpiryDelta: Int,
|
||||
htlcMinimumMsat: Long,
|
||||
feeBaseMsat: Long,
|
||||
feeProportionalMillionths: Long) extends RoutingMessage
|
||||
feeProportionalMillionths: Long) extends RoutingMessage
|
||||
|
||||
case class PerHopPayload(amt_to_forward: Long,
|
||||
outgoing_cltv_value: Int)
|
|
@ -1,31 +1,36 @@
|
|||
eclair {
|
||||
server {
|
||||
address = "localhost"
|
||||
port = 45000
|
||||
host = "localhost"
|
||||
port = 9735
|
||||
}
|
||||
api {
|
||||
address = "localhost"
|
||||
host = "localhost"
|
||||
port = 8080
|
||||
}
|
||||
bitcoind {
|
||||
address = "localhost"
|
||||
port = 18444 # regtest
|
||||
network = "regtest"
|
||||
host = "localhost"
|
||||
port = 18444
|
||||
rpcport = 18332
|
||||
rpcuser = "foo"
|
||||
rpcpassword = "bar"
|
||||
}
|
||||
node {
|
||||
seed = 0102030405060708010203040506070801020304050607080102030405060708
|
||||
alias = "eclair"
|
||||
color {
|
||||
r = 73
|
||||
g = 218
|
||||
b = 170
|
||||
}
|
||||
}
|
||||
delay-blocks = 144
|
||||
mindepth-blocks = 3
|
||||
commit-fee = 80000
|
||||
closing-fee = 10000
|
||||
payment-handler = "noop"
|
||||
}
|
||||
interop-test {
|
||||
bitcoin-path = ""
|
||||
lightning-path = ""
|
||||
expiry-delta-blocks = 144
|
||||
htlc-minimum-msat = 1000000
|
||||
fee-base-msat = 546000
|
||||
fee-proportional-msat = 10
|
||||
payment-handler = "local"
|
||||
}
|
||||
akka {
|
||||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||
|
|
20
eclair-node/src/test/scala/fr/acinq/eclair/PackageSpec.scala
Normal file
20
eclair-node/src/test/scala/fr/acinq/eclair/PackageSpec.scala
Normal file
|
@ -0,0 +1,20 @@
|
|||
package fr.acinq.eclair
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
/**
|
||||
* Created by PM on 27/01/2017.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class PackageSpec extends FunSuite {
|
||||
|
||||
test("calculate simple route") {
|
||||
val blockHeight = 42000
|
||||
val txIndex = 27
|
||||
val outputIndex = 3
|
||||
assert(fromShortId(toShortId(blockHeight, txIndex, outputIndex)) === (blockHeight, txIndex, outputIndex))
|
||||
}
|
||||
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package fr.acinq.eclair
|
||||
|
||||
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
|
||||
import akka.actor.{ActorSystem, Props, Status}
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router._
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||
|
||||
/**
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BeforeAndAfterAll {
|
||||
|
||||
override def afterAll {
|
||||
TestKit.shutdownActorSystem(system)
|
||||
}
|
||||
|
||||
test("route not available") {
|
||||
val router = system.actorOf(Props[Router])
|
||||
val selector = system.actorOf(Props[ChannelSelector])
|
||||
val channel00 = TestProbe()
|
||||
val channel01 = TestProbe()
|
||||
|
||||
// network: aaaa -> bbbbbbb -> cccc
|
||||
val node_a = Globals.Node.publicKey
|
||||
val node_b = PrivateKey(BinaryData("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), compressed = true).publicKey
|
||||
val node_c = PrivateKey(BinaryData("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"), compressed = true).publicKey
|
||||
val node_d = PrivateKey(BinaryData("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"), compressed = true).publicKey
|
||||
|
||||
// no route b -> c
|
||||
router ! ChannelDiscovered(ChannelDesc("01", node_a, node_b))
|
||||
router ! ChannelDiscovered(ChannelDesc("02", node_c, node_d))
|
||||
|
||||
val paymentFsm = system.actorOf(PaymentLifecycle.props(router, selector, 1440))
|
||||
|
||||
val monitor = TestProbe()
|
||||
paymentFsm ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val sender = TestProbe()
|
||||
sender.send(paymentFsm, CreatePayment(42000000, BinaryData("00112233445566778899aabbccddeeff"), node_c))
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
sender.expectMsgType[Status.Failure]
|
||||
}
|
||||
|
||||
//TODO re-enable
|
||||
/*test("payment succeeded") {
|
||||
val router = system.actorOf(Props[Router])
|
||||
val selector = system.actorOf(Props[ChannelSelector])
|
||||
val channel00 = TestProbe()
|
||||
val channel01 = TestProbe()
|
||||
|
||||
// network: aaaa -> bbbbbbb -> cccc
|
||||
val node_a = Globals.Node.publicKey
|
||||
val node_b = BinaryData("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
val node_c = BinaryData("ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
|
||||
|
||||
router ! ChannelDiscovered(ChannelDesc("01", node_a, node_b))
|
||||
router ! ChannelDiscovered(ChannelDesc("02", node_b, node_c))
|
||||
|
||||
selector ! ChannelChangedState(channel00.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURFUNDING, NORMAL, DATA_NORMAL_2(0, Commitments(null, null, null, TheirCommit(0L, CommitmentSpec(Set(), 0L, 0L, 100000), null, null), null, null, 0L, null, null, null, null, null), null, null))
|
||||
selector ! ChannelChangedState(channel01.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURFUNDING, NORMAL, DATA_NORMAL(Commitments(null, null, null, TheirCommit(0L, CommitmentSpec(Set(), 0L, 0L, 100000000), null, null), null, null, 0L, null, null, null, null, null), null, null))
|
||||
|
||||
val paymentFsm = system.actorOf(PaymentLifecycle.props(router, selector, 1440))
|
||||
|
||||
val monitor = TestProbe()
|
||||
paymentFsm ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val sender = TestProbe()
|
||||
val req = CreatePayment(42000000, BinaryData("00112233445566778899aabbccddeeff"), node_c)
|
||||
sender.send(paymentFsm, req)
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_CHANNEL) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_CHANNEL, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
channel01.expectMsgType[CMD_ADD_HTLC]
|
||||
sender.send(paymentFsm, PaymentSent(channel01.ref, req.h))
|
||||
sender.expectMsg("sent")
|
||||
|
||||
}*/
|
||||
|
||||
//TODO re-enable
|
||||
/*test("payment failed") {
|
||||
val router = system.actorOf(Props[Router])
|
||||
val selector = system.actorOf(Props[ChannelSelector])
|
||||
val channel00 = TestProbe()
|
||||
val channel01 = TestProbe()
|
||||
|
||||
// network: aaaa -> bbbbbbb -> cccc
|
||||
val node_a = Globals.Node.publicKey
|
||||
val node_b = BinaryData("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
val node_c = BinaryData("ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
|
||||
|
||||
router ! ChannelDiscovered(ChannelDesc("01", node_a, node_b))
|
||||
router ! ChannelDiscovered(ChannelDesc("02", node_b, node_c))
|
||||
|
||||
selector ! ChannelChangedState(channel00.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURFUNDING, NORMAL, DATA_NORMAL(Commitments(null, null, null, TheirCommit(0L, CommitmentSpec(Set(), 0L, 0L, 100000), null, null), null, null, 0L, null, null, null, null, null), null, null))
|
||||
selector ! ChannelChangedState(channel01.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURFUNDING, NORMAL, DATA_NORMAL(Commitments(null, null, null, TheirCommit(0L, CommitmentSpec(Set(), 0L, 0L, 100000000), null, null), null, null, 0L, null, null, null, null, null), null, null))
|
||||
|
||||
val paymentFsm = system.actorOf(PaymentLifecycle.props(router, selector, 1440))
|
||||
|
||||
val monitor = TestProbe()
|
||||
paymentFsm ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val sender = TestProbe()
|
||||
val req = CreatePayment(42000000, BinaryData("00112233445566778899aabbccddeeff"), node_c)
|
||||
sender.send(paymentFsm, req)
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_CHANNEL) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_CHANNEL, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
channel01.expectMsgType[CMD_ADD_HTLC]
|
||||
sender.send(paymentFsm, PaymentFailed(channel01.ref, req.h, "some reason"))
|
||||
sender.expectMsgType[Status.Failure]
|
||||
}*/
|
||||
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
package fr.acinq.eclair
|
||||
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.eclair.payment.PaymentLifecycle
|
||||
import fr.acinq.eclair.router.{ChannelDesc, Router}
|
||||
import lightning.route_step
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
/**
|
||||
* Created by PM on 31/05/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class RouterSpec extends FunSuite {
|
||||
|
||||
test("calculate simple route") {
|
||||
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("0a") -> ChannelDesc(BinaryData("0a"), BinaryData("01"), BinaryData("02")),
|
||||
BinaryData("0b") -> ChannelDesc(BinaryData("0b"), BinaryData("03"), BinaryData("02")),
|
||||
BinaryData("0c") -> ChannelDesc(BinaryData("0c"), BinaryData("03"), BinaryData("04")),
|
||||
BinaryData("0d") -> ChannelDesc(BinaryData("0d"), BinaryData("04"), BinaryData("05"))
|
||||
)
|
||||
|
||||
val route = Router.findRouteDijkstra(BinaryData("01"), BinaryData("05"), channels)
|
||||
|
||||
assert(route === BinaryData("01") :: BinaryData("02") :: BinaryData("03") :: BinaryData("04") :: BinaryData("05") :: Nil)
|
||||
|
||||
}
|
||||
|
||||
test("calculate simple route 2") {
|
||||
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("99e542c274b073d215af02e57f814f3d16a2373a00ac52b49ef4a1949c912609") -> ChannelDesc(BinaryData("99e542c274b073d215af02e57f814f3d16a2373a00ac52b49ef4a1949c912609"), BinaryData("032b2e37d202658eb5216a698e52da665c25c5d04de0faf1d29aa2af7fb374a003"), BinaryData("0382887856e9f10a8a1ffade96b4009769141e5f3692f2ffc35fd4221f6057643b")),
|
||||
BinaryData("44d7f822e0498e21473a8a40045c9b7e7bd2e78730b5274cb5836e64bc0b6125") -> ChannelDesc(BinaryData("44d7f822e0498e21473a8a40045c9b7e7bd2e78730b5274cb5836e64bc0b6125"), BinaryData("023cda4e9506ce0a5fd3e156fc6d1bff16873375c8e823ee18aa36fa6844c0ae61"), BinaryData("0382887856e9f10a8a1ffade96b4009769141e5f3692f2ffc35fd4221f6057643b"))
|
||||
)
|
||||
|
||||
val route = Router.findRouteDijkstra(BinaryData("032b2e37d202658eb5216a698e52da665c25c5d04de0faf1d29aa2af7fb374a003"), BinaryData("023cda4e9506ce0a5fd3e156fc6d1bff16873375c8e823ee18aa36fa6844c0ae61"), channels)
|
||||
|
||||
assert(route === BinaryData("032b2e37d202658eb5216a698e52da665c25c5d04de0faf1d29aa2af7fb374a003") :: BinaryData("0382887856e9f10a8a1ffade96b4009769141e5f3692f2ffc35fd4221f6057643b") :: BinaryData("023cda4e9506ce0a5fd3e156fc6d1bff16873375c8e823ee18aa36fa6844c0ae61") :: Nil)
|
||||
|
||||
}
|
||||
|
||||
test("calculate simple route 3") {
|
||||
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("178c78f5ec3ffa8cc15d9fa8119ec0d1ff7d4e4ff33297df5e68319fbc34b1bb") -> ChannelDesc(BinaryData("178c78f5ec3ffa8cc15d9fa8119ec0d1ff7d4e4ff33297df5e68319fbc34b1bb"), BinaryData("02f298bcafa3b0aa1d51a552d759a5add7d189222fe068d4ff2417dce43ea9daa1"), BinaryData("021acf75c92318d3723098294d2a6a4b08d9abba2ebb5f2df2b4a8e9153e96a5f4")),
|
||||
BinaryData("e9c89b691f80494e631a4eb7169c981a0be9484cdd51a2cb9975f8aba85a77c8") -> ChannelDesc(BinaryData("e9c89b691f80494e631a4eb7169c981a0be9484cdd51a2cb9975f8aba85a77c8"), BinaryData("031ef3016e2994eb40b75c051ba9089238dcb138fe4148169d6f3f8e114b446cc1"), BinaryData("03befb4f8ad1d87d4c41acbb316791fe157f305caf2123c848f448975aaf85c1bb")),
|
||||
BinaryData("2c2e2b5f1b70d9ab2ea30f5dd0f61d62edad10881d2add1a2796d94eac93e07e") -> ChannelDesc(BinaryData("2c2e2b5f1b70d9ab2ea30f5dd0f61d62edad10881d2add1a2796d94eac93e07e"), BinaryData("02f298bcafa3b0aa1d51a552d759a5add7d189222fe068d4ff2417dce43ea9daa1"), BinaryData("031ef3016e2994eb40b75c051ba9089238dcb138fe4148169d6f3f8e114b446cc1"))
|
||||
)
|
||||
|
||||
val route = Router.findRouteDijkstra(BinaryData("03befb4f8ad1d87d4c41acbb316791fe157f305caf2123c848f448975aaf85c1bb"), BinaryData("021acf75c92318d3723098294d2a6a4b08d9abba2ebb5f2df2b4a8e9153e96a5f4"), channels)
|
||||
|
||||
assert(route === BinaryData("03befb4f8ad1d87d4c41acbb316791fe157f305caf2123c848f448975aaf85c1bb") :: BinaryData("031ef3016e2994eb40b75c051ba9089238dcb138fe4148169d6f3f8e114b446cc1") :: BinaryData("02f298bcafa3b0aa1d51a552d759a5add7d189222fe068d4ff2417dce43ea9daa1") :: BinaryData("021acf75c92318d3723098294d2a6a4b08d9abba2ebb5f2df2b4a8e9153e96a5f4") :: Nil)
|
||||
}
|
||||
|
||||
test("route not found") {
|
||||
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("178c78f5ec3ffa8cc15d9fa8119ec0d1ff7d4e4ff33297df5e68319fbc34b1bb") -> ChannelDesc(BinaryData("178c78f5ec3ffa8cc15d9fa8119ec0d1ff7d4e4ff33297df5e68319fbc34b1bb"), BinaryData("02f298bcafa3b0aa1d51a552d759a5add7d189222fe068d4ff2417dce43ea9daa1"), BinaryData("021acf75c92318d3723098294d2a6a4b08d9abba2ebb5f2df2b4a8e9153e96a5f4")),
|
||||
BinaryData("e9c89b691f80494e631a4eb7169c981a0be9484cdd51a2cb9975f8aba85a77c8") -> ChannelDesc(BinaryData("e9c89b691f80494e631a4eb7169c981a0be9484cdd51a2cb9975f8aba85a77c8"), BinaryData("031ef3016e2994eb40b75c051ba9089238dcb138fe4148169d6f3f8e114b446cc1"), BinaryData("03befb4f8ad1d87d4c41acbb316791fe157f305caf2123c848f448975aaf85c1bb"))
|
||||
)
|
||||
|
||||
intercept[RuntimeException] {
|
||||
Router.findRouteDijkstra(BinaryData("03befb4f8ad1d87d4c41acbb316791fe157f305caf2123c848f448975aaf85c1bb"), BinaryData("021acf75c92318d3723098294d2a6a4b08d9abba2ebb5f2df2b4a8e9153e96a5f4"), channels)
|
||||
}
|
||||
}
|
||||
|
||||
test("route to self") {
|
||||
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("0a") -> ChannelDesc(BinaryData("0a"), BinaryData("01"), BinaryData("02")),
|
||||
BinaryData("0b") -> ChannelDesc(BinaryData("0b"), BinaryData("01"), BinaryData("01"))
|
||||
)
|
||||
|
||||
intercept[RuntimeException] {
|
||||
Router.findRouteDijkstra(BinaryData("01"), BinaryData("01"), channels)
|
||||
}
|
||||
}
|
||||
|
||||
test("compute fees 2") {
|
||||
val nodeIds = Seq(BinaryData("00"), BinaryData("01"), BinaryData("02"))
|
||||
val amountMsat = 1000000
|
||||
val route = PaymentLifecycle.buildRoute(amountMsat, nodeIds)
|
||||
assert(route.steps.length == 4 && route.steps.last == route_step(0, next = route_step.Next.End(true)))
|
||||
assert(route.steps(2).amount == amountMsat)
|
||||
assert(route.steps.dropRight(1).map(_.next.bitcoin.get.key).map(bytestring2bin) == nodeIds)
|
||||
assert(route.steps(0).amount - route.steps(1).amount == nodeFee(Globals.base_fee, Globals.proportional_fee, route.steps(1).amount))
|
||||
}
|
||||
|
||||
test("route to neighbor") {
|
||||
val channels: Map[BinaryData, ChannelDesc] = Map(
|
||||
BinaryData("0a") -> ChannelDesc(BinaryData("0a"), BinaryData("01"), BinaryData("02"))
|
||||
)
|
||||
Router.findRouteDijkstra(BinaryData("01"), BinaryData("02"), channels)
|
||||
}
|
||||
|
||||
test("compute fees") {
|
||||
val nodeIds = Seq(BinaryData("00"), BinaryData("01"))
|
||||
val amountMsat = 300000000
|
||||
val route = PaymentLifecycle.buildRoute(amountMsat, nodeIds)
|
||||
assert(route.steps.length == 3 && route.steps.last == route_step(0, next = route_step.Next.End(true)))
|
||||
assert(route.steps(1).amount == amountMsat)
|
||||
assert(route.steps.dropRight(1).map(_.next.bitcoin.get.key).map(bytestring2bin) == nodeIds)
|
||||
assert(route.steps(0).amount - route.steps(1).amount == nodeFee(Globals.base_fee, Globals.proportional_fee, route.steps(1).amount))
|
||||
}
|
||||
|
||||
}
|
|
@ -44,4 +44,6 @@ class TestBitcoinClient()(implicit system: ActorSystem) extends ExtendedBitcoinC
|
|||
|
||||
override def signTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[SignTransactionResponse] = ???
|
||||
|
||||
override def getTransactionShortId(txId: String)(implicit ec: ExecutionContext): Future[(Int, Int)] = Future.successful((42000, 42))
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ object TestConstants {
|
|||
val pushMsat = 200000000L
|
||||
|
||||
object Alice {
|
||||
val id = randomKey.publicKey
|
||||
val channelParams = LocalParams(
|
||||
dustLimitSatoshis = 542,
|
||||
maxHtlcValueInFlightMsat = Long.MaxValue,
|
||||
|
@ -32,6 +33,7 @@ object TestConstants {
|
|||
}
|
||||
|
||||
object Bob {
|
||||
val id = randomKey.publicKey
|
||||
val channelParams = LocalParams(
|
||||
dustLimitSatoshis = 542,
|
||||
maxHtlcValueInFlightMsat = Long.MaxValue,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package fr.acinq.eclair.channel.states
|
||||
package fr.acinq.eclair
|
||||
|
||||
import akka.actor.{ActorNotFound, ActorSystem, PoisonPill}
|
||||
import akka.testkit.TestKit
|
||||
|
@ -7,9 +7,10 @@ import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, fixture}
|
|||
import scala.concurrent.Await
|
||||
|
||||
/**
|
||||
* This base class kills all actor between each tests.
|
||||
* Created by PM on 06/09/2016.
|
||||
*/
|
||||
abstract class StateSpecBaseClass extends TestKit(ActorSystem("test")) with fixture.FunSuiteLike with BeforeAndAfterEach with BeforeAndAfterAll {
|
||||
abstract class TestkitBaseClass extends TestKit(ActorSystem("test")) with fixture.FunSuiteLike with BeforeAndAfterEach with BeforeAndAfterAll {
|
||||
|
||||
override def afterEach() {
|
||||
system.actorSelection(system / "*") ! PoisonPill
|
|
@ -8,6 +8,7 @@ import fr.acinq.bitcoin.{BinaryData, Crypto}
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.payment.Relayer
|
||||
import fr.acinq.eclair.wire.UpdateAddHtlc
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
|
@ -51,13 +52,15 @@ class ThroughputSpec extends FunSuite {
|
|||
context.become(run(h2r - htlc.paymentHash))
|
||||
}
|
||||
}), "payment-handler")
|
||||
val alice = system.actorOf(Channel.props(pipe, blockchain, paymentHandler, Alice.channelParams, "B"), "a")
|
||||
val bob = system.actorOf(Channel.props(pipe, blockchain, paymentHandler, Bob.channelParams, "A"), "b")
|
||||
val router: ActorRef = ???
|
||||
val relayer = system.actorOf(Relayer.props(Globals.Node.privateKey, paymentHandler))
|
||||
val alice = system.actorOf(Channel.props(pipe, blockchain, ???, relayer, Alice.channelParams, Bob.id), "a")
|
||||
val bob = system.actorOf(Channel.props(pipe, blockchain, ???, relayer, Bob.channelParams, Alice.id), "b")
|
||||
|
||||
val latch = new CountDownLatch(2)
|
||||
val listener = system.actorOf(Props(new Actor {
|
||||
override def receive: Receive = {
|
||||
case ChannelChangedState(_, _, _, NORMAL, _) => latch.countDown()
|
||||
case ChannelChangedState(_, _, _, _, NORMAL, _) => latch.countDown()
|
||||
}
|
||||
}), "listener")
|
||||
system.eventStream.subscribe(listener, classOf[ChannelEvent])
|
||||
|
|
|
@ -41,7 +41,7 @@ trait StateTestsHelperMethods extends TestKitBase {
|
|||
alice2blockchain.forward(blockchainA)
|
||||
bob2blockchain.expectMsgType[WatchSpent]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
bob ! BITCOIN_FUNDING_DEPTHOK
|
||||
bob ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||
alice2blockchain.expectMsgType[WatchLost]
|
||||
bob2blockchain.expectMsgType[WatchLost]
|
||||
alice2bob.expectMsgType[FundingLocked]
|
||||
|
|
|
@ -4,10 +4,9 @@ import akka.actor.{ActorRef, Props}
|
|||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.PeerWatcher
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_CREATED_INTERNAL, _}
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, OpenChannel}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -17,7 +16,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForAcceptChannelStateSpec extends StateSpecBaseClass {
|
||||
class WaitForAcceptChannelTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
|
||||
|
||||
|
@ -27,9 +26,10 @@ class WaitForAcceptChannelStateSpec extends StateSpecBaseClass {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
|
@ -1,10 +1,9 @@
|
|||
package fr.acinq.eclair.channel.states.a
|
||||
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.eclair.TestConstants
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestConstants}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire.{Error, OpenChannel}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
@ -15,7 +14,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForOpenChannelStateSpec extends StateSpecBaseClass {
|
||||
class WaitForOpenChannelTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
||||
|
||||
|
@ -24,9 +23,10 @@ class WaitForOpenChannelStateSpec extends StateSpecBaseClass {
|
|||
val bob2alice = TestProbe()
|
||||
val alice2blockchain = TestProbe()
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
|
@ -5,9 +5,8 @@ import akka.testkit.{TestFSMRef, TestProbe}
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.{MakeFundingTx, PeerWatcher}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, OpenChannel}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -17,7 +16,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForFundingCreatedInternalStateSpec extends StateSpecBaseClass {
|
||||
class WaitForFundingCreatedInternalTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
|
||||
|
||||
|
@ -27,9 +26,10 @@ class WaitForFundingCreatedInternalStateSpec extends StateSpecBaseClass {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
|
@ -5,9 +5,8 @@ import akka.testkit.{TestFSMRef, TestProbe}
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.{PeerWatcher, WatchConfirmed, WatchSpent}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -17,7 +16,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForFundingCreatedStateSpec extends StateSpecBaseClass {
|
||||
class WaitForFundingCreatedTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
||||
|
||||
|
@ -26,9 +25,10 @@ class WaitForFundingCreatedStateSpec extends StateSpecBaseClass {
|
|||
val bob2alice = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, blockchainA, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, blockchainA, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
|
@ -6,9 +6,8 @@ import fr.acinq.bitcoin.BinaryData
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, OpenChannel}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -18,7 +17,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForFundingSignedStateSpec extends StateSpecBaseClass {
|
||||
class WaitForFundingSignedTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
|
||||
|
||||
|
@ -28,9 +27,10 @@ class WaitForFundingSignedStateSpec extends StateSpecBaseClass {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
|
@ -5,9 +5,8 @@ import akka.testkit.{TestFSMRef, TestProbe}
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, OpenChannel}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -17,7 +16,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
||||
class WaitForFundingLockedInternalTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
|
||||
|
||||
|
@ -27,9 +26,10 @@ class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
||||
|
@ -54,7 +54,7 @@ class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
|||
test("recv FundingLocked") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
// make bob send a FundingLocked msg
|
||||
bob ! BITCOIN_FUNDING_DEPTHOK
|
||||
bob ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||
val msg = bob2alice.expectMsgType[FundingLocked]
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].deferred == Some(msg))
|
||||
|
@ -64,7 +64,7 @@ class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
|||
|
||||
test("recv BITCOIN_FUNDING_DEPTHOK") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
alice ! BITCOIN_FUNDING_DEPTHOK
|
||||
alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
|
||||
alice2blockchain.expectMsgType[WatchLost]
|
||||
alice2bob.expectMsgType[FundingLocked]
|
||||
|
@ -83,7 +83,7 @@ class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
|||
within(30 seconds) {
|
||||
// bob publishes his commitment tx
|
||||
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! (BITCOIN_FUNDING_SPENT, tx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
|
|||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! (BITCOIN_FUNDING_SPENT, null)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, null)
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
|
@ -5,9 +5,8 @@ import akka.testkit.{TestFSMRef, TestProbe}
|
|||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.states.StateSpecBaseClass
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -17,7 +16,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
|
||||
class WaitForFundingLockedTestkit extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
|
||||
|
||||
|
@ -27,9 +26,10 @@ class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, TestConstants.pushMsat)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
within(30 seconds) {
|
||||
|
@ -50,7 +50,7 @@ class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
|
|||
alice2blockchain.forward(blockchainA)
|
||||
bob2blockchain.expectMsgType[WatchSpent]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
bob ! BITCOIN_FUNDING_DEPTHOK
|
||||
bob ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||
alice2blockchain.expectMsgType[WatchLost]
|
||||
bob2blockchain.expectMsgType[WatchLost]
|
||||
alice2bob.expectMsgType[FundingLocked]
|
||||
|
@ -68,11 +68,23 @@ class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv FundingLocked (channel id mismatch") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val fundingLocked = bob2alice.expectMsgType[FundingLocked]
|
||||
alice ! fundingLocked.copy(channelId = 42)
|
||||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
// bob publishes his commitment tx
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! (BITCOIN_FUNDING_SPENT, tx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
}
|
||||
|
@ -81,7 +93,7 @@ class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
|
|||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice ! (BITCOIN_FUNDING_SPENT, null)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, null)
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
|
@ -6,11 +6,12 @@ import fr.acinq.bitcoin.Crypto.{Point, Scalar}
|
|||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Script, ScriptFlags, Transaction}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.payment.Binding
|
||||
import fr.acinq.eclair.transactions.{IN, OUT}
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, CommitSig, Error, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants, TestkitBaseClass}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
|
@ -20,9 +21,9 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
||||
class NormalTestkit extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe]
|
||||
type FixtureParam = Tuple7[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe, TestProbe]
|
||||
|
||||
override def withFixture(test: OneArgTest) = {
|
||||
val alice2bob = TestProbe()
|
||||
|
@ -30,33 +31,52 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain))
|
||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _) =>
|
||||
test("recv CMD_ADD_HTLC (empty origin)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val sender = TestProbe()
|
||||
val h = BinaryData("00112233445566778899aabbccddeeff")
|
||||
sender.send(alice, CMD_ADD_HTLC(50000000, h, 144))
|
||||
sender.send(alice, CMD_ADD_HTLC(50000000, h, 144, origin = None))
|
||||
sender.expectMsg("ok")
|
||||
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
|
||||
assert(htlc.id == 1 && htlc.paymentHash == h)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil)),
|
||||
downstreams = Map(htlc.id -> None)))
|
||||
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil))))
|
||||
relayer.expectNoMsg()
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _) =>
|
||||
test("recv CMD_ADD_HTLC (relayed htlc)") { case (alice, _, alice2bob, _, _, _, relayer) =>
|
||||
within(30 seconds) {
|
||||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val sender = TestProbe()
|
||||
val h = BinaryData("00112233445566778899aabbccddeeff")
|
||||
val originHtlc = UpdateAddHtlc(channelId = 4298564, id = 5656, amountMsat = 50000000, expiry = 144, paymentHash = h, onionRoutingPacket = "00" * 1254)
|
||||
val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000, h, originHtlc.expiry - 7, origin = Some(originHtlc))
|
||||
sender.send(alice, cmd)
|
||||
sender.expectMsg("ok")
|
||||
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
|
||||
assert(htlc.id == 1 && htlc.paymentHash == h)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil))))
|
||||
val binding = relayer.expectMsgType[Binding]
|
||||
assert(binding === Binding(downstream = originHtlc, upstream = htlc))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 144))
|
||||
|
@ -64,7 +84,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 1/2)") { case (alice, _, alice2bob, _, _, _) =>
|
||||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 1/2)") { case (alice, _, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_ADD_HTLC(500000000, "11" * 32, 144))
|
||||
|
@ -76,7 +96,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { case (alice, _, alice2bob, _, _, _) =>
|
||||
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { case (alice, _, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 144))
|
||||
|
@ -88,7 +108,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_ADD_HTLC (while waiting for Shutdown)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv CMD_ADD_HTLC (while waiting for Shutdown)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_CLOSE(None))
|
||||
|
@ -102,7 +122,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateAddHtlc") { case (_, bob, alice2bob, _, _, _) =>
|
||||
test("recv UpdateAddHtlc") { case (_, bob, alice2bob, _, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val initialData = bob.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val htlc = UpdateAddHtlc(0, 42, 150, 144, BinaryData("00112233445566778899aabbccddeeff"), "")
|
||||
|
@ -111,7 +131,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateAddHtlc (insufficient funds)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv UpdateAddHtlc (insufficient funds)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val htlc = UpdateAddHtlc(0, 42, Long.MaxValue, 144, BinaryData("00112233445566778899aabbccddeeff"), "")
|
||||
|
@ -123,7 +143,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice2bob.forward(bob, UpdateAddHtlc(0, 42, 500000000, 144, "11" * 32, ""))
|
||||
|
@ -136,7 +156,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
alice2bob.forward(bob, UpdateAddHtlc(0, 42, 300000000, 144, "11" * 32, ""))
|
||||
|
@ -149,7 +169,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_SIGN") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_SIGN") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -160,7 +180,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_SIGN (no changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv CMD_SIGN (no changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_SIGN)
|
||||
|
@ -169,7 +189,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
ignore("recv CMD_SIGN (while waiting for RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
ignore("recv CMD_SIGN (while waiting for RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -184,7 +204,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CommitSig (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
|
@ -206,7 +226,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CommitSig (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
|
@ -232,7 +252,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CommitSig (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
|
@ -269,7 +289,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
|
||||
// TODO: maybe should be illegal?
|
||||
ignore("recv CommitSig (two htlcs received with same r)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
ignore("recv CommitSig (two htlcs received with same r)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val r = BinaryData("00112233445566778899aabbccddeeff")
|
||||
|
@ -296,7 +316,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (no changes)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv CommitSig (no changes)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -309,7 +329,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CommitSig (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv CommitSig (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -324,7 +344,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv RevokeAndAck (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -343,7 +363,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv RevokeAndAck (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -368,7 +388,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv RevokeAndAck (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) // a->b (regular)
|
||||
|
@ -408,7 +428,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv RevokeAndAck (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -429,7 +449,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (unexpectedly)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv RevokeAndAck (unexpectedly)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -442,7 +462,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_FULFILL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_FULFILL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -457,7 +477,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_FULFILL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_FULFILL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val r: BinaryData = "11" * 32
|
||||
|
@ -469,7 +489,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_FULFILL_HTLC (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_FULFILL_HTLC (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -483,7 +503,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFulfillHtlc (sender has not signed)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv UpdateFulfillHtlc (sender has not signed)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -497,12 +517,11 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
|
||||
downstreams = Map()))
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFulfillHtlc (sender has signed)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv UpdateFulfillHtlc (sender has signed)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -516,12 +535,11 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
|
||||
downstreams = Map()))
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFulfillHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv UpdateFulfillHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -533,7 +551,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFulfillHtlc (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv UpdateFulfillHtlc (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -549,7 +567,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_FAIL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_FAIL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -564,7 +582,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_FAIL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_FAIL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val r: BinaryData = "11" * 32
|
||||
|
@ -576,7 +594,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFailHtlc (sender has not signed)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv UpdateFailHtlc (sender has not signed)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -590,12 +608,11 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
|
||||
downstreams = Map()))
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFailHtlc (sender has signed") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv UpdateFailHtlc (sender has signed") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -610,12 +627,11 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
|
||||
downstreams = Map()))
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))))
|
||||
}
|
||||
}
|
||||
|
||||
test("recv UpdateFailHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv UpdateFailHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
val sender = TestProbe()
|
||||
|
@ -627,7 +643,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (no pending htlcs)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv CMD_CLOSE (no pending htlcs)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty)
|
||||
|
@ -639,7 +655,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_CLOSE (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -648,7 +664,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (with invalid final script)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_CLOSE (with invalid final script)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, CMD_CLOSE(Some(BinaryData("00112233445566778899"))))
|
||||
|
@ -656,7 +672,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (with signed sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
||||
test("recv CMD_CLOSE (with signed sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -669,7 +685,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (two in a row)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv CMD_CLOSE (two in a row)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty)
|
||||
|
@ -683,7 +699,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv CMD_CLOSE (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -698,7 +714,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (no pending htlcs)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
||||
test("recv Shutdown (no pending htlcs)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(alice, Shutdown(0, Script.write(Bob.channelParams.defaultFinalScriptPubKey)))
|
||||
|
@ -708,7 +724,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv Shutdown (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -722,7 +738,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (with unacked received htlcs)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv Shutdown (with unacked received htlcs)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -735,7 +751,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (with invalid final script)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
||||
test("recv Shutdown (with invalid final script)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, Shutdown(0, BinaryData("00112233445566778899")))
|
||||
|
@ -746,7 +762,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (with signed htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv Shutdown (with signed htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -760,7 +776,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Shutdown (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv Shutdown (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
@ -776,7 +792,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (their commit w/ htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
||||
test("recv BITCOIN_FUNDING_SPENT (their commit w/ htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
|
@ -803,7 +819,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
// bob publishes his current commit tx
|
||||
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
|
||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||
|
||||
|
@ -826,7 +842,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (revoked commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv BITCOIN_FUNDING_SPENT (revoked commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val sender = TestProbe()
|
||||
|
||||
|
@ -855,7 +871,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
// a->b = 10 000
|
||||
// two main outputs + 4 htlc
|
||||
assert(revokedTx.txOut.size == 6)
|
||||
alice ! (BITCOIN_FUNDING_SPENT, revokedTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx)
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
||||
|
@ -876,7 +892,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
}
|
||||
}
|
||||
|
||||
test("recv Error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
|
||||
test("recv Error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
||||
within(30 seconds) {
|
||||
val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice)
|
||||
val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice)
|
|
@ -4,10 +4,10 @@ import akka.actor.Props
|
|||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.Crypto.Scalar
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction}
|
||||
import fr.acinq.eclair.TestBitcoinClient
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.wire.{CommitSig, Error, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -19,7 +19,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
||||
class ShutdownTestkit extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe]
|
||||
|
||||
|
@ -29,9 +29,10 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
|
||||
val sender = TestProbe()
|
||||
|
@ -356,7 +357,7 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
|
|||
// bob publishes his current commit tx, which contains two pending htlcs alice->bob
|
||||
val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx
|
||||
assert(bobCommitTx.txOut.size == 4) // two main outputs and 2 pending htlcs
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
|
||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||
|
||||
|
@ -392,7 +393,7 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
|
|||
// bob now has a new commitment tx
|
||||
|
||||
// bob published the revoked tx
|
||||
alice ! (BITCOIN_FUNDING_SPENT, revokedTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx)
|
||||
alice2bob.expectMsgType[Error]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
|
@ -2,10 +2,10 @@ package fr.acinq.eclair.channel.states.g
|
|||
|
||||
import akka.actor.Props
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.eclair.TestBitcoinClient
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -17,7 +17,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class NegotiatingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
||||
class NegotiatingTestkit extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe]
|
||||
|
||||
|
@ -27,10 +27,11 @@ class NegotiatingStateSpec extends StateSpecBaseClass with StateTestsHelperMetho
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val relayer = TestProbe()
|
||||
// note that alice.initialFeeRate != bob.initialFeeRate
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
|
||||
val sender = TestProbe()
|
||||
|
@ -91,7 +92,7 @@ class NegotiatingStateSpec extends StateSpecBaseClass with StateTestsHelperMetho
|
|||
assert(alice.stateName == NEGOTIATING)
|
||||
val mutualCloseTx = bob2blockchain.expectMsgType[PublishAsap].tx
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
alice ! (BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
||||
alice2blockchain.expectNoMsg(1 second)
|
||||
assert(alice.stateName == NEGOTIATING)
|
||||
}
|
|
@ -3,10 +3,10 @@ package fr.acinq.eclair.channel.states.h
|
|||
import akka.actor.Props
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import fr.acinq.eclair.TestBitcoinClient
|
||||
import fr.acinq.eclair.{TestkitBaseClass, TestBitcoinClient}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.wire._
|
||||
import org.junit.runner.RunWith
|
||||
|
@ -18,7 +18,7 @@ import scala.concurrent.duration._
|
|||
* Created by PM on 05/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
||||
class ClosingTestkit extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
type FixtureParam = Tuple7[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe, List[Transaction]]
|
||||
|
||||
|
@ -28,9 +28,10 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val alice2blockchain = TestProbe()
|
||||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val bob2blockchain = TestProbe()
|
||||
val paymentHandler = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "0A"))
|
||||
val relayer = TestProbe()
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, router.ref, relayer.ref, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, router.ref, relayer.ref, Bob.channelParams, Alice.id))
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, blockchainA, alice2blockchain, bob2blockchain)
|
||||
|
||||
|
@ -96,7 +97,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
|
||||
// actual test starts here
|
||||
alice ! BITCOIN_CLOSE_DONE
|
||||
alice ! WatchEventConfirmed(BITCOIN_CLOSE_DONE, 0, 0)
|
||||
awaitCond(alice.stateName == CLOSED)
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +115,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
|
||||
// actual test starts here
|
||||
// we are notified afterwards from our watcher about the tx that we just published
|
||||
alice ! (BITCOIN_FUNDING_SPENT, aliceCommitTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, aliceCommitTx)
|
||||
assert(alice.stateData == initialState) // this was a no-op
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +131,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined)
|
||||
|
||||
// actual test starts here
|
||||
alice ! BITCOIN_SPEND_OURS_DONE
|
||||
alice ! WatchEventConfirmed(BITCOIN_SPEND_OURS_DONE, 0, 0)
|
||||
awaitCond(alice.stateName == CLOSED)
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +143,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
||||
val bobCommitTx = bobCommitTxes.last
|
||||
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
|
||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||
|
||||
|
@ -158,14 +159,14 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
||||
val bobCommitTx = bobCommitTxes.last
|
||||
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
|
||||
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
|
||||
|
||||
// actual test starts here
|
||||
alice ! BITCOIN_SPEND_THEIRS_DONE
|
||||
alice ! WatchEventConfirmed(BITCOIN_SPEND_THEIRS_DONE, 0, 0)
|
||||
awaitCond(alice.stateName == CLOSED)
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +177,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
// bob publishes one of his revoked txes
|
||||
val bobRevokedTx = bobCommitTxes.head
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
|
||||
// alice publishes and watches the punishment tx
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -192,7 +193,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||
// bob publishes multiple revoked txes (last one isn't revoked)
|
||||
for (bobRevokedTx <- bobCommitTxes.dropRight(1)) {
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
// alice publishes and watches the punishment tx
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
|
@ -208,7 +209,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
// bob publishes one of his revoked txes
|
||||
val bobRevokedTx = bobCommitTxes.head
|
||||
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||
// alice publishes and watches the punishment tx
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
|
@ -216,7 +217,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
|
|||
// awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING] == initialState.copy(revokedCommitPublished = Seq(RevokedCommitPublished(bobRevokedTx))))
|
||||
|
||||
// actual test starts here
|
||||
alice ! BITCOIN_PUNISHMENT_DONE
|
||||
alice ! WatchEventConfirmed(BITCOIN_PUNISHMENT_DONE, 0, 0)
|
||||
awaitCond(alice.stateName == CLOSED)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package fr.acinq.eclair.crypto
|
||||
|
||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
|
|
|
@ -32,8 +32,8 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
|
|||
val pipe = system.actorOf(Props[MyPipe])
|
||||
val probe1 = TestProbe()
|
||||
val probe2 = TestProbe()
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _) => probe1.ref, TransportHandler.Noop))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _) => probe2.ref, TransportHandler.Noop))
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop))
|
||||
pipe ! (initiator, responder)
|
||||
|
||||
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
|
||||
|
@ -68,8 +68,8 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
|
|||
val pipe = system.actorOf(Props[MyPipe])
|
||||
val probe1 = TestProbe()
|
||||
val probe2 = TestProbe()
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _) => probe1.ref, mySerializer))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _) => probe2.ref, mySerializer))
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, mySerializer))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, mySerializer))
|
||||
pipe ! (initiator, responder)
|
||||
|
||||
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
|
||||
|
@ -92,8 +92,8 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
|
|||
val pipe = system.actorOf(Props[MyPipeSplitter])
|
||||
val probe1 = TestProbe()
|
||||
val probe2 = TestProbe()
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _) => probe1.ref, TransportHandler.Noop))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _) => probe2.ref, TransportHandler.Noop))
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Responder.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop))
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop))
|
||||
pipe ! (initiator, responder)
|
||||
|
||||
awaitCond(initiator.stateName == TransportHandler.WaitingForCyphertext)
|
||||
|
@ -117,8 +117,8 @@ class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLik
|
|||
val probe1 = TestProbe()
|
||||
val probe2 = TestProbe()
|
||||
val supervisor = TestActorRef(Props(new MySupervisor()))
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Initiator.s.pub), pipe, true, (conn, _) => probe1.ref, TransportHandler.Noop), supervisor)
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _) => probe2.ref, TransportHandler.Noop), supervisor)
|
||||
val initiator = TestFSMRef(new TransportHandler(Initiator.s, Some(Initiator.s.pub), pipe, true, (conn, _, _) => probe1.ref, TransportHandler.Noop), supervisor)
|
||||
val responder = TestFSMRef(new TransportHandler(Responder.s, None, pipe, false, (conn, _, _) => probe2.ref, TransportHandler.Noop), supervisor)
|
||||
probe1.watch(initiator)
|
||||
pipe ! (initiator, responder)
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class InteroperabilitySpec extends FunSuite with BeforeAndAfterAll {
|
|||
super.afterAll()
|
||||
}
|
||||
|
||||
def sendCommand(channelId: String, cmd: Command): Future[String] = {
|
||||
def sendCommand(channelId: Long, cmd: Command): Future[String] = {
|
||||
system.actorSelection(Register.actorPathToChannelId(system, channelId)).resolveOne().map(actor => {
|
||||
actor ! cmd
|
||||
"ok"
|
||||
|
@ -145,7 +145,7 @@ class InteroperabilitySpec extends FunSuite with BeforeAndAfterAll {
|
|||
def now: Int = (System.currentTimeMillis() / 1000).toInt
|
||||
|
||||
val future = for {
|
||||
channelId <- listChannels.map(_.head).map(_.channelId.toString)
|
||||
channelId <- listChannels.map(_.head).map(_.channelId)
|
||||
peer = lncli.getPeers.head
|
||||
// lightningd sends us a htlc
|
||||
blockcount <- btccli.getBlockCount
|
||||
|
|
|
@ -4,11 +4,11 @@ import java.io.File
|
|||
import java.util.concurrent.{CountDownLatch, TimeUnit}
|
||||
|
||||
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||
import akka.testkit.{TestFSMRef, TestKit}
|
||||
import akka.testkit.{TestFSMRef, TestKit, TestProbe}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.blockchain.PeerWatcher
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.payment.NoopPaymentHandler
|
||||
import fr.acinq.eclair.payment.{NoopPaymentHandler, Relayer}
|
||||
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
@ -31,8 +31,11 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix
|
|||
val blockchainA = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val blockchainB = system.actorOf(Props(new PeerWatcher(new TestBitcoinClient(), 300)))
|
||||
val paymentHandler = system.actorOf(Props(new NoopPaymentHandler()))
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainA, paymentHandler, Alice.channelParams, "0B"))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainB, paymentHandler, Bob.channelParams, "0A"))
|
||||
// we just bypass the relayer for this test
|
||||
val relayer = paymentHandler
|
||||
val router = TestProbe()
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainA, router.ref, relayer, Alice.channelParams, Bob.id))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(pipe, blockchainB, router.ref, relayer, Bob.channelParams, Alice.id))
|
||||
alice ! INPUT_INIT_FUNDER(TestConstants.fundingSatoshis, 0)
|
||||
bob ! INPUT_INIT_FUNDEE()
|
||||
pipe ! (alice, bob)
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import fr.acinq.bitcoin.BinaryData
|
||||
import fr.acinq.eclair.crypto.Sphinx
|
||||
import fr.acinq.eclair.payment.PaymentLifecycle._
|
||||
import fr.acinq.eclair.randomKey
|
||||
import fr.acinq.eclair.router.Hop
|
||||
import fr.acinq.eclair.wire.{ChannelUpdate, Codecs, PerHopPayload}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import scodec.bits.BitVector
|
||||
|
||||
/**
|
||||
* Created by PM on 31/05/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class HtlcGenerationSpec extends FunSuite {
|
||||
|
||||
test("compute fees") {
|
||||
val feeBaseMsat = 150000L
|
||||
val feeProportionalMillionth = 4L
|
||||
val htlcAmountMsat = 42000000
|
||||
// spec: fee-base-msat + htlc-amount-msat * fee-proportional-millionths / 1000000
|
||||
val ref = feeBaseMsat + htlcAmountMsat * feeProportionalMillionth / 1000000
|
||||
val fee = nodeFee(feeBaseMsat, feeProportionalMillionth, htlcAmountMsat)
|
||||
assert(ref === fee)
|
||||
}
|
||||
|
||||
import HtlcGenerationSpec._
|
||||
|
||||
test("compute route with fees and expiry delta") {
|
||||
|
||||
val (firstAmountMsat, firstExpiry, payloads) = buildRoute(finalAmountMsat, hops.drop(1), currentBlockCount)
|
||||
|
||||
assert(firstAmountMsat === amount_ab)
|
||||
assert(firstExpiry === expiry_ab)
|
||||
assert(payloads ===
|
||||
PerHopPayload(amount_bc, expiry_bc) ::
|
||||
PerHopPayload(amount_cd, expiry_cd) ::
|
||||
PerHopPayload(amount_de, expiry_de) :: Nil)
|
||||
}
|
||||
|
||||
test("build onion") {
|
||||
|
||||
val (_, _, payloads) = buildRoute(finalAmountMsat, hops.drop(1), currentBlockCount)
|
||||
val nodes = hops.map(_.nextNodeId)
|
||||
val packet_b = buildOnion(nodes, payloads, paymentHash)
|
||||
assert(packet_b.size === 1254)
|
||||
|
||||
// let's peel the onion
|
||||
val (bin_b, address_c, packet_c) = Sphinx.parsePacket(priv_b, paymentHash, packet_b)
|
||||
val payload_b = Codecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).toOption.get.value
|
||||
assert(address_c === c.hash160)
|
||||
assert(packet_c.size === 1254)
|
||||
assert(payload_b.amt_to_forward === amount_bc)
|
||||
assert(payload_b.outgoing_cltv_value === expiry_bc)
|
||||
|
||||
val (bin_c, address_d, packet_d) = Sphinx.parsePacket(priv_c, paymentHash, packet_c)
|
||||
val payload_c = Codecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).toOption.get.value
|
||||
assert(address_d === d.hash160)
|
||||
assert(packet_d.size === 1254)
|
||||
assert(payload_c.amt_to_forward === amount_cd)
|
||||
assert(payload_c.outgoing_cltv_value === expiry_cd)
|
||||
|
||||
val (bin_d, address_e, packet_e) = Sphinx.parsePacket(priv_d, paymentHash, packet_d)
|
||||
val payload_d = Codecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).toOption.get.value
|
||||
assert(address_e === e.hash160)
|
||||
assert(packet_e.size === 1254)
|
||||
assert(payload_d.amt_to_forward === amount_de)
|
||||
assert(payload_d.outgoing_cltv_value === expiry_de)
|
||||
|
||||
val (bin_e, address_null, packet_random) = Sphinx.parsePacket(priv_e, paymentHash, packet_e)
|
||||
assert(bin_e === BinaryData("00" * 20))
|
||||
assert(address_null === BinaryData("00" * 20))
|
||||
assert(packet_random.size === 1254)
|
||||
}
|
||||
|
||||
test("build a command including the onion") {
|
||||
|
||||
val add = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
|
||||
assert(add.amountMsat > finalAmountMsat)
|
||||
assert(add.expiry === currentBlockCount + defaultHtlcExpiry + channelUpdate_de.cltvExpiryDelta + channelUpdate_cd.cltvExpiryDelta + channelUpdate_bc.cltvExpiryDelta)
|
||||
assert(add.paymentHash === paymentHash)
|
||||
assert(add.onion.size === 1254)
|
||||
|
||||
// let's peel the onion
|
||||
val (bin_b, address_c, packet_c) = Sphinx.parsePacket(priv_b, paymentHash, add.onion)
|
||||
val payload_b = Codecs.perHopPayloadCodec.decode(BitVector(bin_b.data)).toOption.get.value
|
||||
assert(address_c === c.hash160)
|
||||
assert(packet_c.size === 1254)
|
||||
assert(payload_b.amt_to_forward === amount_bc)
|
||||
assert(payload_b.outgoing_cltv_value === expiry_bc)
|
||||
|
||||
val (bin_c, address_d, packet_d) = Sphinx.parsePacket(priv_c, paymentHash, packet_c)
|
||||
val payload_c = Codecs.perHopPayloadCodec.decode(BitVector(bin_c.data)).toOption.get.value
|
||||
assert(address_d === d.hash160)
|
||||
assert(packet_d.size === 1254)
|
||||
assert(payload_c.amt_to_forward === amount_cd)
|
||||
assert(payload_c.outgoing_cltv_value === expiry_cd)
|
||||
|
||||
val (bin_d, address_e, packet_e) = Sphinx.parsePacket(priv_d, paymentHash, packet_d)
|
||||
val payload_d = Codecs.perHopPayloadCodec.decode(BitVector(bin_d.data)).toOption.get.value
|
||||
assert(address_e === e.hash160)
|
||||
assert(packet_e.size === 1254)
|
||||
assert(payload_d.amt_to_forward === amount_de)
|
||||
assert(payload_d.outgoing_cltv_value === expiry_de)
|
||||
|
||||
val (bin_e, address_null, packet_random) = Sphinx.parsePacket(priv_e, paymentHash, packet_e)
|
||||
assert(bin_e === BinaryData("00" * 20))
|
||||
assert(address_null === BinaryData("00" * 20))
|
||||
assert(packet_random.size === 1254)
|
||||
}
|
||||
|
||||
test("build a command with no hops") {
|
||||
val add = buildCommand(finalAmountMsat, paymentHash, hops.take(1), currentBlockCount)
|
||||
|
||||
assert(add.amountMsat === finalAmountMsat)
|
||||
assert(add.expiry === currentBlockCount + defaultHtlcExpiry)
|
||||
assert(add.paymentHash === paymentHash)
|
||||
assert(add.onion.size === 1254)
|
||||
|
||||
// let's peel the onion
|
||||
val (bin_b, address_null, packet_random) = Sphinx.parsePacket(priv_b, paymentHash, add.onion)
|
||||
assert(bin_b === BinaryData("00" * 20))
|
||||
assert(address_null === BinaryData("00" * 20))
|
||||
assert(packet_random.size === 1254)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object HtlcGenerationSpec {
|
||||
val (priv_a, priv_b, priv_c, priv_d, priv_e) = (randomKey, randomKey, randomKey, randomKey, randomKey)
|
||||
val (a, b, c, d, e) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey)
|
||||
|
||||
val defaultChannelUpdate = ChannelUpdate("00" * 64, 0, 0, "0000", 0, 0, 0, 0)
|
||||
val channelUpdate_ab = defaultChannelUpdate.copy(cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7)
|
||||
val channelUpdate_bc = defaultChannelUpdate.copy(cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
||||
val channelUpdate_cd = defaultChannelUpdate.copy(cltvExpiryDelta = 10, feeBaseMsat = 60000, feeProportionalMillionths = 1)
|
||||
val channelUpdate_de = defaultChannelUpdate.copy(cltvExpiryDelta = 7, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
||||
|
||||
// simple route a -> b -> c -> d -> e
|
||||
|
||||
val hops =
|
||||
Hop(a, b, channelUpdate_ab) ::
|
||||
Hop(b, c, channelUpdate_bc) ::
|
||||
Hop(c, d, channelUpdate_cd) ::
|
||||
Hop(d, e, channelUpdate_de) :: Nil
|
||||
|
||||
val finalAmountMsat = 42000000L
|
||||
val paymentHash = BinaryData("42" * 32)
|
||||
val currentBlockCount = 420000
|
||||
|
||||
val expiry_de = currentBlockCount + defaultHtlcExpiry
|
||||
val amount_de = finalAmountMsat
|
||||
val fee_d = nodeFee(channelUpdate_de.feeBaseMsat, channelUpdate_de.feeProportionalMillionths, amount_de)
|
||||
|
||||
val expiry_cd = expiry_de + channelUpdate_de.cltvExpiryDelta
|
||||
val amount_cd = amount_de + fee_d
|
||||
val fee_c = nodeFee(channelUpdate_cd.feeBaseMsat, channelUpdate_cd.feeProportionalMillionths, amount_cd)
|
||||
|
||||
val expiry_bc = expiry_cd + channelUpdate_cd.cltvExpiryDelta
|
||||
val amount_bc = amount_cd + fee_c
|
||||
val fee_b = nodeFee(channelUpdate_bc.feeBaseMsat, channelUpdate_bc.feeProportionalMillionths, amount_bc)
|
||||
|
||||
val expiry_ab = expiry_bc + channelUpdate_bc.cltvExpiryDelta
|
||||
val amount_ab = amount_bc + fee_b
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
|
||||
import akka.actor.Status.Failure
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.eclair.router.BaseRouterSpec
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
/**
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||
|
||||
val initialBlockCount = 420000
|
||||
|
||||
test("payment failed (route not found)") { case (router, _) =>
|
||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, initialBlockCount))
|
||||
val monitor = TestProbe()
|
||||
val sender = TestProbe()
|
||||
|
||||
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val request = CreatePayment(142000, "42" * 32, f)
|
||||
sender.send(paymentFSM, request)
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
val res = sender.expectMsgType[Failure]
|
||||
assert(res.cause.getMessage === "route not found")
|
||||
}
|
||||
|
||||
test("payment failed (htlc failed)") { case (router, _) =>
|
||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, initialBlockCount))
|
||||
val monitor = TestProbe()
|
||||
val sender = TestProbe()
|
||||
|
||||
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val request = CreatePayment(142000, "42" * 32, d)
|
||||
sender.send(paymentFSM, request)
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
sender.send(paymentFSM, PaymentFailed(null, request.paymentHash, "some reason"))
|
||||
|
||||
val res = sender.expectMsgType[Failure]
|
||||
assert(res.cause.getMessage === "some reason")
|
||||
}
|
||||
|
||||
test("payment succeeded") { case (router, _) =>
|
||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, initialBlockCount))
|
||||
val monitor = TestProbe()
|
||||
val sender = TestProbe()
|
||||
|
||||
paymentFSM ! SubscribeTransitionCallBack(monitor.ref)
|
||||
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
|
||||
|
||||
val request = CreatePayment(142000, "42" * 32, d)
|
||||
sender.send(paymentFSM, request)
|
||||
val Transition(_, WAITING_FOR_REQUEST, WAITING_FOR_ROUTE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
val Transition(_, WAITING_FOR_ROUTE, WAITING_FOR_PAYMENT_COMPLETE) = monitor.expectMsgClass(classOf[Transition[_]])
|
||||
|
||||
sender.send(paymentFSM, PaymentSent(null, request.paymentHash))
|
||||
|
||||
val res = sender.expectMsgType[String]
|
||||
assert(res === "sent")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package fr.acinq.eclair.payment
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.eclair.TestkitBaseClass
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.payment.PaymentLifecycle.buildCommand
|
||||
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class RelayerSpec extends TestkitBaseClass {
|
||||
|
||||
// let's reuse the existing test data
|
||||
import HtlcGenerationSpec._
|
||||
|
||||
type FixtureParam = Tuple3[ActorRef, TestProbe, TestProbe]
|
||||
|
||||
override def withFixture(test: OneArgTest) = {
|
||||
|
||||
within(30 seconds) {
|
||||
val paymentHandler = TestProbe()
|
||||
val paymentListener = TestProbe()
|
||||
system.eventStream.subscribe(paymentListener.ref, classOf[PaymentEvent])
|
||||
// we are node B in the route A -> B -> C -> ....
|
||||
val relayer = system.actorOf(Relayer.props(priv_b, paymentHandler.ref))
|
||||
test((relayer, paymentHandler, paymentListener))
|
||||
}
|
||||
}
|
||||
|
||||
// node c is the next node in the route
|
||||
val nodeId_a = PublicKey(a)
|
||||
val nodeId_c = PublicKey(c)
|
||||
val channelId_ab = 981408633
|
||||
val channelId_bc = 237534
|
||||
|
||||
test("add a channel") { case (relayer, _, _) =>
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, 'upstreams)
|
||||
val upstreams = sender.expectMsgType[Set[OutgoingChannel]]
|
||||
assert(upstreams === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
|
||||
}
|
||||
|
||||
test("remove a channel (mutual close)") { case (relayer, _, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, 'upstreams)
|
||||
val upstreams1 = sender.expectMsgType[Set[OutgoingChannel]]
|
||||
assert(upstreams1 === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, SHUTDOWN, NEGOTIATING, DATA_NEGOTIATING(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null, null, null)))
|
||||
sender.send(relayer, 'upstreams)
|
||||
val upstreams2 = sender.expectMsgType[Set[OutgoingChannel]]
|
||||
assert(upstreams2 === Set.empty)
|
||||
}
|
||||
|
||||
test("remove a channel (unilateral close)") { case (relayer, _, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, 'upstreams)
|
||||
val upstreams1 = sender.expectMsgType[Set[OutgoingChannel]]
|
||||
assert(upstreams1 === Set(OutgoingChannel(channelId_bc, channel_bc.ref, nodeId_c.hash160)))
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, NORMAL, CLOSING, DATA_CLOSING(Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), None, Some(null), None, None, Nil)))
|
||||
sender.send(relayer, 'upstreams)
|
||||
val upstreams2 = sender.expectMsgType[Set[OutgoingChannel]]
|
||||
assert(upstreams2 === Set.empty)
|
||||
}
|
||||
|
||||
test("send an event when we receive a payment") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops.take(1), currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
sender.send(relayer, add_ab)
|
||||
|
||||
val add1 = paymentHandler.expectMsgType[UpdateAddHtlc]
|
||||
paymentListener.expectMsgType[PaymentReceived]
|
||||
|
||||
assert(add1 === add_ab)
|
||||
|
||||
}
|
||||
|
||||
test("relay an htlc-add") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, add_ab)
|
||||
|
||||
sender.expectNoMsg(1 second)
|
||||
val cmd_bc = channel_bc.expectMsgType[CMD_ADD_HTLC]
|
||||
paymentHandler.expectNoMsg(1 second)
|
||||
|
||||
assert(cmd_bc.origin === Some(add_ab))
|
||||
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when there is no available upstream channel") { case (relayer, paymentHandler, _) =>
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
sender.send(relayer, add_ab)
|
||||
|
||||
val fail = sender.expectMsgType[CMD_FAIL_HTLC]
|
||||
channel_bc.expectNoMsg(1 second)
|
||||
paymentHandler.expectNoMsg(1 second)
|
||||
|
||||
assert(fail.id === add_ab.id)
|
||||
|
||||
}
|
||||
|
||||
test("fail to relay an htlc-add when the onion is malformed") { case (relayer, paymentHandler, _) =>
|
||||
|
||||
// TODO: we should use the new update_fail_malformed_htlc message (see BOLT 2)
|
||||
val sender = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, "00" * 1254)
|
||||
}
|
||||
|
||||
sender.send(relayer, add_ab)
|
||||
|
||||
val fail = sender.expectMsgType[CMD_FAIL_HTLC]
|
||||
channel_bc.expectNoMsg(1 second)
|
||||
paymentHandler.expectNoMsg(1 second)
|
||||
|
||||
assert(fail.id === add_ab.id)
|
||||
|
||||
}
|
||||
|
||||
test("relay an htlc-fulfill") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_ab = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_ab), null)))
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, 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, Binding(add_ab, add_bc))
|
||||
// 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, (add_bc, fulfill_cb))
|
||||
|
||||
channel_ab.expectMsg(CMD_SIGN)
|
||||
val fulfill_ba = channel_ab.expectMsgType[CMD_FULFILL_HTLC]
|
||||
channel_ab.expectMsg(CMD_SIGN)
|
||||
paymentListener.expectNoMsg(1 second)
|
||||
|
||||
assert(fulfill_ba.id === add_ab.id)
|
||||
|
||||
}
|
||||
|
||||
test("send an event when we receive an htlc-fulfill and we were the initiator") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_ab = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
// note we simulate this by not having a binding for this channel
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops.take(1), currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
// preimage is wrong, does not matter here
|
||||
val fulfill_cb = UpdateFulfillHtlc(channelId = add_ab.channelId, id = add_ab.id, paymentPreimage = "00" * 32)
|
||||
sender.send(relayer, (add_ab, fulfill_cb))
|
||||
|
||||
channel_ab.expectNoMsg(1 second)
|
||||
paymentListener.expectMsgType[PaymentSent]
|
||||
|
||||
}
|
||||
|
||||
test("relay an htlc-fail") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_ab = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
sender.send(relayer, ChannelChangedState(channel_ab.ref, null, nodeId_a, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_ab), null)))
|
||||
sender.send(relayer, ChannelChangedState(channel_bc.ref, null, nodeId_c, WAIT_FOR_FUNDING_LOCKED, NORMAL, DATA_NORMAL(null, Commitments(null, null, null, null, null, null, 0, null, null, null, channelId_bc), null)))
|
||||
sender.send(relayer, 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, Binding(add_ab, add_bc))
|
||||
val fail_cb = UpdateFailHtlc(channelId = add_bc.channelId, id = add_bc.id, reason = "some reason".getBytes())
|
||||
sender.send(relayer, (add_bc, fail_cb))
|
||||
|
||||
channel_ab.expectMsg(CMD_SIGN)
|
||||
val fulfill_ba = channel_ab.expectMsgType[CMD_FAIL_HTLC]
|
||||
channel_ab.expectMsg(CMD_SIGN)
|
||||
paymentListener.expectNoMsg(1 second)
|
||||
|
||||
assert(fulfill_ba.id === add_ab.id)
|
||||
|
||||
}
|
||||
|
||||
test("send an event when we receive an htlc-fail and we were the initiator") { case (relayer, paymentHandler, paymentListener) =>
|
||||
val sender = TestProbe()
|
||||
val channel_ab = TestProbe()
|
||||
val channel_bc = TestProbe()
|
||||
|
||||
// note we simulate this by not having a binding for this channel
|
||||
|
||||
val add_ab = {
|
||||
val cmd = buildCommand(finalAmountMsat, paymentHash, hops, currentBlockCount)
|
||||
// and then manually build an htlc
|
||||
UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.expiry, cmd.paymentHash, cmd.onion)
|
||||
}
|
||||
|
||||
val fail_cb = UpdateFailHtlc(channelId = add_ab.channelId, id = add_ab.id, reason = "some reason".getBytes())
|
||||
sender.send(relayer, (add_ab, fail_cb))
|
||||
|
||||
channel_ab.expectNoMsg(1 second)
|
||||
paymentListener.expectMsgType[PaymentFailed]
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.eclair.router.Router.DUMMY_SIG
|
||||
import fr.acinq.eclair.wire._
|
||||
import fr.acinq.eclair.{TestkitBaseClass, randomKey}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Base class for router testing.
|
||||
* It is re-used in payment FSM tests
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
abstract class BaseRouterSpec extends TestkitBaseClass {
|
||||
|
||||
type FixtureParam = Tuple2[ActorRef, TestProbe]
|
||||
|
||||
def randomPubkey = randomKey.publicKey
|
||||
|
||||
val (a, b, c, d, e, f) = (randomPubkey, randomPubkey, randomPubkey, randomPubkey, randomPubkey, randomPubkey)
|
||||
|
||||
val ann_a = NodeAnnouncement(DUMMY_SIG, 0, a, (0, 0, 0), "node-A", "0000", Nil)
|
||||
val ann_b = NodeAnnouncement(DUMMY_SIG, 0, b, (0, 0, 0), "node-B", "0000", Nil)
|
||||
val ann_c = NodeAnnouncement(DUMMY_SIG, 0, c, (0, 0, 0), "node-C", "0000", Nil)
|
||||
val ann_d = NodeAnnouncement(DUMMY_SIG, 0, d, (0, 0, 0), "node-D", "0000", Nil)
|
||||
val ann_e = NodeAnnouncement(DUMMY_SIG, 0, e, (0, 0, 0), "node-E", "0000", Nil)
|
||||
val ann_f = NodeAnnouncement(DUMMY_SIG, 0, f, (0, 0, 0), "node-F", "0000", Nil)
|
||||
|
||||
val chan_ab = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 1, DUMMY_SIG, DUMMY_SIG, a, b, "", "")
|
||||
val chan_bc = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 2, DUMMY_SIG, DUMMY_SIG, b, c, "", "")
|
||||
val chan_cd = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 3, DUMMY_SIG, DUMMY_SIG, c, d, "", "")
|
||||
val chan_ef = ChannelAnnouncement(DUMMY_SIG, DUMMY_SIG, channelId = 4, DUMMY_SIG, DUMMY_SIG, e, f, "", "")
|
||||
|
||||
val defaultChannelUpdate = ChannelUpdate(Router.DUMMY_SIG, 0, 0, "0000", 0, 0, 0, 0)
|
||||
val channelUpdate_ab = ChannelUpdate(Router.DUMMY_SIG, channelId = 1, 0, "0000", cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
||||
val channelUpdate_bc = ChannelUpdate(Router.DUMMY_SIG, channelId = 2, 0, "0000", cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
|
||||
val channelUpdate_cd = ChannelUpdate(Router.DUMMY_SIG, channelId = 3, 0, "0000", cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
||||
val channelUpdate_ef = ChannelUpdate(Router.DUMMY_SIG, channelId = 4, 0, "0000", cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
|
||||
|
||||
|
||||
override def withFixture(test: OneArgTest) = {
|
||||
// the network will be a --(1)--> b ---(2)--> c --(3)--> d and e --(4)--> f (we are a)
|
||||
|
||||
within(30 seconds) {
|
||||
// first we set up the router
|
||||
val watcher = TestProbe()
|
||||
val router = system.actorOf(Router.props(watcher.ref, ann_a))
|
||||
// we announce channels
|
||||
router ! chan_ab
|
||||
router ! chan_bc
|
||||
router ! chan_cd
|
||||
router ! chan_ef
|
||||
// then nodes
|
||||
router ! ann_a
|
||||
router ! ann_b
|
||||
router ! ann_c
|
||||
router ! ann_d
|
||||
router ! ann_e
|
||||
router ! ann_f
|
||||
// then channel updates
|
||||
router ! channelUpdate_ab
|
||||
router ! channelUpdate_bc
|
||||
router ! channelUpdate_cd
|
||||
router ! channelUpdate_ef
|
||||
|
||||
val sender = TestProbe()
|
||||
|
||||
sender.send(router, 'nodes)
|
||||
val nodes = sender.expectMsgType[Iterable[NodeAnnouncement]]
|
||||
assert(nodes.size === 6)
|
||||
|
||||
sender.send(router, 'channels)
|
||||
val channels = sender.expectMsgType[Iterable[ChannelAnnouncement]]
|
||||
assert(channels.size === 4)
|
||||
|
||||
sender.send(router, 'updates)
|
||||
val updates = sender.expectMsgType[Iterable[ChannelUpdate]]
|
||||
assert(updates.size === 4)
|
||||
|
||||
test((router, watcher))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.eclair.wire.ChannelUpdate
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Created by PM on 31/05/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class RouteCalculationSpec extends FunSuite {
|
||||
|
||||
val (a, b, c, d, e) = (BinaryData("aa" * 33), BinaryData("bb" * 33), BinaryData("cc" * 33), BinaryData("dd" * 33), BinaryData("ee" * 33))
|
||||
|
||||
test("calculate simple route") {
|
||||
|
||||
val channels = List(
|
||||
ChannelDesc(1L, a, b),
|
||||
ChannelDesc(2L, b, c),
|
||||
ChannelDesc(3L, c, d),
|
||||
ChannelDesc(4L, d, e)
|
||||
)
|
||||
|
||||
val route = Router.findRouteDijkstra(a, e, channels)
|
||||
assert(route.map(_.id) === 1 :: 2 :: 3 :: 4 :: Nil)
|
||||
|
||||
}
|
||||
|
||||
test("route not found") {
|
||||
|
||||
val channels = List(
|
||||
ChannelDesc(1L, a, b),
|
||||
ChannelDesc(2L, b, c),
|
||||
ChannelDesc(4L, d, e)
|
||||
)
|
||||
|
||||
intercept[RuntimeException] {
|
||||
Router.findRouteDijkstra(a, e, channels)
|
||||
}
|
||||
}
|
||||
|
||||
test("route to self") {
|
||||
|
||||
val channels = List(
|
||||
ChannelDesc(1L, a, b),
|
||||
ChannelDesc(2L, b, c),
|
||||
ChannelDesc(3L, c, d),
|
||||
ChannelDesc(4L, d, e)
|
||||
)
|
||||
|
||||
intercept[RuntimeException] {
|
||||
Router.findRouteDijkstra(a, a, channels)
|
||||
}
|
||||
}
|
||||
|
||||
test("route to immediate neighbor") {
|
||||
val channels = List(
|
||||
ChannelDesc(1L, a, b),
|
||||
ChannelDesc(2L, b, c),
|
||||
ChannelDesc(3L, c, d),
|
||||
ChannelDesc(4L, d, e)
|
||||
)
|
||||
|
||||
val route = Router.findRouteDijkstra(a, b, channels)
|
||||
assert(route.map(_.id) === 1 :: Nil)
|
||||
}
|
||||
|
||||
test("directed graph") {
|
||||
val channels = List(
|
||||
ChannelDesc(1L, a, b),
|
||||
ChannelDesc(2L, b, c),
|
||||
ChannelDesc(3L, c, d),
|
||||
ChannelDesc(4L, d, e)
|
||||
)
|
||||
|
||||
// a->e works, e->a fails
|
||||
|
||||
Router.findRouteDijkstra(a, e, channels)
|
||||
|
||||
intercept[RuntimeException] {
|
||||
Router.findRouteDijkstra(e, a, channels)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
test("compute an example sig") {
|
||||
val data = BinaryData("00" * 32)
|
||||
val key = PrivateKey(BinaryData("11" * 32))
|
||||
val sig = Crypto.encodeSignature(Crypto.sign(data, key))
|
||||
assert(Crypto.isDERSignature(sig :+ 1.toByte))
|
||||
}
|
||||
|
||||
test("calculate route and return metadata") {
|
||||
|
||||
val uab = ChannelUpdate(Router.DUMMY_SIG, 1L, 0L, "0000", 1, 42, 2500, 140)
|
||||
val uba = ChannelUpdate(Router.DUMMY_SIG, 1L, 1L, "0001", 1, 43, 2501, 141)
|
||||
val ubc = ChannelUpdate(Router.DUMMY_SIG, 2L, 1L, "0000", 1, 44, 2502, 142)
|
||||
val ucb = ChannelUpdate(Router.DUMMY_SIG, 2L, 1L, "0001", 1, 45, 2503, 143)
|
||||
val ucd = ChannelUpdate(Router.DUMMY_SIG, 3L, 1L, "0000", 1, 46, 2504, 144)
|
||||
val udc = ChannelUpdate(Router.DUMMY_SIG, 3L, 1L, "0001", 1, 47, 2505, 145)
|
||||
val ude = ChannelUpdate(Router.DUMMY_SIG, 4L, 1L, "0000", 1, 48, 2506, 146)
|
||||
val ued = ChannelUpdate(Router.DUMMY_SIG, 4L, 1L, "0001", 1, 49, 2507, 147)
|
||||
|
||||
val updates = Map(
|
||||
ChannelDesc(1L, a, b) -> uab,
|
||||
ChannelDesc(1L, b, a) -> uba,
|
||||
ChannelDesc(2L, b, c) -> ubc,
|
||||
ChannelDesc(2L, c, b) -> ucb,
|
||||
ChannelDesc(3L, c, d) -> ucd,
|
||||
ChannelDesc(3L, d, c) -> udc,
|
||||
ChannelDesc(4L, d, e) -> ude,
|
||||
ChannelDesc(4L, e, d) -> ued
|
||||
)
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val hops = Await.result(Router.findRoute(a, e, updates), 3 seconds)
|
||||
|
||||
assert(hops === Hop(a, b, uab) :: Hop(b, c, ubc) :: Hop(c, d, ucd) :: Hop(d, e, ude) :: Nil)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package fr.acinq.eclair.router
|
||||
|
||||
import akka.actor.Status.Failure
|
||||
import akka.testkit.TestProbe
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
/**
|
||||
* Created by PM on 29/08/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class RouterSpec extends BaseRouterSpec {
|
||||
|
||||
test("route not found (unreachable target)") { case (router, watcher) =>
|
||||
val sender = TestProbe()
|
||||
// no route a->f
|
||||
sender.send(router, RouteRequest(a, f))
|
||||
val res = sender.expectMsgType[Failure]
|
||||
assert(res.cause.getMessage === "route not found")
|
||||
}
|
||||
|
||||
test("route not found (non-existing source)") { case (router, watcher) =>
|
||||
val sender = TestProbe()
|
||||
// no route a->f
|
||||
sender.send(router, RouteRequest(randomPubkey, f))
|
||||
val res = sender.expectMsgType[Failure]
|
||||
assert(res.cause.getMessage === "graph must contain the source vertex")
|
||||
}
|
||||
|
||||
test("route not found (non-existing target)") { case (router, watcher) =>
|
||||
val sender = TestProbe()
|
||||
// no route a->f
|
||||
sender.send(router, RouteRequest(a, randomPubkey))
|
||||
val res = sender.expectMsgType[Failure]
|
||||
assert(res.cause.getMessage === "graph must contain the sink vertex")
|
||||
}
|
||||
|
||||
test("route found") { case (router, watcher) =>
|
||||
val sender = TestProbe()
|
||||
sender.send(router, RouteRequest(a, d))
|
||||
val res = sender.expectMsgType[RouteResponse]
|
||||
assert(res.hops.map(_.nodeId).toList === a.toBin :: b.toBin :: c.toBin :: Nil)
|
||||
assert(res.hops.last.nextNodeId === d.toBin)
|
||||
}
|
||||
|
||||
}
|
|
@ -103,14 +103,14 @@ class ClaimSentHtlcSpec extends FunSuite {
|
|||
assert(e.getMessage === "unsatisfied CSV lock time")
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC if he knows the payment hash") {
|
||||
test("Bob can spend this HTLC if he knows the payment hash") {
|
||||
val sig = Transaction.signInput(tx1, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Alice.R :: redeemScript :: Nil)
|
||||
val tx2 = tx1.updateWitness(0, witness)
|
||||
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||
}
|
||||
|
||||
test("Blob can spend this HTLC if he knows the revocation hash") {
|
||||
test("Bob can spend this HTLC if he knows the revocation hash") {
|
||||
val sig = Transaction.signInput(tx1, 0, redeemScript, SIGHASH_ALL, tx.txOut(0).amount, 1, Bob.finalKey)
|
||||
val witness = ScriptWitness(sig :: Alice.revokeCommit :: redeemScript :: Nil)
|
||||
val tx2 = tx1.updateWitness(0, witness)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package fr.acinq.eclair.wire
|
||||
|
||||
import java.net.InetAddress
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.eclair.wire.Codecs.{ipv6, lightningMessageCodec, rgb, zeropaddedstring}
|
||||
import fr.acinq.eclair.wire.Codecs.{socketaddress, lightningMessageCodec, rgb, zeropaddedstring}
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
@ -47,20 +47,22 @@ class CodecsSpec extends FunSuite {
|
|||
assert(color === color2)
|
||||
}
|
||||
|
||||
test("encode/decode with ipv6 codec") {
|
||||
test("encode/decode with socketaddress codec") {
|
||||
{
|
||||
val ipv4addr = InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte))
|
||||
val bin = ipv6.encode(ipv4addr).toOption.get
|
||||
assert(bin === hex"00 00 00 00 00 00 00 00 00 00 FF FF C0 A8 01 2A".toBitVector)
|
||||
val ipv4addr2 = ipv6.decode(bin).toOption.get.value
|
||||
assert(ipv4addr === ipv4addr2)
|
||||
val isa = new InetSocketAddress(ipv4addr, 4231)
|
||||
val bin = socketaddress.encode(isa).toOption.get
|
||||
assert(bin === hex"01 C0 A8 01 2A 10 87".toBitVector)
|
||||
val isa2 = socketaddress.decode(bin).toOption.get.value
|
||||
assert(isa === isa2)
|
||||
}
|
||||
{
|
||||
val ipv6addr = InetAddress.getByAddress(hex"2001 0db8 0000 85a3 0000 0000 ac1f 8001".toArray)
|
||||
val bin = ipv6.encode(ipv6addr).toOption.get
|
||||
assert(bin === hex"2001 0db8 0000 85a3 0000 0000 ac1f 8001".toBitVector)
|
||||
val ipv6addr2 = ipv6.decode(bin).toOption.get.value
|
||||
assert(ipv6addr === ipv6addr2)
|
||||
val isa = new InetSocketAddress(ipv6addr, 4231)
|
||||
val bin = socketaddress.encode(isa).toOption.get
|
||||
assert(bin === hex"02 2001 0db8 0000 85a3 0000 0000 ac1f 8001 1087".toBitVector)
|
||||
val isa2 = socketaddress.decode(bin).toOption.get.value
|
||||
assert(isa === isa2)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +151,7 @@ class CodecsSpec extends FunSuite {
|
|||
val commit_sig = CommitSig(1, randomSignature, randomSignature :: randomSignature :: randomSignature :: Nil)
|
||||
val revoke_and_ack = RevokeAndAck(1, scalar(0), point(1), randomSignature :: randomSignature :: randomSignature :: randomSignature :: randomSignature :: Nil)
|
||||
val channel_announcement = ChannelAnnouncement(randomSignature, randomSignature, 1, randomSignature, randomSignature, bin(33, 5), bin(33, 6), bin(33, 7), bin(33, 8))
|
||||
val node_announcement = NodeAnnouncement(randomSignature, 1, InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)), 2, bin(33, 2), (100.toByte, 200.toByte, 300.toByte), "node-alias")
|
||||
val node_announcement = NodeAnnouncement(randomSignature, 1, bin(33, 2), (100.toByte, 200.toByte, 300.toByte), "node-alias", bin(0, 0), new InetSocketAddress(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)), 42000) :: Nil)
|
||||
val channel_update = ChannelUpdate(randomSignature, 1, 2, bin(2, 2), 3, 4, 5, 6)
|
||||
|
||||
val msgs: List[LightningMessage] =
|
||||
|
@ -165,4 +167,12 @@ class CodecsSpec extends FunSuite {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("encode/decode per-hop payload") {
|
||||
val payload = PerHopPayload(amt_to_forward = 142000, outgoing_cltv_value = 500000)
|
||||
val bin = Codecs.perHopPayloadCodec.encode(payload).toOption.get
|
||||
assert(bin.toByteVector.size === 20)
|
||||
val payload1 = Codecs.perHopPayloadCodec.decode(bin).toOption.get.value
|
||||
assert(payload === payload1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>fr.acinq.eclair</groupId>
|
||||
<artifactId>eclair_2.11</artifactId>
|
||||
<version>0.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>lightning-types_2.11</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.googlecode.maven-download-plugin</groupId>
|
||||
<artifactId>download-maven-plugin</artifactId>
|
||||
<version>1.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>download-scalapbc</id>
|
||||
<phase>initialize</phase>
|
||||
<goals>
|
||||
<goal>wget</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<url>https://github.com/trueaccord/ScalaPB/releases/download/v${scalapb.version}/scalapbc-${scalapb.version}.zip</url>
|
||||
<md5>9fef6a23b9e717c14e1bb3924ac0e156</md5>
|
||||
<unpack>true</unpack>
|
||||
<outputDirectory>${project.build.directory}</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>mkdir-generated-sources</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<target>
|
||||
<mkdir dir="${project.build.directory}/generated-sources/scala"/>
|
||||
</target>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>exec</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<executable>${project.build.directory}/scalapbc-${scalapb.version}/bin/scalapbc${script.extension}</executable>
|
||||
<commandlineArgs>--proto_path=${project.basedir}/src/main/protobuf --scala_out=${project.build.directory}/generated-sources/scala ${project.basedir}/src/main/protobuf/lightning.proto</commandlineArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>${project.build.directory}/generated-sources/scala</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.trueaccord.scalapb</groupId>
|
||||
<artifactId>scalapb-runtime_${scala.version.short}</artifactId>
|
||||
<version>${scalapb.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>default</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<script.extension></script.extension>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>Windows</id>
|
||||
<activation>
|
||||
<os>
|
||||
<family>Windows</family>
|
||||
</os>
|
||||
</activation>
|
||||
<properties>
|
||||
<script.extension>.bat</script.extension>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
|
@ -1,147 +0,0 @@
|
|||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Author: kenton@google.com (Kenton Varda)
|
||||
//
|
||||
// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to
|
||||
// change.
|
||||
//
|
||||
// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is
|
||||
// just a program that reads a CodeGeneratorRequest from stdin and writes a
|
||||
// CodeGeneratorResponse to stdout.
|
||||
//
|
||||
// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
|
||||
// of dealing with the raw protocol defined here.
|
||||
//
|
||||
// A plugin executable needs only to be placed somewhere in the path. The
|
||||
// plugin should be named "protoc-gen-$NAME", and will then be used when the
|
||||
// flag "--${NAME}_out" is passed to protoc.
|
||||
|
||||
package google.protobuf.compiler;
|
||||
option java_package = "com.google.protobuf.compiler";
|
||||
option java_outer_classname = "PluginProtos";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
// An encoded CodeGeneratorRequest is written to the plugin's stdin.
|
||||
message CodeGeneratorRequest {
|
||||
// The .proto files that were explicitly listed on the command-line. The
|
||||
// code generator should generate code only for these files. Each file's
|
||||
// descriptor will be included in proto_file, below.
|
||||
repeated string file_to_generate = 1;
|
||||
|
||||
// The generator parameter passed on the command-line.
|
||||
optional string parameter = 2;
|
||||
|
||||
// FileDescriptorProtos for all files in files_to_generate and everything
|
||||
// they import. The files will appear in topological order, so each file
|
||||
// appears before any file that imports it.
|
||||
//
|
||||
// protoc guarantees that all proto_files will be written after
|
||||
// the fields above, even though this is not technically guaranteed by the
|
||||
// protobuf wire format. This theoretically could allow a plugin to stream
|
||||
// in the FileDescriptorProtos and handle them one by one rather than read
|
||||
// the entire set into memory at once. However, as of this writing, this
|
||||
// is not similarly optimized on protoc's end -- it will store all fields in
|
||||
// memory at once before sending them to the plugin.
|
||||
repeated FileDescriptorProto proto_file = 15;
|
||||
}
|
||||
|
||||
// The plugin writes an encoded CodeGeneratorResponse to stdout.
|
||||
message CodeGeneratorResponse {
|
||||
// Error message. If non-empty, code generation failed. The plugin process
|
||||
// should exit with status code zero even if it reports an error in this way.
|
||||
//
|
||||
// This should be used to indicate errors in .proto files which prevent the
|
||||
// code generator from generating correct code. Errors which indicate a
|
||||
// problem in protoc itself -- such as the input CodeGeneratorRequest being
|
||||
// unparseable -- should be reported by writing a message to stderr and
|
||||
// exiting with a non-zero status code.
|
||||
optional string error = 1;
|
||||
|
||||
// Represents a single generated file.
|
||||
message File {
|
||||
// The file name, relative to the output directory. The name must not
|
||||
// contain "." or ".." components and must be relative, not be absolute (so,
|
||||
// the file cannot lie outside the output directory). "/" must be used as
|
||||
// the path separator, not "\".
|
||||
//
|
||||
// If the name is omitted, the content will be appended to the previous
|
||||
// file. This allows the generator to break large files into small chunks,
|
||||
// and allows the generated text to be streamed back to protoc so that large
|
||||
// files need not reside completely in memory at one time. Note that as of
|
||||
// this writing protoc does not optimize for this -- it will read the entire
|
||||
// CodeGeneratorResponse before writing files to disk.
|
||||
optional string name = 1;
|
||||
|
||||
// If non-empty, indicates that the named file should already exist, and the
|
||||
// content here is to be inserted into that file at a defined insertion
|
||||
// point. This feature allows a code generator to extend the output
|
||||
// produced by another code generator. The original generator may provide
|
||||
// insertion points by placing special annotations in the file that look
|
||||
// like:
|
||||
// @@protoc_insertion_point(NAME)
|
||||
// The annotation can have arbitrary text before and after it on the line,
|
||||
// which allows it to be placed in a comment. NAME should be replaced with
|
||||
// an identifier naming the point -- this is what other generators will use
|
||||
// as the insertion_point. Code inserted at this point will be placed
|
||||
// immediately above the line containing the insertion point (thus multiple
|
||||
// insertions to the same point will come out in the order they were added).
|
||||
// The double-@ is intended to make it unlikely that the generated code
|
||||
// could contain things that look like insertion points by accident.
|
||||
//
|
||||
// For example, the C++ code generator places the following line in the
|
||||
// .pb.h files that it generates:
|
||||
// // @@protoc_insertion_point(namespace_scope)
|
||||
// This line appears within the scope of the file's package namespace, but
|
||||
// outside of any particular class. Another plugin can then specify the
|
||||
// insertion_point "namespace_scope" to generate additional classes or
|
||||
// other declarations that should be placed in this scope.
|
||||
//
|
||||
// Note that if the line containing the insertion point begins with
|
||||
// whitespace, the same whitespace will be added to every line of the
|
||||
// inserted text. This is useful for languages like Python, where
|
||||
// indentation matters. In these languages, the insertion point comment
|
||||
// should be indented the same amount as any inserted code will need to be
|
||||
// in order to work correctly in that context.
|
||||
//
|
||||
// The code generator that generates the initial file and the one which
|
||||
// inserts into it must both run as part of a single invocation of protoc.
|
||||
// Code generators are executed in the order in which they appear on the
|
||||
// command line.
|
||||
//
|
||||
// If |insertion_point| is present, |name| must also be present.
|
||||
optional string insertion_point = 2;
|
||||
|
||||
// The file contents.
|
||||
optional string content = 15;
|
||||
}
|
||||
repeated File file = 15;
|
||||
}
|
|
@ -1,687 +0,0 @@
|
|||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Author: kenton@google.com (Kenton Varda)
|
||||
// Based on original Protocol Buffers design by
|
||||
// Sanjay Ghemawat, Jeff Dean, and others.
|
||||
//
|
||||
// The messages in this file describe the definitions found in .proto files.
|
||||
// A valid .proto file can be translated directly to a FileDescriptorProto
|
||||
// without any other information (e.g. without reading its imports).
|
||||
|
||||
|
||||
|
||||
package google.protobuf;
|
||||
option java_package = "com.google.protobuf";
|
||||
option java_outer_classname = "DescriptorProtos";
|
||||
|
||||
// descriptor.proto must be optimized for speed because reflection-based
|
||||
// algorithms don't work during bootstrapping.
|
||||
option optimize_for = SPEED;
|
||||
|
||||
// The protocol compiler can output a FileDescriptorSet containing the .proto
|
||||
// files it parses.
|
||||
message FileDescriptorSet {
|
||||
repeated FileDescriptorProto file = 1;
|
||||
}
|
||||
|
||||
// Describes a complete .proto file.
|
||||
message FileDescriptorProto {
|
||||
optional string name = 1; // file name, relative to root of source tree
|
||||
optional string package = 2; // e.g. "foo", "foo.bar", etc.
|
||||
|
||||
// Names of files imported by this file.
|
||||
repeated string dependency = 3;
|
||||
// Indexes of the public imported files in the dependency list above.
|
||||
repeated int32 public_dependency = 10;
|
||||
// Indexes of the weak imported files in the dependency list.
|
||||
// For Google-internal migration only. Do not use.
|
||||
repeated int32 weak_dependency = 11;
|
||||
|
||||
// All top-level definitions in this file.
|
||||
repeated DescriptorProto message_type = 4;
|
||||
repeated EnumDescriptorProto enum_type = 5;
|
||||
repeated ServiceDescriptorProto service = 6;
|
||||
repeated FieldDescriptorProto extension = 7;
|
||||
|
||||
optional FileOptions options = 8;
|
||||
|
||||
// This field contains optional information about the original source code.
|
||||
// You may safely remove this entire field whithout harming runtime
|
||||
// functionality of the descriptors -- the information is needed only by
|
||||
// development tools.
|
||||
optional SourceCodeInfo source_code_info = 9;
|
||||
}
|
||||
|
||||
// Describes a message type.
|
||||
message DescriptorProto {
|
||||
optional string name = 1;
|
||||
|
||||
repeated FieldDescriptorProto field = 2;
|
||||
repeated FieldDescriptorProto extension = 6;
|
||||
|
||||
repeated DescriptorProto nested_type = 3;
|
||||
repeated EnumDescriptorProto enum_type = 4;
|
||||
|
||||
message ExtensionRange {
|
||||
optional int32 start = 1;
|
||||
optional int32 end = 2;
|
||||
}
|
||||
repeated ExtensionRange extension_range = 5;
|
||||
|
||||
repeated OneofDescriptorProto oneof_decl = 8;
|
||||
|
||||
optional MessageOptions options = 7;
|
||||
}
|
||||
|
||||
// Describes a field within a message.
|
||||
message FieldDescriptorProto {
|
||||
enum Type {
|
||||
// 0 is reserved for errors.
|
||||
// Order is weird for historical reasons.
|
||||
TYPE_DOUBLE = 1;
|
||||
TYPE_FLOAT = 2;
|
||||
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if
|
||||
// negative values are likely.
|
||||
TYPE_INT64 = 3;
|
||||
TYPE_UINT64 = 4;
|
||||
// Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if
|
||||
// negative values are likely.
|
||||
TYPE_INT32 = 5;
|
||||
TYPE_FIXED64 = 6;
|
||||
TYPE_FIXED32 = 7;
|
||||
TYPE_BOOL = 8;
|
||||
TYPE_STRING = 9;
|
||||
TYPE_GROUP = 10; // Tag-delimited aggregate.
|
||||
TYPE_MESSAGE = 11; // Length-delimited aggregate.
|
||||
|
||||
// New in version 2.
|
||||
TYPE_BYTES = 12;
|
||||
TYPE_UINT32 = 13;
|
||||
TYPE_ENUM = 14;
|
||||
TYPE_SFIXED32 = 15;
|
||||
TYPE_SFIXED64 = 16;
|
||||
TYPE_SINT32 = 17; // Uses ZigZag encoding.
|
||||
TYPE_SINT64 = 18; // Uses ZigZag encoding.
|
||||
};
|
||||
|
||||
enum Label {
|
||||
// 0 is reserved for errors
|
||||
LABEL_OPTIONAL = 1;
|
||||
LABEL_REQUIRED = 2;
|
||||
LABEL_REPEATED = 3;
|
||||
// TODO(sanjay): Should we add LABEL_MAP?
|
||||
};
|
||||
|
||||
optional string name = 1;
|
||||
optional int32 number = 3;
|
||||
optional Label label = 4;
|
||||
|
||||
// If type_name is set, this need not be set. If both this and type_name
|
||||
// are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
|
||||
optional Type type = 5;
|
||||
|
||||
// For message and enum types, this is the name of the type. If the name
|
||||
// starts with a '.', it is fully-qualified. Otherwise, C++-like scoping
|
||||
// rules are used to find the type (i.e. first the nested types within this
|
||||
// message are searched, then within the parent, on up to the root
|
||||
// namespace).
|
||||
optional string type_name = 6;
|
||||
|
||||
// For extensions, this is the name of the type being extended. It is
|
||||
// resolved in the same manner as type_name.
|
||||
optional string extendee = 2;
|
||||
|
||||
// For numeric types, contains the original text representation of the value.
|
||||
// For booleans, "true" or "false".
|
||||
// For strings, contains the default text contents (not escaped in any way).
|
||||
// For bytes, contains the C escaped value. All bytes >= 128 are escaped.
|
||||
// TODO(kenton): Base-64 encode?
|
||||
optional string default_value = 7;
|
||||
|
||||
// If set, gives the index of a oneof in the containing type's oneof_decl
|
||||
// list. This field is a member of that oneof. Extensions of a oneof should
|
||||
// not set this since the oneof to which they belong will be inferred based
|
||||
// on the extension range containing the extension's field number.
|
||||
optional int32 oneof_index = 9;
|
||||
|
||||
optional FieldOptions options = 8;
|
||||
}
|
||||
|
||||
// Describes a oneof.
|
||||
message OneofDescriptorProto {
|
||||
optional string name = 1;
|
||||
}
|
||||
|
||||
// Describes an enum type.
|
||||
message EnumDescriptorProto {
|
||||
optional string name = 1;
|
||||
|
||||
repeated EnumValueDescriptorProto value = 2;
|
||||
|
||||
optional EnumOptions options = 3;
|
||||
}
|
||||
|
||||
// Describes a value within an enum.
|
||||
message EnumValueDescriptorProto {
|
||||
optional string name = 1;
|
||||
optional int32 number = 2;
|
||||
|
||||
optional EnumValueOptions options = 3;
|
||||
}
|
||||
|
||||
// Describes a service.
|
||||
message ServiceDescriptorProto {
|
||||
optional string name = 1;
|
||||
repeated MethodDescriptorProto method = 2;
|
||||
|
||||
optional ServiceOptions options = 3;
|
||||
}
|
||||
|
||||
// Describes a method of a service.
|
||||
message MethodDescriptorProto {
|
||||
optional string name = 1;
|
||||
|
||||
// Input and output type names. These are resolved in the same way as
|
||||
// FieldDescriptorProto.type_name, but must refer to a message type.
|
||||
optional string input_type = 2;
|
||||
optional string output_type = 3;
|
||||
|
||||
optional MethodOptions options = 4;
|
||||
}
|
||||
|
||||
|
||||
// ===================================================================
|
||||
// Options
|
||||
|
||||
// Each of the definitions above may have "options" attached. These are
|
||||
// just annotations which may cause code to be generated slightly differently
|
||||
// or may contain hints for code that manipulates protocol messages.
|
||||
//
|
||||
// Clients may define custom options as extensions of the *Options messages.
|
||||
// These extensions may not yet be known at parsing time, so the parser cannot
|
||||
// store the values in them. Instead it stores them in a field in the *Options
|
||||
// message called uninterpreted_option. This field must have the same name
|
||||
// across all *Options messages. We then use this field to populate the
|
||||
// extensions when we build a descriptor, at which point all protos have been
|
||||
// parsed and so all extensions are known.
|
||||
//
|
||||
// Extension numbers for custom options may be chosen as follows:
|
||||
// * For options which will only be used within a single application or
|
||||
// organization, or for experimental options, use field numbers 50000
|
||||
// through 99999. It is up to you to ensure that you do not use the
|
||||
// same number for multiple options.
|
||||
// * For options which will be published and used publicly by multiple
|
||||
// independent entities, e-mail protobuf-global-extension-registry@google.com
|
||||
// to reserve extension numbers. Simply provide your project name (e.g.
|
||||
// Object-C plugin) and your porject website (if available) -- there's no need
|
||||
// to explain how you intend to use them. Usually you only need one extension
|
||||
// number. You can declare multiple options with only one extension number by
|
||||
// putting them in a sub-message. See the Custom Options section of the docs
|
||||
// for examples:
|
||||
// https://developers.google.com/protocol-buffers/docs/proto#options
|
||||
// If this turns out to be popular, a web service will be set up
|
||||
// to automatically assign option numbers.
|
||||
|
||||
|
||||
message FileOptions {
|
||||
|
||||
// Sets the Java package where classes generated from this .proto will be
|
||||
// placed. By default, the proto package is used, but this is often
|
||||
// inappropriate because proto packages do not normally start with backwards
|
||||
// domain names.
|
||||
optional string java_package = 1;
|
||||
|
||||
|
||||
// If set, all the classes from the .proto file are wrapped in a single
|
||||
// outer class with the given name. This applies to both Proto1
|
||||
// (equivalent to the old "--one_java_file" option) and Proto2 (where
|
||||
// a .proto always translates to a single class, but you may want to
|
||||
// explicitly choose the class name).
|
||||
optional string java_outer_classname = 8;
|
||||
|
||||
// If set true, then the Java code generator will generate a separate .java
|
||||
// file for each top-level message, enum, and service defined in the .proto
|
||||
// file. Thus, these types will *not* be nested inside the outer class
|
||||
// named by java_outer_classname. However, the outer class will still be
|
||||
// generated to contain the file's getDescriptor() method as well as any
|
||||
// top-level extensions defined in the file.
|
||||
optional bool java_multiple_files = 10 [default=false];
|
||||
|
||||
// If set true, then the Java code generator will generate equals() and
|
||||
// hashCode() methods for all messages defined in the .proto file.
|
||||
// - In the full runtime, this is purely a speed optimization, as the
|
||||
// AbstractMessage base class includes reflection-based implementations of
|
||||
// these methods.
|
||||
//- In the lite runtime, setting this option changes the semantics of
|
||||
// equals() and hashCode() to more closely match those of the full runtime;
|
||||
// the generated methods compute their results based on field values rather
|
||||
// than object identity. (Implementations should not assume that hashcodes
|
||||
// will be consistent across runtimes or versions of the protocol compiler.)
|
||||
optional bool java_generate_equals_and_hash = 20 [default=false];
|
||||
|
||||
// If set true, then the Java2 code generator will generate code that
|
||||
// throws an exception whenever an attempt is made to assign a non-UTF-8
|
||||
// byte sequence to a string field.
|
||||
// Message reflection will do the same.
|
||||
// However, an extension field still accepts non-UTF-8 byte sequences.
|
||||
// This option has no effect on when used with the lite runtime.
|
||||
optional bool java_string_check_utf8 = 27 [default=false];
|
||||
|
||||
|
||||
// Generated classes can be optimized for speed or code size.
|
||||
enum OptimizeMode {
|
||||
SPEED = 1; // Generate complete code for parsing, serialization,
|
||||
// etc.
|
||||
CODE_SIZE = 2; // Use ReflectionOps to implement these methods.
|
||||
LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime.
|
||||
}
|
||||
optional OptimizeMode optimize_for = 9 [default=SPEED];
|
||||
|
||||
// Sets the Go package where structs generated from this .proto will be
|
||||
// placed. There is no default.
|
||||
optional string go_package = 11;
|
||||
|
||||
|
||||
|
||||
// Should generic services be generated in each language? "Generic" services
|
||||
// are not specific to any particular RPC system. They are generated by the
|
||||
// main code generators in each language (without additional plugins).
|
||||
// Generic services were the only kind of service generation supported by
|
||||
// early versions of proto2.
|
||||
//
|
||||
// Generic services are now considered deprecated in favor of using plugins
|
||||
// that generate code specific to your particular RPC system. Therefore,
|
||||
// these default to false. Old code which depends on generic services should
|
||||
// explicitly set them to true.
|
||||
optional bool cc_generic_services = 16 [default=false];
|
||||
optional bool java_generic_services = 17 [default=false];
|
||||
optional bool py_generic_services = 18 [default=false];
|
||||
|
||||
// Is this file deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for everything in the file, or it will be completely ignored; in the very
|
||||
// least, this is a formalization for deprecating files.
|
||||
optional bool deprecated = 23 [default=false];
|
||||
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message MessageOptions {
|
||||
// Set true to use the old proto1 MessageSet wire format for extensions.
|
||||
// This is provided for backwards-compatibility with the MessageSet wire
|
||||
// format. You should not use this for any other reason: It's less
|
||||
// efficient, has fewer features, and is more complicated.
|
||||
//
|
||||
// The message must be defined exactly as follows:
|
||||
// message Foo {
|
||||
// option message_set_wire_format = true;
|
||||
// extensions 4 to max;
|
||||
// }
|
||||
// Note that the message cannot have any defined fields; MessageSets only
|
||||
// have extensions.
|
||||
//
|
||||
// All extensions of your type must be singular messages; e.g. they cannot
|
||||
// be int32s, enums, or repeated messages.
|
||||
//
|
||||
// Because this is an option, the above two restrictions are not enforced by
|
||||
// the protocol compiler.
|
||||
optional bool message_set_wire_format = 1 [default=false];
|
||||
|
||||
// Disables the generation of the standard "descriptor()" accessor, which can
|
||||
// conflict with a field of the same name. This is meant to make migration
|
||||
// from proto1 easier; new code should avoid fields named "descriptor".
|
||||
optional bool no_standard_descriptor_accessor = 2 [default=false];
|
||||
|
||||
// Is this message deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for the message, or it will be completely ignored; in the very least,
|
||||
// this is a formalization for deprecating messages.
|
||||
optional bool deprecated = 3 [default=false];
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message FieldOptions {
|
||||
// The ctype option instructs the C++ code generator to use a different
|
||||
// representation of the field than it normally would. See the specific
|
||||
// options below. This option is not yet implemented in the open source
|
||||
// release -- sorry, we'll try to include it in a future version!
|
||||
optional CType ctype = 1 [default = STRING];
|
||||
enum CType {
|
||||
// Default mode.
|
||||
STRING = 0;
|
||||
|
||||
CORD = 1;
|
||||
|
||||
STRING_PIECE = 2;
|
||||
}
|
||||
// The packed option can be enabled for repeated primitive fields to enable
|
||||
// a more efficient representation on the wire. Rather than repeatedly
|
||||
// writing the tag and type for each element, the entire array is encoded as
|
||||
// a single length-delimited blob.
|
||||
optional bool packed = 2;
|
||||
|
||||
|
||||
|
||||
// Should this field be parsed lazily? Lazy applies only to message-type
|
||||
// fields. It means that when the outer message is initially parsed, the
|
||||
// inner message's contents will not be parsed but instead stored in encoded
|
||||
// form. The inner message will actually be parsed when it is first accessed.
|
||||
//
|
||||
// This is only a hint. Implementations are free to choose whether to use
|
||||
// eager or lazy parsing regardless of the value of this option. However,
|
||||
// setting this option true suggests that the protocol author believes that
|
||||
// using lazy parsing on this field is worth the additional bookkeeping
|
||||
// overhead typically needed to implement it.
|
||||
//
|
||||
// This option does not affect the public interface of any generated code;
|
||||
// all method signatures remain the same. Furthermore, thread-safety of the
|
||||
// interface is not affected by this option; const methods remain safe to
|
||||
// call from multiple threads concurrently, while non-const methods continue
|
||||
// to require exclusive access.
|
||||
//
|
||||
//
|
||||
// Note that implementations may choose not to check required fields within
|
||||
// a lazy sub-message. That is, calling IsInitialized() on the outher message
|
||||
// may return true even if the inner message has missing required fields.
|
||||
// This is necessary because otherwise the inner message would have to be
|
||||
// parsed in order to perform the check, defeating the purpose of lazy
|
||||
// parsing. An implementation which chooses not to check required fields
|
||||
// must be consistent about it. That is, for any particular sub-message, the
|
||||
// implementation must either *always* check its required fields, or *never*
|
||||
// check its required fields, regardless of whether or not the message has
|
||||
// been parsed.
|
||||
optional bool lazy = 5 [default=false];
|
||||
|
||||
// Is this field deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for accessors, or it will be completely ignored; in the very least, this
|
||||
// is a formalization for deprecating fields.
|
||||
optional bool deprecated = 3 [default=false];
|
||||
|
||||
// EXPERIMENTAL. DO NOT USE.
|
||||
// For "map" fields, the name of the field in the enclosed type that
|
||||
// is the key for this map. For example, suppose we have:
|
||||
// message Item {
|
||||
// required string name = 1;
|
||||
// required string value = 2;
|
||||
// }
|
||||
// message Config {
|
||||
// repeated Item items = 1 [experimental_map_key="name"];
|
||||
// }
|
||||
// In this situation, the map key for Item will be set to "name".
|
||||
// TODO: Fully-implement this, then remove the "experimental_" prefix.
|
||||
optional string experimental_map_key = 9;
|
||||
|
||||
// For Google-internal migration only. Do not use.
|
||||
optional bool weak = 10 [default=false];
|
||||
|
||||
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message EnumOptions {
|
||||
|
||||
// Set this option to true to allow mapping different tag names to the same
|
||||
// value.
|
||||
optional bool allow_alias = 2;
|
||||
|
||||
// Is this enum deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for the enum, or it will be completely ignored; in the very least, this
|
||||
// is a formalization for deprecating enums.
|
||||
optional bool deprecated = 3 [default=false];
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message EnumValueOptions {
|
||||
// Is this enum value deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for the enum value, or it will be completely ignored; in the very least,
|
||||
// this is a formalization for deprecating enum values.
|
||||
optional bool deprecated = 1 [default=false];
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message ServiceOptions {
|
||||
|
||||
// Note: Field numbers 1 through 32 are reserved for Google's internal RPC
|
||||
// framework. We apologize for hoarding these numbers to ourselves, but
|
||||
// we were already using them long before we decided to release Protocol
|
||||
// Buffers.
|
||||
|
||||
// Is this service deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for the service, or it will be completely ignored; in the very least,
|
||||
// this is a formalization for deprecating services.
|
||||
optional bool deprecated = 33 [default=false];
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
message MethodOptions {
|
||||
|
||||
// Note: Field numbers 1 through 32 are reserved for Google's internal RPC
|
||||
// framework. We apologize for hoarding these numbers to ourselves, but
|
||||
// we were already using them long before we decided to release Protocol
|
||||
// Buffers.
|
||||
|
||||
// Is this method deprecated?
|
||||
// Depending on the target platform, this can emit Deprecated annotations
|
||||
// for the method, or it will be completely ignored; in the very least,
|
||||
// this is a formalization for deprecating methods.
|
||||
optional bool deprecated = 33 [default=false];
|
||||
|
||||
// The parser stores options it doesn't recognize here. See above.
|
||||
repeated UninterpretedOption uninterpreted_option = 999;
|
||||
|
||||
// Clients can define custom options in extensions of this message. See above.
|
||||
extensions 1000 to max;
|
||||
}
|
||||
|
||||
|
||||
// A message representing a option the parser does not recognize. This only
|
||||
// appears in options protos created by the compiler::Parser class.
|
||||
// DescriptorPool resolves these when building Descriptor objects. Therefore,
|
||||
// options protos in descriptor objects (e.g. returned by Descriptor::options(),
|
||||
// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions
|
||||
// in them.
|
||||
message UninterpretedOption {
|
||||
// The name of the uninterpreted option. Each string represents a segment in
|
||||
// a dot-separated name. is_extension is true iff a segment represents an
|
||||
// extension (denoted with parentheses in options specs in .proto files).
|
||||
// E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents
|
||||
// "foo.(bar.baz).qux".
|
||||
message NamePart {
|
||||
required string name_part = 1;
|
||||
required bool is_extension = 2;
|
||||
}
|
||||
repeated NamePart name = 2;
|
||||
|
||||
// The value of the uninterpreted option, in whatever type the tokenizer
|
||||
// identified it as during parsing. Exactly one of these should be set.
|
||||
optional string identifier_value = 3;
|
||||
optional uint64 positive_int_value = 4;
|
||||
optional int64 negative_int_value = 5;
|
||||
optional double double_value = 6;
|
||||
optional bytes string_value = 7;
|
||||
optional string aggregate_value = 8;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Optional source code info
|
||||
|
||||
// Encapsulates information about the original source file from which a
|
||||
// FileDescriptorProto was generated.
|
||||
message SourceCodeInfo {
|
||||
// A Location identifies a piece of source code in a .proto file which
|
||||
// corresponds to a particular definition. This information is intended
|
||||
// to be useful to IDEs, code indexers, documentation generators, and similar
|
||||
// tools.
|
||||
//
|
||||
// For example, say we have a file like:
|
||||
// message Foo {
|
||||
// optional string foo = 1;
|
||||
// }
|
||||
// Let's look at just the field definition:
|
||||
// optional string foo = 1;
|
||||
// ^ ^^ ^^ ^ ^^^
|
||||
// a bc de f ghi
|
||||
// We have the following locations:
|
||||
// span path represents
|
||||
// [a,i) [ 4, 0, 2, 0 ] The whole field definition.
|
||||
// [a,b) [ 4, 0, 2, 0, 4 ] The label (optional).
|
||||
// [c,d) [ 4, 0, 2, 0, 5 ] The type (string).
|
||||
// [e,f) [ 4, 0, 2, 0, 1 ] The name (foo).
|
||||
// [g,h) [ 4, 0, 2, 0, 3 ] The number (1).
|
||||
//
|
||||
// Notes:
|
||||
// - A location may refer to a repeated field itself (i.e. not to any
|
||||
// particular index within it). This is used whenever a set of elements are
|
||||
// logically enclosed in a single code segment. For example, an entire
|
||||
// extend block (possibly containing multiple extension definitions) will
|
||||
// have an outer location whose path refers to the "extensions" repeated
|
||||
// field without an index.
|
||||
// - Multiple locations may have the same path. This happens when a single
|
||||
// logical declaration is spread out across multiple places. The most
|
||||
// obvious example is the "extend" block again -- there may be multiple
|
||||
// extend blocks in the same scope, each of which will have the same path.
|
||||
// - A location's span is not always a subset of its parent's span. For
|
||||
// example, the "extendee" of an extension declaration appears at the
|
||||
// beginning of the "extend" block and is shared by all extensions within
|
||||
// the block.
|
||||
// - Just because a location's span is a subset of some other location's span
|
||||
// does not mean that it is a descendent. For example, a "group" defines
|
||||
// both a type and a field in a single declaration. Thus, the locations
|
||||
// corresponding to the type and field and their components will overlap.
|
||||
// - Code which tries to interpret locations should probably be designed to
|
||||
// ignore those that it doesn't understand, as more types of locations could
|
||||
// be recorded in the future.
|
||||
repeated Location location = 1;
|
||||
message Location {
|
||||
// Identifies which part of the FileDescriptorProto was defined at this
|
||||
// location.
|
||||
//
|
||||
// Each element is a field number or an index. They form a path from
|
||||
// the root FileDescriptorProto to the place where the definition. For
|
||||
// example, this path:
|
||||
// [ 4, 3, 2, 7, 1 ]
|
||||
// refers to:
|
||||
// file.message_type(3) // 4, 3
|
||||
// .field(7) // 2, 7
|
||||
// .name() // 1
|
||||
// This is because FileDescriptorProto.message_type has field number 4:
|
||||
// repeated DescriptorProto message_type = 4;
|
||||
// and DescriptorProto.field has field number 2:
|
||||
// repeated FieldDescriptorProto field = 2;
|
||||
// and FieldDescriptorProto.name has field number 1:
|
||||
// optional string name = 1;
|
||||
//
|
||||
// Thus, the above path gives the location of a field name. If we removed
|
||||
// the last element:
|
||||
// [ 4, 3, 2, 7 ]
|
||||
// this path refers to the whole field declaration (from the beginning
|
||||
// of the label to the terminating semicolon).
|
||||
repeated int32 path = 1 [packed=true];
|
||||
|
||||
// Always has exactly three or four elements: start line, start column,
|
||||
// end line (optional, otherwise assumed same as start line), end column.
|
||||
// These are packed into a single field for efficiency. Note that line
|
||||
// and column numbers are zero-based -- typically you will want to add
|
||||
// 1 to each before displaying to a user.
|
||||
repeated int32 span = 2 [packed=true];
|
||||
|
||||
// If this SourceCodeInfo represents a complete declaration, these are any
|
||||
// comments appearing before and after the declaration which appear to be
|
||||
// attached to the declaration.
|
||||
//
|
||||
// A series of line comments appearing on consecutive lines, with no other
|
||||
// tokens appearing on those lines, will be treated as a single comment.
|
||||
//
|
||||
// Only the comment content is provided; comment markers (e.g. //) are
|
||||
// stripped out. For block comments, leading whitespace and an asterisk
|
||||
// will be stripped from the beginning of each line other than the first.
|
||||
// Newlines are included in the output.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// optional int32 foo = 1; // Comment attached to foo.
|
||||
// // Comment attached to bar.
|
||||
// optional int32 bar = 2;
|
||||
//
|
||||
// optional string baz = 3;
|
||||
// // Comment attached to baz.
|
||||
// // Another line attached to baz.
|
||||
//
|
||||
// // Comment attached to qux.
|
||||
// //
|
||||
// // Another line attached to qux.
|
||||
// optional double qux = 4;
|
||||
//
|
||||
// optional string corge = 5;
|
||||
// /* Block comment attached
|
||||
// * to corge. Leading asterisks
|
||||
// * will be removed. */
|
||||
// /* Block comment attached to
|
||||
// * grault. */
|
||||
// optional int32 grault = 6;
|
||||
optional string leading_comments = 3;
|
||||
optional string trailing_comments = 4;
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
syntax = "proto2";
|
||||
|
||||
import "scalapb/scalapb.proto";
|
||||
|
||||
// The outer layer handles encryption, authentication and message
|
||||
// boundaries.
|
||||
|
||||
//
|
||||
// Helper Types
|
||||
//
|
||||
|
||||
// Protobufs don't have fixed-length fields, so these are a hack.
|
||||
message signature {
|
||||
option (scalapb.message).extends = "lightning.SignatureToString";
|
||||
required fixed64 r1 = 1;
|
||||
required fixed64 r2 = 2;
|
||||
required fixed64 r3 = 3;
|
||||
required fixed64 r4 = 4;
|
||||
required fixed64 s1 = 5;
|
||||
required fixed64 s2 = 6;
|
||||
required fixed64 s3 = 7;
|
||||
required fixed64 s4 = 8;
|
||||
}
|
||||
|
||||
// Pubkey for commitment transaction input.
|
||||
message bitcoin_pubkey {
|
||||
option (scalapb.message).extends = "lightning.PubkeyToString";
|
||||
// Must be 33 bytes.
|
||||
required bytes key = 1;
|
||||
}
|
||||
|
||||
message route_step {
|
||||
// Where to next?
|
||||
oneof next {
|
||||
// Actually, this is the last one
|
||||
bool end = 1;
|
||||
// Next lightning node.
|
||||
bitcoin_pubkey bitcoin = 2;
|
||||
// Other realms go here...
|
||||
}
|
||||
|
||||
// How much to forward (difference is fee)
|
||||
required uint32 amount = 4;
|
||||
};
|
||||
|
||||
message route {
|
||||
repeated route_step steps = 1;
|
||||
};
|
||||
|
||||
message routing {
|
||||
required bytes info = 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Packet Types
|
||||
//
|
||||
|
||||
// Set channel params.
|
||||
message authenticate {
|
||||
// Which node this is.
|
||||
required bitcoin_pubkey node_id = 1;
|
||||
// Signature of your session key. */
|
||||
required signature session_sig = 2;
|
||||
};
|
||||
|
||||
// This is the union which defines all of them
|
||||
message pkt {
|
||||
oneof pkt {
|
||||
// Start of connection
|
||||
authenticate auth = 50;
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
syntax = "proto2";
|
||||
|
||||
package scalapb;
|
||||
|
||||
option java_package = "com.trueaccord.scalapb";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
message ScalaPbOptions {
|
||||
// If set then it overrides the java_package and package.
|
||||
optional string package_name = 1;
|
||||
|
||||
// If true, the compiler does not append the proto base file name
|
||||
// into the generated package name. If false (the default), the
|
||||
// generated scala package name is the package_name.basename where
|
||||
// basename is the proto file name without the .proto extension.
|
||||
optional bool flat_package = 2;
|
||||
|
||||
// Adds the following imports at the top of the file (this is meant
|
||||
// to provide implicit TypeMappers)
|
||||
repeated string import = 3;
|
||||
}
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
// File-level optionals for ScalaPB.
|
||||
// Extension number officially assigned by protobuf-global-extension-registry@google.com
|
||||
optional ScalaPbOptions options = 1020;
|
||||
}
|
||||
|
||||
message MessageOptions {
|
||||
// additional classes and traits to mix in to the case class.
|
||||
repeated string extends = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
// Message-level optionals for ScalaPB.
|
||||
// Extension number officially assigned by protobuf-global-extension-registry@google.com
|
||||
optional MessageOptions message = 1020;
|
||||
}
|
||||
|
||||
message FieldOptions {
|
||||
optional string type = 1;
|
||||
}
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
// File-level optionals for ScalaPB.
|
||||
// Extension number officially assigned by protobuf-global-extension-registry@google.com
|
||||
optional FieldOptions field = 1020;
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/**
|
||||
* Created by PM on 18/02/2016.
|
||||
*/
|
||||
|
||||
package lightning
|
||||
|
||||
import java.io.{ByteArrayOutputStream, OutputStream}
|
||||
import java.math.BigInteger
|
||||
import javax.xml.bind.DatatypeConverter
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
|
||||
|
||||
object ToStrings {
|
||||
def writeUInt8(input: Long, out: OutputStream): Unit = out.write((input & 0xff).asInstanceOf[Int])
|
||||
|
||||
def writeUInt64(input: Long, out: OutputStream): Unit = {
|
||||
writeUInt8((input) & 0xff, out)
|
||||
writeUInt8((input >>> 8) & 0xff, out)
|
||||
writeUInt8((input >>> 16) & 0xff, out)
|
||||
writeUInt8((input >>> 24) & 0xff, out)
|
||||
writeUInt8((input >>> 32) & 0xff, out)
|
||||
writeUInt8((input >>> 40) & 0xff, out)
|
||||
writeUInt8((input >>> 48) & 0xff, out)
|
||||
writeUInt8((input >>> 56) & 0xff, out)
|
||||
}
|
||||
}
|
||||
|
||||
trait Sha256ToString {
|
||||
|
||||
// @formatter:off
|
||||
def a: Long
|
||||
def b: Long
|
||||
def c: Long
|
||||
def d: Long
|
||||
// @formatter:on
|
||||
|
||||
override def toString = {
|
||||
import ToStrings._
|
||||
val bos = new ByteArrayOutputStream()
|
||||
writeUInt64(a, bos)
|
||||
writeUInt64(b, bos)
|
||||
writeUInt64(c, bos)
|
||||
writeUInt64(d, bos)
|
||||
s"sha256_hash(${DatatypeConverter.printHexBinary(bos.toByteArray)})"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait RvalToString {
|
||||
|
||||
// @formatter:off
|
||||
def a: Long
|
||||
def b: Long
|
||||
def c: Long
|
||||
def d: Long
|
||||
// @formatter:on
|
||||
|
||||
override def toString = {
|
||||
import ToStrings._
|
||||
val bos = new ByteArrayOutputStream()
|
||||
writeUInt64(a, bos)
|
||||
writeUInt64(b, bos)
|
||||
writeUInt64(c, bos)
|
||||
writeUInt64(d, bos)
|
||||
s"rval(${DatatypeConverter.printHexBinary(bos.toByteArray)})"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait SignatureToString {
|
||||
|
||||
// @formatter:off
|
||||
def r1: Long
|
||||
def r2: Long
|
||||
def r3: Long
|
||||
def r4: Long
|
||||
def s1: Long
|
||||
def s2: Long
|
||||
def s3: Long
|
||||
def s4: Long
|
||||
// @formatter:on
|
||||
|
||||
override def toString = {
|
||||
import ToStrings._
|
||||
val rbos = new ByteArrayOutputStream()
|
||||
writeUInt64(r1, rbos)
|
||||
writeUInt64(r2, rbos)
|
||||
writeUInt64(r3, rbos)
|
||||
writeUInt64(r4, rbos)
|
||||
val r = new BigInteger(1, rbos.toByteArray.reverse)
|
||||
val sbos = new ByteArrayOutputStream()
|
||||
writeUInt64(s1, sbos)
|
||||
writeUInt64(s2, sbos)
|
||||
writeUInt64(s3, sbos)
|
||||
writeUInt64(s4, sbos)
|
||||
val s = new BigInteger(1, sbos.toByteArray.reverse)
|
||||
s"signature(r=${DatatypeConverter.printHexBinary(r.toByteArray)},s=${DatatypeConverter.printHexBinary(s.toByteArray)})"
|
||||
}
|
||||
}
|
||||
|
||||
trait PubkeyToString {
|
||||
|
||||
def key: ByteString
|
||||
|
||||
override def toString = s"bitcoin_pubkey(${DatatypeConverter.printHexBinary(key.toByteArray)})"
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package lightning
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.FunSuite
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
|
||||
/**
|
||||
* Created by PM on 06/07/2016.
|
||||
*/
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class NonRegSpec extends FunSuite {
|
||||
|
||||
test("check signature ToString extensions") {
|
||||
val sig = signature(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
assert(sig.isInstanceOf[SignatureToString])
|
||||
}
|
||||
}
|
2
pom.xml
2
pom.xml
|
@ -8,7 +8,6 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>lightning-types</module>
|
||||
<module>eclair-node</module>
|
||||
</modules>
|
||||
|
||||
|
@ -49,7 +48,6 @@
|
|||
<acinqtools.version>1.2</acinqtools.version>
|
||||
<akka.version>2.4.12</akka.version>
|
||||
<bitcoinlib.version>0.9.9</bitcoinlib.version>
|
||||
<scalapb.version>0.4.21</scalapb.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,16 +1,4 @@
|
|||
pushd .
|
||||
# protobuf 2.6.1
|
||||
cd
|
||||
wget https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.bz2
|
||||
tar xjvf protobuf-2.6.1.tar.bz2
|
||||
cd protobuf-2.6.1
|
||||
./autogen.sh && ./configure && make && sudo make install
|
||||
# protobuf-c
|
||||
cd
|
||||
export LD_LIBRARY_PATH=/usr/local/lib/
|
||||
git clone https://github.com/protobuf-c/protobuf-c.git
|
||||
cd protobuf-c
|
||||
./autogen.sh && ./configure && make && sudo make install
|
||||
# lightning deps
|
||||
sudo add-apt-repository -y ppa:chris-lea/libsodium
|
||||
sudo apt-get update
|
||||
|
|
Loading…
Add table
Reference in a new issue