1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 14:22:39 +01:00

Merge branch 'wip-bolt2' into wip-bolts

This commit is contained in:
pm47 2016-12-13 14:15:23 +01:00
commit b05f52b412
115 changed files with 3903 additions and 3447 deletions

View file

@ -6,7 +6,7 @@ scala:
env:
- export LD_LIBRARY_PATH=/usr/local/lib
script:
- mvn install
- mvn install -DskipTests
jdk:
- oraclejdk8
notifications:

View file

@ -55,9 +55,9 @@ curl -X POST -H "Content-Type: application/json" -d '{
"params" : [ "localhost", 46000, 3000000 ]
}' http://localhost:8080
```
Since eclair is funder, it will create and publish the anchor tx
Since eclair is funder, it will create and publish the funding tx
Mine a few blocks to confirm the anchor tx:
Mine a few blocks to confirm the funding tx:
```shell
bitcoin-cli generate 10
```

View file

@ -79,7 +79,7 @@
<dependency>
<groupId>fr.acinq</groupId>
<artifactId>bitcoin-lib_${scala.version.short}</artifactId>
<version>${bitcoinlib.version}</version>
<version>0.9.8-SNAPSHOT</version>
</dependency>
<!-- SERIALIZATION -->
<dependency>
@ -102,6 +102,11 @@
<artifactId>lenses_${scala.version.short}</artifactId>
<version>0.4</version>
</dependency>
<dependency>
<groupId>org.scodec</groupId>
<artifactId>scodec-core_${scala.version.short}</artifactId>
<version>1.10.3</version>
</dependency>
<!-- LOGGING -->
<dependency>
<groupId>org.clapper</groupId>

View file

@ -6,25 +6,21 @@ public class Poly3105 {
static final int[] minusp = {5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252};
static void add(int[] h, int[] c)
{
static void add(int[] h, int[] c) {
int j;
int u = 0;
for (j = 0; j < 17; ++j)
{
for (j = 0; j < 17; ++j) {
u += h[j] + c[j];
h[j] = u & 255;
u >>>= 8;
}
}
static void squeeze(int[] h)
{
static void squeeze(int[] h) {
int u = 0;
for (int j = 0; j < 16; ++j)
{
for (int j = 0; j < 16; ++j) {
u += h[j];
h[j] = u & 255;
u >>>= 8;
@ -34,8 +30,7 @@ public class Poly3105 {
h[16] = u & 3;
u = 5 * (u >>> 2);
for (int j = 0; j < 16; ++j)
{
for (int j = 0; j < 16; ++j) {
u += h[j];
h[j] = u & 255;
u >>>= 8;
@ -45,8 +40,7 @@ public class Poly3105 {
h[16] = u;
}
static void freeze(int[] h)
{
static void freeze(int[] h) {
int[] horig = new int[17];
for (int j = 0; j < 17; ++j)
@ -54,18 +48,16 @@ public class Poly3105 {
add(h, minusp);
int negative = (int)(-(h[16] >>> 7));
int negative = (int) (-(h[16] >>> 7));
for (int j = 0; j < 17; ++j)
h[j] ^= negative & (horig[j] ^ h[j]);
}
static void mulmod(int[] h, int[] r)
{
static void mulmod(int[] h, int[] r) {
int[] hr = new int[17];
for (int i = 0; i < 17; ++i)
{
for (int i = 0; i < 17; ++i) {
int u = 0;
for (int j = 0; j <= i; ++j)
@ -83,8 +75,7 @@ public class Poly3105 {
squeeze(h);
}
public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, int invoffset, long inlen, byte[] k)
{
public static int crypto_onetimeauth(byte[] outv, int outvoffset, byte[] inv, int invoffset, long inlen, byte[] k) {
int j;
int[] r = new int[17];
int[] h = new int[17];
@ -111,13 +102,12 @@ public class Poly3105 {
for (j = 0; j < 17; ++j)
h[j] = 0;
while (inlen > 0)
{
while (inlen > 0) {
for (j = 0; j < 17; ++j)
c[j] = 0;
for (j = 0; (j < 16) && (j < inlen); ++j)
c[j] = inv[invoffset + j]&0xff;
c[j] = inv[invoffset + j] & 0xff;
c[j] = 1;
invoffset += j;
@ -135,7 +125,7 @@ public class Poly3105 {
add(h, c);
for (j = 0; j < 16; ++j)
outv[j + outvoffset] = (byte)h[j];
outv[j + outvoffset] = (byte) h[j];
return 0;
}

View file

@ -18,8 +18,6 @@ eclair {
node {
seed = 0102030405060708010203040506070801020304050607080102030405060708
}
commit-fee = 80000
closing-fee = 10000
base-fee = 546000
proportional-fee = 10
payment-handler = "local"

View file

@ -1,54 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<VBox styleClass="channel" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane maxWidth="1.7976931348623157E308" styleClass="grid">
<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>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="8.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="Funder" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<Label styleClass="text-muted" text="State" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<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" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
<Label styleClass="text-muted" text="Your balance (millibits)" GridPane.rowIndex="3" />
</children>
</GridPane>
<HBox styleClass="channel-separator" />
</children>
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@../commons/form.css" />
<URL value="@../commons/progressbar.css" />
<URL value="@channel.css" />
</stylesheets>
<children>
<GridPane maxWidth="1.7976931348623157E308" styleClass="grid">
<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>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="8.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="Funder" GridPane.columnIndex="2" GridPane.rowIndex="3"/>
<Label styleClass="text-muted" text="State" GridPane.columnIndex="2" GridPane.rowIndex="4"/>
<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"
GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
<Label styleClass="text-muted" text="Your balance (millibits)" GridPane.rowIndex="3"/>
</children>
</GridPane>
<HBox styleClass="channel-separator"/>
</children>
<stylesheets>
<URL value="@../commons/globals.css"/>
<URL value="@../commons/form.css"/>
<URL value="@../commons/progressbar.css"/>
<URL value="@channel.css"/>
</stylesheets>
</VBox>

View file

@ -1,138 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<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" xmlns:fx="http://javafx.com/fxml/1">
<center>
<TabPane prefHeight="250.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
<Tab closable="false" text="Channels">
<content>
<StackPane>
<children>
<ScrollPane fitToWidth="true" styleClass="channel-container">
<content>
<VBox fx:id="channelBox" />
</content>
</ScrollPane>
<VBox fx:id="channelInfo" alignment="TOP_CENTER" styleClass="channels-info">
<children>
<Label styleClass="text-strong" text="No channels opened yet..." />
<Label styleClass="text-muted" text="You can open a new channel by clicking on &quot;Channels&quot; &gt; &quot;Open Channel...&quot;" wrapText="true" />
</children>
</VBox>
</children>
</StackPane>
</content>
</Tab>
<Tab fx:id="graphTab" closable="false" text="Graph">
<content>
<Label text="Label" />
</content></Tab>
</tabs>
</TabPane>
</center>
<bottom>
<GridPane styleClass="status-bar" BorderPane.alignment="CENTER_LEFT">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="400.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<HBox alignment="CENTER_LEFT">
<children>
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../commons/images/eclair-shape.png" />
</image>
</ImageView>
<Label fx:id="labelNodeId" onContextMenuRequested="#handleNodeIdContext" text="N/A" />
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" GridPane.columnIndex="1">
<children>
<HBox alignment="CENTER_RIGHT" minWidth="80.0">
<children>
<Separator orientation="VERTICAL" />
<Label text="HTTP">
<styleClass>
<String fx:value="badge" />
<String fx:value="badge-http" />
</styleClass>
</Label>
<Label fx:id="labelApi" styleClass="value" text="00000" textAlignment="RIGHT" />
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" minWidth="80.0">
<children>
<Separator orientation="VERTICAL" />
<Label text="TCP">
<styleClass>
<String fx:value="badge" />
<String fx:value="badge-tcp" />
</styleClass>
</Label>
<Label fx:id="labelServer" text="000000" textAlignment="RIGHT" />
</children>
</HBox>
<HBox alignment="CENTER_RIGHT">
<children>
<Separator orientation="VERTICAL" />
<Label text="Bitcoin-core" textAlignment="RIGHT" />
<Label fx:id="bitcoinVersion" text="N/A" />
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)" />
</children>
</HBox>
</children>
</HBox>
</children>
</GridPane>
</bottom>
<stylesheets>
<URL value="@main.css" />
<URL value="@../commons/globals.css" />
</stylesheets>
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="Channels">
<items>
<MenuItem fx:id="menuOpen" mnemonicParsing="false" onAction="#handleOpenChannel" text="Open channel..." />
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="menuSend" mnemonicParsing="false" onAction="#handleSendPayment" text="Send Payment..." />
<MenuItem fx:id="menuReceive" mnemonicParsing="false" onAction="#handleReceivePayment" text="Receive Payment..." />
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#handleCloseRequest" text="Close" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
<items>
<MenuItem mnemonicParsing="false" onAction="#handleOpenAbout" text="About Eclair..." />
</items>
</Menu>
</menus>
</MenuBar>
</top>
<?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"
xmlns:fx="http://javafx.com/fxml/1">
<center>
<TabPane prefHeight="250.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
<tabs>
<Tab closable="false" text="Channels">
<content>
<StackPane>
<children>
<ScrollPane fitToWidth="true" styleClass="channel-container">
<content>
<VBox fx:id="channelBox"/>
</content>
</ScrollPane>
<VBox fx:id="channelInfo" alignment="TOP_CENTER" styleClass="channels-info">
<children>
<Label styleClass="text-strong" text="No channels opened yet..."/>
<Label styleClass="text-muted"
text="You can open a new channel by clicking on &quot;Channels&quot; &gt; &quot;Open Channel...&quot;"
wrapText="true"/>
</children>
</VBox>
</children>
</StackPane>
</content>
</Tab>
<Tab fx:id="graphTab" closable="false" text="Graph">
<content>
<Label text="Label"/>
</content>
</Tab>
</tabs>
</TabPane>
</center>
<bottom>
<GridPane styleClass="status-bar" BorderPane.alignment="CENTER_LEFT">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="400.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<HBox alignment="CENTER_LEFT">
<children>
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true"
preserveRatio="true">
<image>
<Image url="@../commons/images/eclair-shape.png"/>
</image>
</ImageView>
<Label fx:id="labelNodeId" onContextMenuRequested="#handleNodeIdContext" text="N/A"/>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" GridPane.columnIndex="1">
<children>
<HBox alignment="CENTER_RIGHT" minWidth="80.0">
<children>
<Separator orientation="VERTICAL"/>
<Label text="HTTP">
<styleClass>
<String fx:value="badge"/>
<String fx:value="badge-http"/>
</styleClass>
</Label>
<Label fx:id="labelApi" styleClass="value" text="00000" textAlignment="RIGHT"/>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" minWidth="80.0">
<children>
<Separator orientation="VERTICAL"/>
<Label text="TCP">
<styleClass>
<String fx:value="badge"/>
<String fx:value="badge-tcp"/>
</styleClass>
</Label>
<Label fx:id="labelServer" text="000000" textAlignment="RIGHT"/>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT">
<children>
<Separator orientation="VERTICAL"/>
<Label text="Bitcoin-core" textAlignment="RIGHT"/>
<Label fx:id="bitcoinVersion" text="N/A"/>
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)"/>
</children>
</HBox>
</children>
</HBox>
</children>
</GridPane>
</bottom>
<stylesheets>
<URL value="@main.css"/>
<URL value="@../commons/globals.css"/>
</stylesheets>
<top>
<MenuBar BorderPane.alignment="CENTER">
<menus>
<Menu mnemonicParsing="false" text="Channels">
<items>
<MenuItem fx:id="menuOpen" mnemonicParsing="false" onAction="#handleOpenChannel"
text="Open channel..."/>
<SeparatorMenuItem mnemonicParsing="false"/>
<MenuItem fx:id="menuSend" mnemonicParsing="false" onAction="#handleSendPayment"
text="Send Payment..."/>
<MenuItem fx:id="menuReceive" mnemonicParsing="false" onAction="#handleReceivePayment"
text="Receive Payment..."/>
<SeparatorMenuItem mnemonicParsing="false"/>
<MenuItem mnemonicParsing="false" onAction="#handleCloseRequest" text="Close"/>
</items>
</Menu>
<Menu mnemonicParsing="false" text="Help">
<items>
<MenuItem mnemonicParsing="false" onAction="#handleOpenAbout" text="About Eclair..."/>
</items>
</Menu>
</menus>
</MenuBar>
</top>
</BorderPane>

View file

@ -1,66 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<?import javafx.scene.text.TextFlow?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox fillWidth="false" styleClass="about-content" GridPane.columnIndex="1" GridPane.columnSpan="2">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Eclair">
<styleClass>
<String fx:value="about-text" />
<String fx:value="text-strong" />
</styleClass>
</Text>
<TextFlow />
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text" text="This software is brought to you by ACINQ :-)" />
</children>
</TextFlow>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text" text="The source code is available on GitHub (https://github.com/ACINQ/eclair)" />
</children>
</TextFlow>
<TextFlow layoutX="10.0" layoutY="90.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text" text="This software is released under the Apache 2 License (http://www.apache.org/licenses/)" />
</children>
</TextFlow>
</children>
</VBox>
<ImageView fitHeight="120.0" fitWidth="120.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="CENTER">
<image>
<Image url="@../commons/images/eclair02.png" />
</image>
</ImageView>
</children>
<styleClass>
<String fx:value="grid" />
<String fx:value="modal" />
<String fx:value="about" />
</styleClass>
<stylesheets>
<URL value="@../commons/form.css" />
<URL value="@../commons/globals.css" />
<URL value="@modals.css" />
</stylesheets>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?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">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<VBox fillWidth="false" styleClass="about-content" GridPane.columnIndex="1" GridPane.columnSpan="2">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Eclair">
<styleClass>
<String fx:value="about-text"/>
<String fx:value="text-strong"/>
</styleClass>
</Text>
<TextFlow/>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text"
text="This software is brought to you by ACINQ :-)"/>
</children>
</TextFlow>
<TextFlow>
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text"
text="The source code is available on GitHub (https://github.com/ACINQ/eclair)"/>
</children>
</TextFlow>
<TextFlow layoutX="10.0" layoutY="90.0">
<children>
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="about-text"
text="This software is released under the Apache 2 License (http://www.apache.org/licenses/)"/>
</children>
</TextFlow>
</children>
</VBox>
<ImageView fitHeight="120.0" fitWidth="120.0" pickOnBounds="true" preserveRatio="true"
GridPane.halignment="CENTER">
<image>
<Image url="@../commons/images/eclair02.png"/>
</image>
</ImageView>
</children>
<styleClass>
<String fx:value="grid"/>
<String fx:value="modal"/>
<String fx:value="about"/>
</styleClass>
<stylesheets>
<URL value="@../commons/form.css"/>
<URL value="@../commons/globals.css"/>
<URL value="@modals.css"/>
</stylesheets>
</GridPane>

View file

@ -1,51 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="528.0" styleClass="grid" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="258.0" minWidth="10.0" prefWidth="157.0" />
<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>
<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" prefWidth="313.0" promptText="host:port" 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" />
<ComboBox fx:id="unit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:id="milliBTC" fx:value="milliBTC" />
<String fx:id="Satoshi" fx:value="Satoshi" />
<String fx:id="milliSatoshi" fx:value="milliSatoshi" />
</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 (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" />
</children>
<stylesheets>
<URL value="@modals.css" />
<URL value="@../commons/form.css" />
<URL value="@../commons/globals.css" />
</stylesheets>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="528.0"
styleClass="grid" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="258.0" minWidth="10.0" prefWidth="157.0"/>
<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>
<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" prefWidth="313.0" promptText="host:port" 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"/>
<ComboBox fx:id="unit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="3">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:id="milliBTC" fx:value="milliBTC"/>
<String fx:id="Satoshi" fx:value="Satoshi"/>
<String fx:id="milliSatoshi" fx:value="milliSatoshi"/>
</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 (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"/>
</children>
<stylesheets>
<URL value="@modals.css"/>
<URL value="@../commons/form.css"/>
<URL value="@../commons/globals.css"/>
</stylesheets>
</GridPane>

View file

@ -1,41 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Text?>
<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">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="274.0" minWidth="10.0" prefWidth="242.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints fillHeight="false" minHeight="0.0" prefHeight="1.0" valignment="CENTER" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="ALWAYS" />
</rowConstraints>
<children>
<Label text="Amount (mSat)" GridPane.rowIndex="1" />
<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" />
<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" 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" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2" />
</children>
<stylesheets>
<URL value="@../commons/globals.css" />
<URL value="@../commons/form.css" />
</stylesheets>
<?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">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="274.0" minWidth="10.0" prefWidth="242.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints fillHeight="false" minHeight="0.0" prefHeight="1.0" valignment="CENTER" vgrow="ALWAYS"/>
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="ALWAYS"/>
</rowConstraints>
<children>
<Label text="Amount (mSat)" GridPane.rowIndex="1"/>
<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"/>
<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"
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"
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
</children>
<stylesheets>
<URL value="@../commons/globals.css"/>
<URL value="@../commons/form.css"/>
</stylesheets>
</GridPane>

View file

@ -1,55 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<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">
<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>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="1.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label styleClass="text-strong" text="Enter a Payment Request below" GridPane.columnSpan="2" GridPane.valignment="TOP" />
<TextArea fx:id="paymentRequest" minHeight="150.0" prefHeight="150.0" styleClass="ta" wrapText="true" 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" GridPane.columnSpan="2" GridPane.rowIndex="2">
<styleClass>
<String fx:value="text-error" />
<String fx:value="text-error-upward" />
</styleClass></Label>
<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" />
<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>
<styleClass>
<String fx:value="grid" />
<String fx:value="modal" />
</styleClass>
<stylesheets>
<URL value="@../commons/form.css" />
<URL value="@../commons/globals.css" />
<URL value="@modals.css" />
</stylesheets>
<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">
<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>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="1.0" vgrow="SOMETIMES"/>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<Label styleClass="text-strong" text="Enter a Payment Request below" GridPane.columnSpan="2"
GridPane.valignment="TOP"/>
<TextArea fx:id="paymentRequest" minHeight="150.0" prefHeight="150.0" styleClass="ta" wrapText="true"
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"
GridPane.columnSpan="2" GridPane.rowIndex="2">
<styleClass>
<String fx:value="text-error"/>
<String fx:value="text-error-upward"/>
</styleClass>
</Label>
<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"/>
<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>
<styleClass>
<String fx:value="grid"/>
<String fx:value="modal"/>
</styleClass>
<stylesheets>
<URL value="@../commons/form.css"/>
<URL value="@../commons/globals.css"/>
<URL value="@modals.css"/>
</stylesheets>
</GridPane>

View file

@ -1,66 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.effect.BoxBlur?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<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" 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" preserveRatio="true">
<image>
<Image url="@../commons/images/eclair01-icon.png" />
</image>
<effect>
<BoxBlur height="114.75" width="92.44" />
</effect>
</ImageView>
<ImageView fx:id="img" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="114.0" opacity="0.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../commons/images/eclair01-icon.png" />
</image>
</ImageView>
<VBox fx:id="errorBox" alignment="CENTER" layoutX="195.0" layoutY="71.0" opacity="0.0" prefWidth="370.0" styleClass="error-box">
<children>
<GridPane hgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="40.0" prefWidth="40.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="45.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ImageView fitHeight="32.0" fitWidth="32.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="RIGHT">
<image>
<Image url="@../commons/images/warning.png" />
</image>
</ImageView>
<Label fx:id="errorLabel" text="An error has occured!" wrapText="true" GridPane.columnIndex="1" />
</children>
<VBox.margin>
<Insets bottom="40.0" />
</VBox.margin>
</GridPane>
<Button fx:id="closeButton" mnemonicParsing="false" onAction="#closeAndKill" text="Close">
<styleClass>
<String fx:value="button" />
<String fx:value="grey" />
</styleClass></Button>
</children>
</VBox>
</children>
<stylesheets>
<URL value="@splash.css" />
<URL value="@../commons/globals.css" />
</stylesheets>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?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"
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"
preserveRatio="true">
<image>
<Image url="@../commons/images/eclair01-icon.png"/>
</image>
<effect>
<BoxBlur height="114.75" width="92.44"/>
</effect>
</ImageView>
<ImageView fx:id="img" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="114.0" opacity="0.0"
pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../commons/images/eclair01-icon.png"/>
</image>
</ImageView>
<VBox fx:id="errorBox" alignment="CENTER" layoutX="195.0" layoutY="71.0" opacity="0.0" prefWidth="370.0"
styleClass="error-box">
<children>
<GridPane hgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="40.0" prefWidth="40.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0"/>
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="45.0" vgrow="SOMETIMES"/>
</rowConstraints>
<children>
<ImageView fitHeight="32.0" fitWidth="32.0" pickOnBounds="true" preserveRatio="true"
GridPane.halignment="RIGHT">
<image>
<Image url="@../commons/images/warning.png"/>
</image>
</ImageView>
<Label fx:id="errorLabel" text="An error has occured!" wrapText="true"
GridPane.columnIndex="1"/>
</children>
<VBox.margin>
<Insets bottom="40.0"/>
</VBox.margin>
</GridPane>
<Button fx:id="closeButton" mnemonicParsing="false" onAction="#closeAndKill" text="Close">
<styleClass>
<String fx:value="button"/>
<String fx:value="grey"/>
</styleClass>
</Button>
</children>
</VBox>
</children>
<stylesheets>
<URL value="@splash.css"/>
<URL value="@../commons/globals.css"/>
</stylesheets>
</Pane>

View file

@ -8,9 +8,9 @@
</encoder>
</appender>
<logger name="fr.acinq.eclair.channel" level="DEBUG" />
<logger name="fr.acinq.eclair.channel.Register" level="DEBUG" />
<logger name="fr.acinq.protos" level="DEBUG" />
<logger name="fr.acinq.eclair.channel" level="DEBUG"/>
<logger name="fr.acinq.eclair.channel.Register" level="DEBUG"/>
<logger name="fr.acinq.protos" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>

View file

@ -27,7 +27,8 @@
<OnMatch>ACCEPT</OnMatch>
</filter>
<encoder>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %blue(%msg) %ex{12}%n</pattern>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %blue(%msg) %ex{12}%n
</pattern>
</encoder>
</appender>
@ -42,13 +43,15 @@
<OnMatch>ACCEPT</OnMatch>
</filter>
<encoder>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %boldGreen(%msg) %ex{12}%n</pattern>
<pattern>%yellow(${HOSTNAME} %d) %highlight(%-5level) %logger{36} %X{akkaSource} - %boldGreen(%msg)
%ex{12}%n
</pattern>
</encoder>
</appender>
<logger name="fr.acinq.eclair.channel" level="DEBUG" />
<logger name="fr.acinq.eclair.router" level="DEBUG" />
<logger name="fr.acinq.eclair.blockchain.peer.PeerClient" level="INFO" />
<logger name="fr.acinq.eclair.channel" level="DEBUG"/>
<logger name="fr.acinq.eclair.router" level="DEBUG"/>
<logger name="fr.acinq.eclair.blockchain.peer.PeerClient" level="INFO"/>
<root level="INFO">
<appender-ref ref="CHANNEL"/>

View file

@ -2,8 +2,6 @@ package fr.acinq.eclair
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet}
import lightning.locktime
import lightning.locktime.Locktime.{Blocks}
import scala.concurrent.duration._
@ -24,10 +22,9 @@ object Globals {
val id = publicKey.toString()
}
val default_locktime = locktime(Blocks(144))
val default_locktime = 144
val default_mindepth = 3
val commit_fee = config.getInt("eclair.commit-fee")
val closing_fee = config.getInt("eclair.closing-fee")
val default_feeratePerKw = 10000
val base_fee = config.getInt("eclair.base-fee")
val proportional_fee = config.getInt("eclair.proportional-fee")
val default_anchor_amount = 1000000

View file

@ -1,10 +1,8 @@
package fr.acinq.eclair.api
import fr.acinq.eclair._
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.channel.State
import fr.acinq.eclair.crypto.ShaChain
import lightning.sha256_hash
import org.json4s.CustomSerializer
import org.json4s.JsonAST.{JNull, JString}
@ -17,7 +15,7 @@ class BinaryDataSerializer extends CustomSerializer[BinaryData](format => ( {
}, {
case x: BinaryData => JString(x.toString())
}
))
))
class StateSerializer extends CustomSerializer[State](format => ( {
case JString(x) if (false) => // NOT IMPLEMENTED
@ -25,15 +23,7 @@ class StateSerializer extends CustomSerializer[State](format => ( {
}, {
case x: State => JString(x.toString())
}
))
class Sha256Serializer extends CustomSerializer[sha256_hash](format => ( {
case JString(x) if (false) => // NOT IMPLEMENTED
???
}, {
case x: sha256_hash => JString(sha2562bin(x).toString())
}
))
))
class ShaChainSerializer extends CustomSerializer[ShaChain](format => ( {
case JString(x) if (false) => // NOT IMPLEMENTED
@ -41,4 +31,4 @@ class ShaChainSerializer extends CustomSerializer[ShaChain](format => ( {
}, {
case x: ShaChain => JNull
}
))
))

View file

@ -41,7 +41,7 @@ trait Service extends Logging {
implicit def ec: ExecutionContext = ExecutionContext.Implicits.global
implicit val serialization = jackson.Serialization
implicit val formats = org.json4s.DefaultFormats + new BinaryDataSerializer + new StateSerializer + new Sha256Serializer + new ShaChainSerializer
implicit val formats = org.json4s.DefaultFormats + new BinaryDataSerializer + new StateSerializer + new ShaChainSerializer
implicit val timeout = Timeout(30 seconds)
implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True

View file

@ -2,8 +2,8 @@ package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin._
import fr.acinq.eclair.blockchain.rpc.{BitcoinJsonRPCClient, JsonRPCError}
import fr.acinq.eclair.channel
import fr.acinq.eclair.channel.Scripts
import fr.acinq.eclair.{channel, transactions}
import fr.acinq.eclair.transactions.OldScripts
import org.bouncycastle.util.encoders.Hex
import org.json4s.JsonAST._
@ -41,9 +41,9 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
* @return a Future[txid] where txid (a String) is the is of the tx that sends the bitcoins
*/
def sendFromAccount(account: String, destination: String, amount: Double)(implicit ec: ExecutionContext): Future[String] =
client.invoke("sendfrom", account, destination, amount) collect {
case JString(txid) => txid
}
client.invoke("sendfrom", account, destination, amount) collect {
case JString(txid) => txid
}
/**
* @param txId
@ -93,12 +93,12 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
publishTransaction(tx2Hex(tx))
def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
val anchorOutputScript = channel.Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
val anchorOutputScript = transactions.OldScripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
val tx = Transaction(version = 2, txIn = Seq.empty[TxIn], txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
val future = for {
FundTransactionResponse(tx1, changepos, fee) <- fundTransaction(tx)
SignTransactionResponse(anchorTx, true) <- signTransaction(tx1)
Some(pos) = Scripts.findPublicKeyScriptIndex(anchorTx, anchorOutputScript)
Some(pos) = OldScripts.findPublicKeyScriptIndex(anchorTx, anchorOutputScript)
} yield (anchorTx, pos)
future
@ -106,20 +106,20 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
def makeAnchorTx(fundingPriv: BinaryData, ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Btc)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
val pub = Crypto.publicKeyFromPrivateKey(fundingPriv)
val script = Script.write(Scripts.pay2sh(Scripts.pay2wpkh(pub)))
val script = Script.write(OldScripts.pay2sh(OldScripts.pay2wpkh(pub)))
val address = Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, script)
val future = for {
id <- sendFromAccount("", address, amount.amount.toDouble)
tx <- getTransaction(id)
Some(pos) = Scripts.findPublicKeyScriptIndex(tx, script)
Some(pos) = OldScripts.findPublicKeyScriptIndex(tx, script)
output = tx.txOut(pos)
anchorOutputScript = channel.Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
anchorOutputScript = transactions.OldScripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)
tx1 = Transaction(version = 2, txIn = TxIn(OutPoint(tx, pos), Nil, 0xffffffffL) :: Nil, txOut = TxOut(amount, anchorOutputScript) :: Nil, lockTime = 0)
pubKeyScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(pub)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
sig = Transaction.signInput(tx1, 0, pubKeyScript, SIGHASH_ALL, output.amount, 1, fundingPriv)
witness = ScriptWitness(Seq(sig, pub))
tx2 = tx1.updateWitness(0, witness)
Some(pos1) = Scripts.findPublicKeyScriptIndex(tx2, anchorOutputScript)
Some(pos1) = OldScripts.findPublicKeyScriptIndex(tx2, anchorOutputScript)
} yield (tx2, pos1)
future
@ -133,7 +133,7 @@ class ExtendedBitcoinClient(val client: BitcoinJsonRPCClient) {
* @return the current number of blocks in the active chain
*/
def getBlockCount(implicit ec: ExecutionContext): Future[Long] =
client.invoke("getblockcount") collect {
case JInt(count) => count.toLong
}
client.invoke("getblockcount") collect {
case JInt(count) => count.toLong
}
}

View file

@ -4,7 +4,8 @@ import akka.actor.{Actor, ActorLogging, Props, Terminated}
import akka.pattern.pipe
import fr.acinq.bitcoin._
import fr.acinq.eclair.blockchain.peer.{BlockchainEvent, CurrentBlockCount, NewBlock, NewTransaction}
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_SPENT, Scripts}
import fr.acinq.eclair.channel.BITCOIN_FUNDING_SPENT
import fr.acinq.eclair.transactions.OldScripts
import scala.collection.SortedMap
import scala.concurrent.ExecutionContext
@ -27,14 +28,14 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
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_ANCHOR_SPENT, tx)
channel ! (BITCOIN_FUNDING_SPENT, tx)
self ! ('remove, w)
case _ => {}
}
case NewBlock(block) =>
client.getBlockCount.map(count => context.system.eventStream.publish(CurrentBlockCount(count)))
// TODO : beware of the herd effect
// TODO: beware of the herd effect
watches.collect {
case w@WatchConfirmed(channel, txId, minDepth, event) =>
client.getTxConfirmations(txId.toString).map(_ match {
@ -69,13 +70,13 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
}
case PublishAsap(tx) =>
val cltvTimeout = Scripts.cltvTimeout(tx)
val csvTimeout = currentBlockCount + Scripts.csvTimeout(tx)
val cltvTimeout = OldScripts.cltvTimeout(tx)
val csvTimeout = currentBlockCount + OldScripts.csvTimeout(tx)
val timeout = Math.max(cltvTimeout, csvTimeout)
val block2tx1 = block2tx.updated(timeout, tx +: block2tx.getOrElse(timeout, Seq.empty[Transaction]))
context.become(watching(watches, block2tx1, currentBlockCount))
case MakeAnchor(ourCommitPub, theirCommitPub, amount) =>
case MakeFundingTx(ourCommitPub, theirCommitPub, amount) =>
client.makeAnchorTx(ourCommitPub, theirCommitPub, amount).pipeTo(sender)
case Terminated(channel) =>

View file

@ -1,7 +1,7 @@
package fr.acinq.eclair.blockchain
import akka.actor.ActorRef
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction}
import fr.acinq.eclair.channel.BitcoinEvent
/**
@ -13,13 +13,13 @@ import fr.acinq.eclair.channel.BitcoinEvent
trait Watch {
def channel: ActorRef
}
final case class WatchConfirmed(channel: ActorRef, txId: BinaryData, minDepth: Int, event: BitcoinEvent) extends 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
final case class WatchLost(channel: ActorRef, txId: BinaryData, minDepth: Int, event: BitcoinEvent) extends Watch
final case class WatchLost(channel: ActorRef, txId: BinaryData, minDepth: Long, event: BitcoinEvent) extends Watch
final case class Publish(tx: Transaction)
final case class PublishAsap(tx: Transaction)
final case class MakeAnchor(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)
final case class MakeFundingTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)
// @formatter:on

View file

@ -2,11 +2,11 @@ package fr.acinq.eclair.blockchain.peer
import java.net.InetSocketAddress
import fr.acinq.bitcoin._
import akka.actor._
import akka.io.Tcp.Connected
import akka.pattern.{Backoff, BackoffSupervisor}
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin._
import scala.compat.Platform
import scala.concurrent.duration._

View file

@ -5,18 +5,20 @@ import java.net.InetSocketAddress
import akka.actor._
import akka.io.{IO, Tcp}
import akka.util.ByteString
import fr.acinq.bitcoin._
import scala.util.{Failure, Success, Try}
/**
* handles communication with a remote BTC node
* @param remote address of the remote node
* @param listener listener actor BTC messages sent by the remote node will be forwarded to
*/
* handles communication with a remote BTC node
*
* @param remote address of the remote node
* @param listener listener actor BTC messages sent by the remote node will be forwarded to
*/
class PeerHandler(remote: InetSocketAddress, listener: ActorRef) extends Actor with ActorLogging {
import akka.io.Tcp._
implicit val system = context.system
context.watch(listener)

View file

@ -5,8 +5,8 @@ import java.io.IOException
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{Authorization, BasicHttpCredentials}
import akka.http.scaladsl.unmarshalling.Unmarshal
import akka.stream.ActorMaterializer
import de.heikoseeberger.akkahttpjson4s.Json4sSupport._

View file

@ -9,6 +9,7 @@ import akka.actor.{Actor, ActorLogging, ActorRef}
/**
* Purpose of this actor is to be an alias for its origin actor.
* It allows to reference the using {{{system.actorSelection()}}} with a meaningful name
*
* @param origin aliased actor
*/
class AliasActor(origin: ActorRef) extends Actor with ActorLogging {

View file

@ -2,7 +2,6 @@ package fr.acinq.eclair.channel
import akka.actor.ActorRef
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import lightning.sha256_hash
/**
* Created by PM on 17/08/2016.
@ -10,7 +9,7 @@ import lightning.sha256_hash
trait ChannelEvent
case class ChannelCreated(channel: ActorRef, params: OurChannelParams, theirNodeId: String) extends ChannelEvent
case class ChannelCreated(channel: ActorRef, params: LocalParams, theirNodeId: String) extends ChannelEvent
case class ChannelIdAssigned(channel: ActorRef, channelId: BinaryData, amount: Satoshi) extends ChannelEvent
@ -18,8 +17,8 @@ case class ChannelChangedState(channel: ActorRef, theirNodeId: BinaryData, previ
case class ChannelSignatureReceived(channel: ActorRef, Commitments: Commitments) extends ChannelEvent
case class PaymentSent(channel: ActorRef, h: sha256_hash) extends ChannelEvent
case class PaymentSent(channel: ActorRef, h: BinaryData) extends ChannelEvent
case class PaymentFailed(channel: ActorRef, h: sha256_hash, reason: String) extends ChannelEvent
case class PaymentFailed(channel: ActorRef, h: BinaryData, reason: String) extends ChannelEvent
case class PaymentReceived(channel: ActorRef, h: sha256_hash) extends ChannelEvent
case class PaymentReceived(channel: ActorRef, h: BinaryData) extends ChannelEvent

View file

@ -1,8 +1,10 @@
package fr.acinq.eclair.channel
import akka.actor.ActorRef
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
import lightning._
import fr.acinq.bitcoin.{BinaryData, Transaction, TxOut}
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.wire.{ClosingSigned, FundingLocked, Shutdown}
import lightning.{route, route_step}
import scala.concurrent.duration.FiniteDuration
@ -24,23 +26,21 @@ import scala.concurrent.duration.FiniteDuration
"Y8888P" 888 d88P 888 888 8888888888 "Y8888P"
*/
sealed trait State
case object INIT_NOANCHOR extends State
case object INIT_WITHANCHOR extends State
case object OPEN_WAIT_FOR_OPEN_NOANCHOR extends State
case object OPEN_WAIT_FOR_OPEN_WITHANCHOR extends State
case object OPEN_WAIT_FOR_ANCHOR extends State
case object OPEN_WAIT_FOR_COMMIT_SIG extends State
case object OPEN_WAITING_THEIRANCHOR extends State
case object OPEN_WAITING_OURANCHOR extends State
case object OPEN_WAIT_FOR_COMPLETE_OURANCHOR extends State
case object OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR extends State
case object WAIT_FOR_INIT_INTERNAL extends State
case object WAIT_FOR_OPEN_CHANNEL extends State
case object WAIT_FOR_ACCEPT_CHANNEL extends State
case object WAIT_FOR_FUNDING_CREATED_INTERNAL extends State
case object WAIT_FOR_FUNDING_CREATED extends State
case object WAIT_FOR_FUNDING_SIGNED extends State
case object WAIT_FOR_FUNDING_LOCKED_INTERNAL extends State
case object WAIT_FOR_FUNDING_LOCKED extends State
case object NORMAL extends State
case object SHUTDOWN extends State
case object NEGOTIATING extends State
case object CLOSING extends State
case object CLOSED extends State
case object ERR_ANCHOR_LOST extends State
case object ERR_ANCHOR_TIMEOUT extends State
case object ERR_FUNDING_LOST extends State
case object ERR_FUNDING_TIMEOUT extends State
case object ERR_INFORMATION_LEAK extends State
/*
@ -54,17 +54,18 @@ case object ERR_INFORMATION_LEAK extends State
8888888888 Y8P 8888888888 888 Y888 888 "Y8888P"
*/
case class INPUT_INIT_FUNDER(fundingSatoshis: Long, pushMsat: Long)
case class INPUT_INIT_FUNDEE()
case object INPUT_NO_MORE_HTLCS
// when requesting a mutual close, we wait for as much as this timeout, then unilateral close
case object INPUT_CLOSE_COMPLETE_TIMEOUT
sealed trait BitcoinEvent
case object BITCOIN_ANCHOR_DEPTHOK extends BitcoinEvent
case object BITCOIN_ANCHOR_LOST extends BitcoinEvent
case object BITCOIN_ANCHOR_TIMEOUT extends BitcoinEvent
case object BITCOIN_ANCHOR_SPENT extends BitcoinEvent
case object BITCOIN_ANCHOR_OURCOMMIT_DELAYPASSED extends BitcoinEvent
case object BITCOIN_FUNDING_DEPTHOK extends BitcoinEvent
case object BITCOIN_FUNDING_LOST extends BitcoinEvent
case object BITCOIN_FUNDING_TIMEOUT extends BitcoinEvent
case object BITCOIN_FUNDING_SPENT extends BitcoinEvent
case object BITCOIN_FUNDING_OURCOMMIT_DELAYPASSED extends BitcoinEvent
case object BITCOIN_SPEND_THEIRS_DONE extends BitcoinEvent
case object BITCOIN_SPEND_OURS_DONE extends BitcoinEvent
case object BITCOIN_STEAL_DONE extends BitcoinEvent
@ -90,15 +91,15 @@ 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: Int, rHash: sha256_hash, expiry: locktime, 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_FULFILL_HTLC(id: Long, r: rval, commit: Boolean = false) extends Command
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_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
final case class CMD_CLOSE(scriptPubKey: Option[BinaryData]) extends Command
case object CMD_GETSTATE extends Command
case object CMD_GETSTATEDATA extends Command
case object CMD_GETINFO extends Command
final case class RES_GETINFO(nodeid: BinaryData, channelid: BinaryData, state: State, data: Data)
final case class RES_GETINFO(nodeid: BinaryData, channelId: Long, state: State, data: Data)
/*
8888888b. d8888 88888888888 d8888
@ -115,48 +116,24 @@ sealed trait Data
case object Nothing extends Data
final case class OurChannelParams(delay: locktime, commitPrivKey: BinaryData, finalPrivKey: BinaryData, minDepth: Int, initialFeeRate: Long, shaSeed: BinaryData, anchorAmount: Option[Satoshi], autoSignInterval: Option[FiniteDuration] = None) {
val commitPubKey: BinaryData = Crypto.publicKeyFromPrivateKey(commitPrivKey)
val finalPubKey: BinaryData = Crypto.publicKeyFromPrivateKey(finalPrivKey)
}
final case class TheirChannelParams(delay: locktime, commitPubKey: BinaryData, finalPubKey: BinaryData, minDepth: Option[Int], initialFeeRate: Long)
object TheirChannelParams {
def apply(params: OurChannelParams) = new TheirChannelParams(params.delay, params.commitPubKey, params.finalPubKey, Some(params.minDepth), params.initialFeeRate)
}
sealed trait Direction
case object IN extends Direction
case object OUT extends Direction
case class Htlc(direction: Direction, add: update_add_htlc, val previousChannelId: Option[BinaryData])
final case class CommitmentSpec(htlcs: Set[Htlc], feeRate: Long, amount_us_msat: Long, amount_them_msat: Long) {
val totalFunds = amount_us_msat + amount_them_msat + htlcs.toSeq.map(_.add.amountMsat).sum
}
final case class ClosingData(ourScriptPubKey: BinaryData, theirScriptPubKey: Option[BinaryData])
trait HasCommitments extends Data {
def commitments: Commitments
}
final case class DATA_OPEN_WAIT_FOR_OPEN(ourParams: OurChannelParams) extends Data
final case class DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: BinaryData, theirNextRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: sha256_hash, theirNextRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, initialCommitment: TheirCommit, theirNextRevocationHash: sha256_hash) extends Data
final case class DATA_OPEN_WAITING(commitments: Commitments, deferred: Option[open_complete]) extends Data with HasCommitments
final case class DATA_NORMAL(commitments: Commitments,
ourShutdown: Option[close_shutdown],
downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
final case class DATA_SHUTDOWN(commitments: Commitments,
ourShutdown: close_shutdown, theirShutdown: close_shutdown,
final case class DATA_WAIT_FOR_OPEN_CHANNEL(localParams: LocalParams, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(temporaryChannelId: Long, localParams: LocalParams, fundingSatoshis: Long, pushMsat: Long, autoSignInterval: Option[FiniteDuration]) extends Data
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: BinaryData) extends Data
final case class DATA_WAIT_FOR_FUNDING_CREATED(temporaryChannelId: Long, params: ChannelParams, pushMsat: Long, remoteFirstPerCommitmentPoint: BinaryData) extends Data
final case class DATA_WAIT_FOR_FUNDING_SIGNED(temporaryChannelId: Long, params: ChannelParams, fundingTx: Transaction, fundingTxOutputIndex: Int, fundingTxOutput: TxOut, localSpec: CommitmentSpec, localTx: Transaction, 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, ourShutdown: Option[Shutdown], downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
final case class DATA_SHUTDOWN(channelId: Long, params: ChannelParams, commitments: Commitments,
ourShutdown: Shutdown, theirShutdown: Shutdown,
downstreams: Map[Long, Option[Origin]]) extends Data with HasCommitments
final case class DATA_NEGOTIATING(commitments: Commitments,
ourShutdown: close_shutdown, theirShutdown: close_shutdown, ourSignature: close_signature) extends Data with HasCommitments
final case class DATA_NEGOTIATING(channelId: Long, params: ChannelParams, commitments: Commitments,
ourShutdown: Shutdown, theirShutdown: Shutdown, ourClosingSigned: ClosingSigned) extends Data with HasCommitments
final case class DATA_CLOSING(commitments: Commitments,
ourSignature: Option[close_signature] = None,
ourSignature: Option[ClosingSigned] = None,
mutualClosePublished: Option[Transaction] = None,
ourCommitPublished: Option[Transaction] = None,
theirCommitPublished: Option[Transaction] = None,
@ -164,4 +141,36 @@ final case class DATA_CLOSING(commitments: Commitments,
assert(mutualClosePublished.isDefined || ourCommitPublished.isDefined || theirCommitPublished.isDefined || revokedPublished.size > 0, "there should be at least one tx published in this state")
}
final case class ChannelParams(localParams: LocalParams,
remoteParams: RemoteParams,
fundingSatoshis: Long,
minimumDepth: Long,
autoSignInterval: Option[FiniteDuration] = None)
final case class LocalParams(dustLimitSatoshis: Long,
maxHtlcValueInFlightMsat: Long,
channelReserveSatoshis: Long,
htlcMinimumMsat: Long,
feeratePerKw: Long,
toSelfDelay: Int,
maxAcceptedHtlcs: Int,
fundingPrivkey: Scalar,
revocationSecret: Scalar,
paymentSecret: Scalar,
delayedPaymentKey: Scalar,
finalPrivKey: Scalar,
shaSeed: BinaryData)
final case class RemoteParams(dustLimitSatoshis: Long,
maxHtlcValueInFlightMsat: Long,
channelReserveSatoshis: Long,
htlcMinimumMsat: Long,
feeratePerKw: Long,
toSelfDelay: Int,
maxAcceptedHtlcs: Int,
fundingPubkey: Point,
revocationBasepoint: Point,
paymentBasepoint: Point,
delayedPaymentBasepoint: Point)
// @formatter:on

View file

@ -1,74 +1,46 @@
package fr.acinq.eclair.channel
import com.google.protobuf.ByteString
import com.trueaccord.scalapb.GeneratedMessage
import fr.acinq.bitcoin.{BinaryData, Crypto, ScriptFlags, Transaction, TxOut}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.Scripts.TxTemplate
import fr.acinq.eclair.channel.TypeDefs.Change
import fr.acinq.eclair.crypto.LightningCrypto.sha256
import fr.acinq.eclair.crypto.ShaChain
import lightning._
trait TxDb {
def add(parentId: BinaryData, spending: Transaction): Unit
def get(parentId: BinaryData): Option[Transaction]
}
class BasicTxDb extends TxDb {
val db = collection.mutable.HashMap.empty[BinaryData, Transaction]
override def add(parentId: BinaryData, spending: Transaction): Unit = {
db += parentId -> spending
}
override def get(parentId: BinaryData): Option[Transaction] = db.get(parentId)
}
import fr.acinq.eclair.transactions.{CommitTxTemplate, CommitmentSpec, Htlc}
import fr.acinq.eclair.wire._
// @formatter:off
object TypeDefs {
type Change = GeneratedMessage
case class LocalChanges(proposed: List[UpdateMessage], signed: List[UpdateMessage], acked: List[UpdateMessage]) {
def all: List[UpdateMessage] = proposed ++ signed ++ acked
}
case class OurChanges(proposed: List[Change], signed: List[Change], acked: List[Change]) {
def all: List[Change] = proposed ++ signed ++ acked
}
case class TheirChanges(proposed: List[Change], acked: List[Change])
case class Changes(ourChanges: OurChanges, theirChanges: TheirChanges)
case class OurCommit(index: Long, spec: CommitmentSpec, publishableTx: Transaction)
case class TheirCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, theirRevocationHash: sha256_hash)
case class RemoteChanges(proposed: List[UpdateMessage], acked: List[UpdateMessage])
case class Changes(ourChanges: LocalChanges, theirChanges: RemoteChanges)
case class LocalCommit(index: Long, spec: CommitmentSpec, publishableTx: Transaction)
case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: BinaryData, remotePerCommitmentPoint: BinaryData)
// @formatter:on
/**
* about theirNextCommitInfo:
* about remoteNextCommitInfo:
* we either:
* - have built and signed their next commit tx with their next revocation hash which can now be discarded
* - have their next revocation hash
* - have their next per-commitment point
* So, when we've signed and sent a commit message and are waiting for their revocation message,
* theirNextCommitInfo is their next commit tx. The rest of the time, it is their next revocation hash
* theirNextCommitInfo is their next commit tx. The rest of the time, it is their next per-commitment point
*/
case class Commitments(ourParams: OurChannelParams, theirParams: TheirChannelParams,
ourCommit: OurCommit, theirCommit: TheirCommit,
ourChanges: OurChanges, theirChanges: TheirChanges,
ourCurrentHtlcId: Long,
theirNextCommitInfo: Either[TheirCommit, BinaryData],
anchorOutput: TxOut, theirPreimages: ShaChain, txDb: TxDb) {
case class Commitments(localParams: LocalParams, remoteParams: RemoteParams,
localCommit: LocalCommit, remoteCommit: RemoteCommit,
localChanges: LocalChanges, remoteChanges: RemoteChanges,
localCurrentHtlcId: Long,
remoteNextCommitInfo: Either[RemoteCommit, BinaryData],
fundingTxOutput: TxOut,
remotePerCommitmentSecrets: ShaChain, txDb: TxDb, channelId: Long) {
def anchorId: BinaryData = {
assert(ourCommit.publishableTx.txIn.size == 1, "commitment tx should only have one input")
ourCommit.publishableTx.txIn(0).outPoint.hash
assert(localCommit.publishableTx.txIn.size == 1, "commitment tx should only have one input")
localCommit.publishableTx.txIn(0).outPoint.hash
}
def hasNoPendingHtlcs: Boolean = ourCommit.spec.htlcs.isEmpty && theirCommit.spec.htlcs.isEmpty
def hasNoPendingHtlcs: Boolean = localCommit.spec.htlcs.isEmpty && remoteCommit.spec.htlcs.isEmpty
def addOurProposal(proposal: Change): Commitments = Commitments.addOurProposal(this, proposal)
def addLocalProposal(proposal: UpdateMessage): Commitments = Commitments.addLocalProposal(this, proposal)
def addTheirProposal(proposal: Change): Commitments = Commitments.addTheirProposal(this, proposal)
def addRemoteProposal(proposal: UpdateMessage): Commitments = Commitments.addRemoteProposal(this, proposal)
}
object Commitments {
@ -79,109 +51,116 @@ object Commitments {
* @param proposal
* @return an updated commitment instance
*/
private def addOurProposal(commitments: Commitments, proposal: Change): Commitments =
commitments.copy(ourChanges = commitments.ourChanges.copy(proposed = commitments.ourChanges.proposed :+ proposal))
private def addLocalProposal(commitments: Commitments, proposal: UpdateMessage): Commitments =
commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed :+ proposal))
private def addTheirProposal(commitments: Commitments, proposal: Change): Commitments =
commitments.copy(theirChanges = commitments.theirChanges.copy(proposed = commitments.theirChanges.proposed :+ proposal))
private def addRemoteProposal(commitments: Commitments, proposal: UpdateMessage): Commitments =
commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed :+ proposal))
def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC): (Commitments, update_add_htlc) = {
def sendAdd(commitments: Commitments, cmd: CMD_ADD_HTLC): (Commitments, UpdateAddHtlc) = {
// our available funds as seen by them, including all pending changes
val reduced = Helpers.reduce(commitments.theirCommit.spec, commitments.theirChanges.acked, commitments.ourChanges.proposed)
val reduced = CommitmentSpec.reduce(commitments.remoteCommit.spec, commitments.remoteChanges.acked, commitments.localChanges.proposed)
// a node cannot spend pending incoming htlcs
val available = reduced.amount_them_msat
val available = reduced.to_remote_msat
if (cmd.amountMsat > available) {
throw new RuntimeException(s"insufficient funds (available=$available msat)")
} else {
val id = cmd.id.getOrElse(commitments.ourCurrentHtlcId + 1)
val add = update_add_htlc(id, cmd.amountMsat, cmd.rHash, cmd.expiry, routing(ByteString.copyFrom(cmd.payment_route.toByteArray)))
val commitments1 = addOurProposal(commitments, add).copy(ourCurrentHtlcId = id)
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 commitments1 = addLocalProposal(commitments, add).copy(localCurrentHtlcId = id)
(commitments1, add)
}
}
def receiveAdd(commitments: Commitments, add: update_add_htlc): Commitments = {
def receiveAdd(commitments: Commitments, add: UpdateAddHtlc): Commitments = {
// their available funds as seen by us, including all pending changes
val reduced = Helpers.reduce(commitments.ourCommit.spec, commitments.ourChanges.acked, commitments.theirChanges.proposed)
val reduced = CommitmentSpec.reduce(commitments.localCommit.spec, commitments.localChanges.acked, commitments.remoteChanges.proposed)
// a node cannot spend pending incoming htlcs
val available = reduced.amount_them_msat
val available = reduced.to_remote_msat
if (add.amountMsat > available) {
throw new RuntimeException("Insufficient funds")
} else {
// TODO: nodeIds are ignored
addTheirProposal(commitments, add)
addRemoteProposal(commitments, add)
}
}
def sendFulfill(commitments: Commitments, cmd: CMD_FULFILL_HTLC): (Commitments, update_fulfill_htlc) = {
commitments.ourCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == cmd.id => u.add } match {
case Some(htlc) if htlc.rHash == bin2sha256(Crypto.sha256(cmd.r)) =>
val fulfill = update_fulfill_htlc(cmd.id, cmd.r)
val commitments1 = addOurProposal(commitments, fulfill)
def sendFulfill(commitments: Commitments, cmd: CMD_FULFILL_HTLC, channelId: Long): (Commitments, UpdateFulfillHtlc) = {
commitments.localCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == cmd.id => u.add } match {
case Some(htlc) if htlc.paymentHash == sha256(cmd.r) =>
val fulfill = UpdateFulfillHtlc(channelId, cmd.id, cmd.r)
val commitments1 = addLocalProposal(commitments, fulfill)
(commitments1, fulfill)
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc id=${cmd.id}")
case None => throw new RuntimeException(s"unknown htlc id=${cmd.id}")
}
}
def receiveFulfill(commitments: Commitments, fulfill: update_fulfill_htlc): (Commitments, update_add_htlc) = {
commitments.theirCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == fulfill.id => u.add } match {
case Some(htlc) if htlc.rHash == bin2sha256(Crypto.sha256(fulfill.r)) => (addTheirProposal(commitments, fulfill), htlc)
def receiveFulfill(commitments: Commitments, fulfill: UpdateFulfillHtlc): (Commitments, UpdateAddHtlc) = {
commitments.remoteCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == fulfill.id => u.add } match {
case Some(htlc) if htlc.paymentHash == sha256(fulfill.paymentPreimage) => (addRemoteProposal(commitments, fulfill), htlc)
case Some(htlc) => throw new RuntimeException(s"invalid htlc preimage for htlc id=${fulfill.id}")
case None => throw new RuntimeException(s"unknown htlc id=${fulfill.id}") // TODO : we should fail the channel
case None => throw new RuntimeException(s"unknown htlc id=${fulfill.id}") // TODO: we should fail the channel
}
}
def sendFail(commitments: Commitments, cmd: CMD_FAIL_HTLC): (Commitments, update_fail_htlc) = {
commitments.ourCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == cmd.id => u } match {
def sendFail(commitments: Commitments, cmd: CMD_FAIL_HTLC): (Commitments, UpdateFailHtlc) = {
commitments.localCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == cmd.id => u } match {
case Some(htlc) =>
val fail = update_fail_htlc(cmd.id, fail_reason(ByteString.copyFromUtf8(cmd.reason)))
val commitments1 = addOurProposal(commitments, fail)
val fail: UpdateFailHtlc = ???
//UpdateFailHtlc(cmd.channelId, cmd.id, fail_reason(ByteString.copyFromUtf8(cmd.reason)))
val commitments1 = addLocalProposal(commitments, fail)
(commitments1, fail)
case None => throw new RuntimeException(s"unknown htlc id=${cmd.id}")
}
}
def receiveFail(commitments: Commitments, fail: update_fail_htlc): (Commitments, update_add_htlc) = {
commitments.theirCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == fail.id => u.add } match {
case Some(htlc) => (addTheirProposal(commitments, fail), htlc)
case None => throw new RuntimeException(s"unknown htlc id=${fail.id}") // TODO : we should fail the channel
def receiveFail(commitments: Commitments, fail: UpdateFailHtlc): (Commitments, UpdateAddHtlc) = {
commitments.remoteCommit.spec.htlcs.collectFirst { case u: Htlc if u.add.id == fail.id => u.add } match {
case Some(htlc) => (addRemoteProposal(commitments, fail), htlc)
case None => throw new RuntimeException(s"unknown htlc id=${fail.id}") // TODO: we should fail the channel
}
}
def weHaveChanges(commitments: Commitments): Boolean = commitments.theirChanges.acked.size > 0 || commitments.ourChanges.proposed.size > 0
def localHasChanges(commitments: Commitments): Boolean = commitments.remoteChanges.acked.size > 0 || commitments.localChanges.proposed.size > 0
def theyHaveChanges(commitments: Commitments): Boolean = commitments.ourChanges.acked.size > 0 || commitments.theirChanges.proposed.size > 0
def remoteHasChanges(commitments: Commitments): Boolean = commitments.localChanges.acked.size > 0 || commitments.remoteChanges.proposed.size > 0
def sendCommit(commitments: Commitments): (Commitments, update_commit) = {
def revocationPreimage(seed: BinaryData, index: Long): BinaryData = ShaChain.shaChainFromSeed(seed, 0xFFFFFFFFFFFFFFFFL - index)
def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index))
def sendCommit(commitments: Commitments): (Commitments, CommitSig) = {
import commitments._
commitments.theirNextCommitInfo match {
case Right(theirNextRevocationHash) if !weHaveChanges(commitments) =>
commitments.remoteNextCommitInfo match {
case Right(_) if !localHasChanges(commitments) =>
throw new RuntimeException("cannot sign when there are no changes")
case Right(theirNextRevocationHash) =>
case Right(remoteNextPerCommitmentPoint) =>
// sign all our proposals + their acked proposals
// their commitment now includes all our changes + their acked changes
val spec = Helpers.reduce(theirCommit.spec, theirChanges.acked, ourChanges.proposed)
val theirTxTemplate = Helpers.makeTheirTxTemplate(ourParams, theirParams, ourCommit.publishableTx.txIn, theirNextRevocationHash, spec)
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
val theirTxTemplate = CommitmentSpec.makeRemoteTxTemplate(localParams, remoteParams, localCommit.publishableTx.txIn, remoteNextPerCommitmentPoint, spec)
val theirTx = theirTxTemplate.makeTx
// don't sign if they don't get paid
val commit = if (theirTxTemplate.weHaveAnOutput) {
val ourSig = Helpers.sign(ourParams, theirParams, anchorOutput.amount, theirTx)
update_commit(Some(ourSig))
} else {
update_commit(None)
}
val commit: CommitSig = ???
/*if (theirTxTemplate.weHaveAnOutput) {
val ourSig = Helpers.sign(localParams, remoteParams, anchorOutput.amount, theirTx)
CommitSig(Some(ourSig))
} else {
CommitSig(None)
}*/
val commitments1 = commitments.copy(
theirNextCommitInfo = Left(TheirCommit(theirCommit.index + 1, spec, theirTx.txid, theirNextRevocationHash)),
ourChanges = ourChanges.copy(proposed = Nil, signed = ourChanges.proposed),
theirChanges = theirChanges.copy(acked = Nil))
remoteNextCommitInfo = Left(RemoteCommit(remoteCommit.index + 1, spec, theirTx.txid, remoteNextPerCommitmentPoint)),
localChanges = localChanges.copy(proposed = Nil, signed = localChanges.proposed),
remoteChanges = remoteChanges.copy(acked = Nil))
(commitments1, commit)
case Left(theirNextCommit) =>
case Left(remoteNextCommit) =>
throw new RuntimeException("cannot sign until next revocation hash is received")
}
}
def receiveCommit(commitments: Commitments, commit: update_commit): (Commitments, update_revocation) = {
def receiveCommit(commitments: Commitments, commit: CommitSig): (Commitments, RevokeAndAck) = {
import commitments._
// they sent us a signature for *their* view of *our* next commit tx
// so in terms of rev.hashes and indexes we have:
@ -192,81 +171,82 @@ object Commitments {
// we will reply to this sig with our old revocation hash preimage (at index) and our next revocation hash (at index + 1)
// and will increment our index
if (!theyHaveChanges(commitments))
if (!remoteHasChanges(commitments))
throw new RuntimeException("cannot sign when there are no changes")
// check that their signature is valid
// signatures are now optional in the commit message, and will be sent only if the other party is actually
// receiving money i.e its commit tx has one output for them
val spec = Helpers.reduce(ourCommit.spec, ourChanges.acked, theirChanges.proposed)
val ourNextRevocationHash = Helpers.revocationHash(ourParams.shaSeed, ourCommit.index + 1)
val ourTxTemplate = Helpers.makeOurTxTemplate(ourParams, theirParams, ourCommit.publishableTx.txIn, ourNextRevocationHash, spec)
val spec = CommitmentSpec.reduce(localCommit.spec, localChanges.acked, remoteChanges.proposed)
val ourNextRevocationHash = revocationHash(localParams.shaSeed, localCommit.index + 1)
val ourTxTemplate = CommitmentSpec.makeLocalTxTemplate(localParams, remoteParams, localCommit.publishableTx.txIn, ourNextRevocationHash, spec)
// this tx will NOT be signed if our output is empty
val ourCommitTx = commit.sig match {
case None if ourTxTemplate.weHaveAnOutput => throw new RuntimeException("expected signature")
case None => ourTxTemplate.makeTx
case Some(_) if !ourTxTemplate.weHaveAnOutput => throw new RuntimeException("unexpected signature")
case Some(theirSig) =>
val ourTx = ourTxTemplate.makeTx
val ourSig = Helpers.sign(ourParams, theirParams, anchorOutput.amount, ourTx)
val signedTx = Helpers.addSigs(ourParams, theirParams, anchorOutput.amount, ourTx, ourSig, theirSig)
Helpers.checksig(ourParams, theirParams, anchorOutput, signedTx).get
signedTx
}
val ourCommitTx: Transaction = ???
/*commit.sig match {
case None if ourTxTemplate.weHaveAnOutput => throw new RuntimeException("expected signature")
case None => ourTxTemplate.makeTx
case Some(_) if !ourTxTemplate.weHaveAnOutput => throw new RuntimeException("unexpected signature")
case Some(theirSig) =>
val ourTx = ourTxTemplate.makeTx
val ourSig = Helpers.sign(localParams, remoteParams, anchorOutput.amount, ourTx)
val signedTx = Helpers.addSigs(localParams, remoteParams, anchorOutput.amount, ourTx, ourSig, theirSig)
Helpers.checksig(localParams, remoteParams, anchorOutput, signedTx).get
signedTx
}*/
// we will send our revocation preimage + our next revocation hash
val ourRevocationPreimage = Helpers.revocationPreimage(ourParams.shaSeed, ourCommit.index)
val ourNextRevocationHash1 = Helpers.revocationHash(ourParams.shaSeed, ourCommit.index + 2)
val revocation = update_revocation(ourRevocationPreimage, ourNextRevocationHash1)
val ourRevocationPreimage = revocationPreimage(localParams.shaSeed, localCommit.index)
val ourNextRevocationHash1 = revocationHash(localParams.shaSeed, localCommit.index + 2)
val revocation: RevokeAndAck = ??? //RevokeAndAck(ourRevocationPreimage, ourNextRevocationHash1)
// update our commitment data
val ourCommit1 = ourCommit.copy(index = ourCommit.index + 1, spec, publishableTx = ourCommitTx)
val ourChanges1 = ourChanges.copy(acked = Nil)
val theirChanges1 = theirChanges.copy(proposed = Nil, acked = theirChanges.acked ++ theirChanges.proposed)
val commitments1 = commitments.copy(ourCommit = ourCommit1, ourChanges = ourChanges1, theirChanges = theirChanges1)
val ourCommit1 = localCommit.copy(index = localCommit.index + 1, spec, publishableTx = ourCommitTx)
val ourChanges1 = localChanges.copy(acked = Nil)
val theirChanges1 = remoteChanges.copy(proposed = Nil, acked = remoteChanges.acked ++ remoteChanges.proposed)
val commitments1 = commitments.copy(localCommit = ourCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1)
(commitments1, revocation)
}
def receiveRevocation(commitments: Commitments, revocation: update_revocation): Commitments = {
def receiveRevocation(commitments: Commitments, revocation: RevokeAndAck): Commitments = {
import commitments._
// we receive a revocation because we just sent them a sig for their next commit tx
theirNextCommitInfo match {
case Left(theirNextCommit) if BinaryData(Crypto.sha256(revocation.revocationPreimage)) != BinaryData(theirCommit.theirRevocationHash) =>
remoteNextCommitInfo match {
case Left(theirNextCommit) if BinaryData(Crypto.sha256(revocation.perCommitmentSecret)) != BinaryData(remoteCommit.remotePerCommitmentPoint) =>
throw new RuntimeException("invalid preimage")
case Left(theirNextCommit) =>
// this is their revoked commit tx
val theirTxTemplate = Helpers.makeTheirTxTemplate(ourParams, theirParams, ourCommit.publishableTx.txIn, theirCommit.theirRevocationHash, theirCommit.spec)
val theirTxTemplate = CommitmentSpec.makeRemoteTxTemplate(localParams, remoteParams, localCommit.publishableTx.txIn, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec)
val theirTx = theirTxTemplate.makeTx
val punishTx = Helpers.claimRevokedCommitTx(theirTxTemplate, revocation.revocationPreimage, ourParams.finalPrivKey)
val punishTx: Transaction = ??? //Helpers.claimRevokedCommitTx(theirTxTemplate, revocation.revocationPreimage, localParams.finalPrivKey)
Transaction.correctlySpends(punishTx, Seq(theirTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
txDb.add(theirTx.txid, punishTx)
commitments.copy(
ourChanges = ourChanges.copy(signed = Nil, acked = ourChanges.acked ++ ourChanges.signed),
theirCommit = theirNextCommit,
theirNextCommitInfo = Right(revocation.nextRevocationHash),
theirPreimages = commitments.theirPreimages.addHash(revocation.revocationPreimage, 0xFFFFFFFFFFFFFFFFL - commitments.theirCommit.index))
localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed),
remoteCommit = theirNextCommit,
remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint),
remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFFFFFL - commitments.remoteCommit.index))
case Right(_) =>
throw new RuntimeException("received unexpected update_revocation message")
throw new RuntimeException("received unexpected RevokeAndAck message")
}
}
def makeOurTxTemplate(commitments: Commitments): TxTemplate = {
Helpers.makeOurTxTemplate(commitments.ourParams, commitments.theirParams, commitments.ourCommit.publishableTx.txIn,
Helpers.revocationHash(commitments.ourParams.shaSeed, commitments.ourCommit.index), commitments.ourCommit.spec)
def makeLocalTxTemplate(commitments: Commitments): CommitTxTemplate = {
CommitmentSpec.makeLocalTxTemplate(commitments.localParams, commitments.remoteParams, commitments.localCommit.publishableTx.txIn,
revocationHash(commitments.localParams.shaSeed, commitments.localCommit.index), commitments.localCommit.spec)
}
def makeTheirTxTemplate(commitments: Commitments): TxTemplate = {
commitments.theirNextCommitInfo match {
def makeRemoteTxTemplate(commitments: Commitments): CommitTxTemplate = {
commitments.remoteNextCommitInfo match {
case Left(theirNextCommit) =>
Helpers.makeTheirTxTemplate(commitments.ourParams, commitments.theirParams, commitments.ourCommit.publishableTx.txIn,
theirNextCommit.theirRevocationHash, theirNextCommit.spec)
CommitmentSpec.makeRemoteTxTemplate(commitments.localParams, commitments.remoteParams, commitments.localCommit.publishableTx.txIn,
theirNextCommit.remotePerCommitmentPoint, theirNextCommit.spec)
case Right(revocationHash) =>
Helpers.makeTheirTxTemplate(commitments.ourParams, commitments.theirParams, commitments.ourCommit.publishableTx.txIn,
commitments.theirCommit.theirRevocationHash, commitments.theirCommit.spec)
CommitmentSpec.makeRemoteTxTemplate(commitments.localParams, commitments.remoteParams, commitments.localCommit.publishableTx.txIn,
commitments.remoteCommit.remotePerCommitmentPoint, commitments.remoteCommit.spec)
}
}
}

View file

@ -1,247 +1,187 @@
package fr.acinq.eclair.channel
import Scripts._
import fr.acinq.bitcoin.{OutPoint, _}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.TypeDefs.Change
import fr.acinq.eclair.crypto.ShaChain
import lightning._
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.transactions.OldScripts._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.UpdateFulfillHtlc
import scala.annotation.tailrec
import scala.util.Try
/**
* Created by PM on 20/05/2016.
*/
object Helpers {
def removeHtlc(changes: List[Change], id: Long): List[Change] = changes.filterNot(_ match {
case u: update_add_htlc if u.id == id => true
case _ => false
})
object Funding {
def addHtlc(spec: CommitmentSpec, direction: Direction, update: update_add_htlc): CommitmentSpec = {
val htlc = Htlc(direction, update, previousChannelId = None)
direction match {
case OUT => spec.copy(amount_us_msat = spec.amount_us_msat - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
case IN => spec.copy(amount_them_msat = spec.amount_them_msat - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
/**
* Extracts a [TxIn] from a funding tx id and an output index
* @param fundingTxId
* @param fundingTxOutputIndex
*/
def inputFromFundingTx(fundingTxId: BinaryData, fundingTxOutputIndex: Int): TxIn = TxIn(OutPoint(fundingTxId, fundingTxOutputIndex), Array.emptyByteArray, 0xffffffffL)
/**
* Creates both sides's first commitment transaction
* @param funder
* @param params
* @param pushMsat
* @param fundingTxHash
* @param fundingTxOutputIndex
* @param remoteFirstPerCommitmentPoint
* @return (localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
*/
def makeFirstCommitmentTx(funder: Boolean, params: ChannelParams, pushMsat: Long, fundingTxHash: BinaryData, fundingTxOutputIndex: Int, remoteFirstPerCommitmentPoint: BinaryData): (CommitmentSpec, Transaction, CommitmentSpec, Transaction, TxOut) = {
val toLocalMsat = if (funder) params.fundingSatoshis * 1000 - pushMsat else pushMsat
val toRemoteMsat = if (funder) pushMsat else params.fundingSatoshis * 1000 - pushMsat
// local and remote feerate are the same at this point (funder gets to choose the initial feerate)
val localSpec = CommitmentSpec(Set.empty[Htlc], feeRate = params.localParams.feeratePerKw, to_local_msat = toLocalMsat, to_remote_msat = toRemoteMsat)
val remoteSpec = CommitmentSpec(Set.empty[Htlc], feeRate = params.remoteParams.feeratePerKw, to_local_msat = toRemoteMsat, to_remote_msat = toLocalMsat)
val commitmentInput = Funding.inputFromFundingTx(fundingTxHash, fundingTxOutputIndex)
val localPerCommitmentPoint = Generators.perCommitPoint(params.localParams.shaSeed, 0)
val localTx = CommitmentSpec.makeLocalTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, localPerCommitmentPoint.data, localSpec).makeTx
val remoteTx = CommitmentSpec.makeRemoteTxTemplate(params.localParams, params.remoteParams, commitmentInput :: Nil, remoteFirstPerCommitmentPoint, remoteSpec).makeTx
val localFundingPubkey = params.localParams.fundingPrivkey.point
val fundingTxOutput = TxOut(Satoshi(params.fundingSatoshis), publicKeyScript = OldScripts.anchorPubkeyScript(localFundingPubkey, params.remoteParams.fundingPubkey))
(localSpec, localTx, remoteSpec, remoteTx, fundingTxOutput)
}
}
// OUT means we are sending an update_fulfill_htlc message which means that we are fulfilling an HTLC that they sent
def fulfillHtlc(spec: CommitmentSpec, direction: Direction, update: update_fulfill_htlc): CommitmentSpec = {
spec.htlcs.find(htlc => htlc.add.id == update.id && htlc.add.rHash == bin2sha256(Crypto.sha256(update.r))) match {
case Some(htlc) if direction == OUT => spec.copy(amount_us_msat = spec.amount_us_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(amount_them_msat = spec.amount_them_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${update.id}")
}
}
object Closing {
// OUT means we are sending an update_fail_htlc message which means that we are failing an HTLC that they sent
def failHtlc(spec: CommitmentSpec, direction: Direction, update: update_fail_htlc): CommitmentSpec = {
spec.htlcs.find(_.add.id == update.id) match {
case Some(htlc) if direction == OUT => spec.copy(amount_them_msat = spec.amount_them_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(amount_us_msat = spec.amount_us_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${update.id}")
}
}
def checkCloseSignature(closeSig: BinaryData, closeFee: Satoshi, d: DATA_NEGOTIATING): Try[Transaction] = ???
def reduce(ourCommitSpec: CommitmentSpec, ourChanges: List[Change], theirChanges: List[Change]): CommitmentSpec = {
val spec1 = ourChanges.foldLeft(ourCommitSpec) {
case (spec, u: update_add_htlc) => addHtlc(spec, OUT, u)
case (spec, _) => spec
}
val spec2 = theirChanges.foldLeft(spec1) {
case (spec, u: update_add_htlc) => addHtlc(spec, IN, u)
case (spec, _) => spec
}
val spec3 = ourChanges.foldLeft(spec2) {
case (spec, u: update_fulfill_htlc) => fulfillHtlc(spec, OUT, u)
case (spec, u: update_fail_htlc) => failHtlc(spec, OUT, u)
case (spec, _) => spec
}
val spec4 = theirChanges.foldLeft(spec3) {
case (spec, u: update_fulfill_htlc) => fulfillHtlc(spec, IN, u)
case (spec, u: update_fail_htlc) => failHtlc(spec, IN, u)
case (spec, _) => spec
}
spec4
}
def makeOurTxTemplate(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], ourRevocationHash: sha256_hash, spec: CommitmentSpec): TxTemplate =
makeCommitTxTemplate(inputs, ourParams.finalPubKey, theirParams.finalPubKey, ourParams.delay, ourRevocationHash, spec)
def makeOurTx(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], ourRevocationHash: sha256_hash, spec: CommitmentSpec): Transaction =
makeCommitTx(inputs, ourParams.finalPubKey, theirParams.finalPubKey, ourParams.delay, ourRevocationHash, spec)
def makeTheirTxTemplate(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], theirRevocationHash: sha256_hash, spec: CommitmentSpec): TxTemplate =
makeCommitTxTemplate(inputs, theirParams.finalPubKey, ourParams.finalPubKey, theirParams.delay, theirRevocationHash, spec)
def makeTheirTx(ourParams: OurChannelParams, theirParams: TheirChannelParams, inputs: Seq[TxIn], theirRevocationHash: sha256_hash, spec: CommitmentSpec): Transaction =
makeTheirTxTemplate(ourParams, theirParams, inputs, theirRevocationHash, spec).makeTx
def sign(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Satoshi, tx: Transaction): signature =
bin2signature(Transaction.signInput(tx, 0, multiSig2of2(ourParams.commitPubKey, theirParams.commitPubKey), SIGHASH_ALL, anchorAmount, 1, ourParams.commitPrivKey))
def addSigs(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorAmount: Satoshi, tx: Transaction, ourSig: signature, theirSig: signature): Transaction = {
// TODO : Transaction.sign(...) should handle multisig
val ourSig = Transaction.signInput(tx, 0, multiSig2of2(ourParams.commitPubKey, theirParams.commitPubKey), SIGHASH_ALL, anchorAmount, 1, ourParams.commitPrivKey)
val witness = witness2of2(theirSig, ourSig, theirParams.commitPubKey, ourParams.commitPubKey)
tx.updateWitness(0, witness)
}
def checksig(ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorOutput: TxOut, tx: Transaction): Try[Unit] =
Try(Transaction.correctlySpends(tx, Map(tx.txIn(0).outPoint -> anchorOutput), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
def checkCloseSignature(closeSig: BinaryData, closeFee: Satoshi, d: DATA_NEGOTIATING): Try[Transaction] = {
/*{
val (finalTx, ourCloseSig) = Helpers.makeFinalTx(d.commitments, d.ourShutdown.scriptPubkey, d.theirShutdown.scriptPubkey, closeFee)
val signedTx = addSigs(d.commitments.ourParams, d.commitments.theirParams, d.commitments.anchorOutput.amount, finalTx, ourCloseSig.sig, closeSig)
checksig(d.commitments.ourParams, d.commitments.theirParams, d.commitments.anchorOutput, signedTx).map(_ => signedTx)
}
}*/
def isMutualClose(tx: Transaction, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: OurCommit): Boolean = {
// we rebuild the closing tx as seen by both parties
//TODO we should use the closing fee in pkts
//val closingState = commitment.state.adjust_fees(Globals.closing_fee * 1000, ourParams.anchorAmount.isDefined)
//val finalTx = makeFinalTx(commitment.tx.txIn, theirParams.finalPubKey, ourFinalPubKey, closingState)
val finalTx: Transaction = null // = makeFinalTx(commitment.tx.txIn, ourParams.finalPubKey, theirParams.finalPubKey, closingState)
// and only compare the outputs
tx.txOut == finalTx.txOut
}
/**
*
* @param commitments
* @param ourScriptPubKey
* @param theirScriptPubKey
* @param closeFee bitcoin fee for the final tx
* @return a (final tx, fee, our signature) tuple. The tx is not signed.
*/
def makeFinalTx(commitments: Commitments, ourScriptPubKey: BinaryData, theirScriptPubKey: BinaryData, closeFee: Satoshi): (Transaction, Long, BinaryData) = ???
def isOurCommit(tx: Transaction, commitment: OurCommit): Boolean = tx == commitment.publishableTx
def isTheirCommit(tx: Transaction, ourParams: OurChannelParams, theirParams: TheirChannelParams, commitment: TheirCommit): Boolean = {
// we rebuild their commitment tx
val theirTx = makeTheirTx(ourParams, theirParams, tx.txIn, commitment.theirRevocationHash, commitment.spec)
// and only compare the outputs
tx.txOut == theirTx.txOut
}
def isRevokedCommit(tx: Transaction): Boolean = {
// TODO : for now we assume that every published tx which is none of (mutualclose, ourcommit, theircommit) is a revoked commit
// which means ERR_INFORMATION_LEAK will never occur
true
}
/**
*
* @param commitments
* @param ourScriptPubKey
* @param theirScriptPubKey
* @param closeFee bitcoin fee for the final tx
* @return a (final tx, our signature) tuple. The tx is not signed.
*/
def makeFinalTx(commitments: Commitments, ourScriptPubKey: BinaryData, theirScriptPubKey: BinaryData, closeFee: Satoshi): (Transaction, close_signature) = {
/*{
val amount_us = Satoshi(commitments.ourCommit.spec.amount_us_msat / 1000)
val amount_them = Satoshi(commitments.theirCommit.spec.amount_us_msat / 1000)
val finalTx = Scripts.makeFinalTx(commitments.ourCommit.publishableTx.txIn, ourScriptPubKey, theirScriptPubKey, amount_us, amount_them, closeFee)
val ourSig = Helpers.sign(commitments.ourParams, commitments.theirParams, commitments.anchorOutput.amount, finalTx)
(finalTx, close_signature(closeFee.toLong, ourSig))
}
(finalTx, ClosingSigned(closeFee.toLong, ourSig))
}*/
/**
*
* @param commitments
* @param ourScriptPubKey
* @param theirScriptPubKey
* @return a (final tx, our signature) tuple. The tx is not signed. Bitcoin fees will be copied from our
* last commit tx
*/
def makeFinalTx(commitments: Commitments, ourScriptPubKey: BinaryData, theirScriptPubKey: BinaryData): (Transaction, close_signature) = {
val commitFee = commitments.anchorOutput.amount.toLong - commitments.ourCommit.publishableTx.txOut.map(_.amount.toLong).sum
val closeFee = Satoshi(2 * (commitFee / 4))
makeFinalTx(commitments, ourScriptPubKey, theirScriptPubKey, closeFee)
}
def revocationPreimage(seed: BinaryData, index: Long): BinaryData = ShaChain.shaChainFromSeed(seed, 0xFFFFFFFFFFFFFFFFL - index)
def revocationHash(seed: BinaryData, index: Long): BinaryData = Crypto.sha256(revocationPreimage(seed, index))
/**
* Claim a revoked commit tx using the matching revocation preimage, which allows us to claim all its inputs without a
* delay
*
* @param theirTxTemplate revoked commit tx template
* @param revocationPreimage revocation preimage (which must match this specific commit tx)
* @param privateKey private key to send the claimed funds to (the returned tx will include a single P2WPKH output)
* @return a signed transaction that spends the revoked commit tx
*/
def claimRevokedCommitTx(theirTxTemplate: TxTemplate, revocationPreimage: BinaryData, privateKey: BinaryData): Transaction = {
val theirTx = theirTxTemplate.makeTx
val outputs = collection.mutable.ListBuffer.empty[TxOut]
// first, find out how much we can claim
val outputsToClaim = (theirTxTemplate.ourOutput.toSeq ++ theirTxTemplate.htlcReceived ++ theirTxTemplate.htlcSent).filter(o => theirTx.txOut.indexOf(o.txOut) != -1)
val totalAmount = outputsToClaim.map(_.amount).sum // TODO: substract a small network fee
// create a tx that sends everything to our private key
val tx = Transaction(version = 2,
txIn = Seq.empty[TxIn],
txOut = TxOut(totalAmount, pay2wpkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = 0)
// create tx inputs that spend each output that we can spend
val inputs = outputsToClaim.map(outputTemplate => {
val index = theirTx.txOut.indexOf(outputTemplate.txOut)
TxIn(OutPoint(theirTx, index), signatureScript = BinaryData.empty, sequence = 0xffffffffL)
})
assert(inputs.length == outputsToClaim.length)
// and sign them
val tx1 = tx.copy(txIn = inputs)
val witnesses = for (i <- 0 until tx1.txIn.length) yield {
val sig = Transaction.signInput(tx1, i, outputsToClaim(i).redeemScript, SIGHASH_ALL, outputsToClaim(i).amount, 1, privateKey)
val witness = ScriptWitness(sig :: revocationPreimage :: outputsToClaim(i).redeemScript :: Nil)
witness
/**
*
* @param commitments
* @param ourScriptPubKey
* @param theirScriptPubKey
* @return a (final tx, fee, our signature) tuple. The tx is not signed. Bitcoin fees will be copied from our
* last commit tx
*/
def makeFinalTx(commitments: Commitments, ourScriptPubKey: BinaryData, theirScriptPubKey: BinaryData): (Transaction, Long, BinaryData) = {
val commitFee = commitments.fundingTxOutput.amount.toLong - commitments.localCommit.publishableTx.txOut.map(_.amount.toLong).sum
val closeFee = Satoshi(2 * (commitFee / 4))
makeFinalTx(commitments, ourScriptPubKey, theirScriptPubKey, closeFee)
}
tx1.updateWitnesses(witnesses)
}
/**
* Claim a revoked commit tx using the matching revocation preimage, which allows us to claim all its inputs without a
* delay
*
* @param theirTxTemplate revoked commit tx template
* @param revocationPreimage revocation preimage (which must match this specific commit tx)
* @param privateKey private key to send the claimed funds to (the returned tx will include a single P2WPKH output)
* @return a signed transaction that spends the revoked commit tx
*/
def claimRevokedCommitTx(theirTxTemplate: CommitTxTemplate, revocationPreimage: BinaryData, privateKey: BinaryData): Transaction = {
val theirTx = theirTxTemplate.makeTx
val outputs = collection.mutable.ListBuffer.empty[TxOut]
/**
* claim an HTLC that we received using its payment preimage. This is used only when the other party publishes its
* current commit tx which contains pending HTLCs.
*
* @param tx commit tx published by the other party
* @param htlcTemplate HTLC template for an HTLC in the commit tx for which we have the preimage
* @param paymentPreimage HTLC preimage
* @param privateKey private key which matches the pubkey that the HTLC was sent to
* @return a signed transaction that spends the HTLC in their published commit tx.
* This tx is not spendable right away: it has both an absolute CLTV time-out and a relative CSV time-out
* before which it can be published
*/
def claimReceivedHtlc(tx: Transaction, htlcTemplate: HtlcTemplate, paymentPreimage: BinaryData, privateKey: BinaryData): Transaction = {
require(htlcTemplate.htlc.add.rHash == bin2sha256(Crypto.sha256(paymentPreimage)), "invalid payment preimage")
// find its index in their tx
val index = tx.txOut.indexOf(htlcTemplate.txOut)
// first, find out how much we can claim
val outputsToClaim = (theirTxTemplate.localOutput.toSeq ++ theirTxTemplate.htlcReceived ++ theirTxTemplate.htlcSent).filter(o => theirTx.txOut.indexOf(o.txOut) != -1)
val totalAmount = outputsToClaim.map(_.amount).sum // TODO: substract a small network fee
val tx1 = Transaction(version = 2,
txIn = TxIn(OutPoint(tx, index), BinaryData.empty, sequence = Scripts.locktime2long_csv(htlcTemplate.delay)) :: Nil,
txOut = TxOut(htlcTemplate.amount, Scripts.pay2pkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = Scripts.locktime2long_cltv(htlcTemplate.htlc.add.expiry))
// create a tx that sends everything to our private key
val tx = Transaction(version = 2,
txIn = Seq.empty[TxIn],
txOut = TxOut(totalAmount, pay2wpkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = 0)
val sig = Transaction.signInput(tx1, 0, htlcTemplate.redeemScript, SIGHASH_ALL, htlcTemplate.amount, 1, privateKey)
val witness = ScriptWitness(sig :: paymentPreimage :: htlcTemplate.redeemScript :: Nil)
val tx2 = tx1.updateWitness(0, witness)
tx2
}
// create tx inputs that spend each output that we can spend
val inputs = outputsToClaim.map(outputTemplate => {
val index = theirTx.txOut.indexOf(outputTemplate.txOut)
TxIn(OutPoint(theirTx, index), signatureScript = BinaryData.empty, sequence = 0xffffffffL)
})
assert(inputs.length == outputsToClaim.length)
/**
* claim all the HTLCs that we've received from their current commit tx
*
* @param txTemplate commit tx published by the other party
* @param commitments our commitment data, which include payment preimages
* @return a list of transactions (one per HTLC that we can claim)
*/
def claimReceivedHtlcs(tx: Transaction, txTemplate: TxTemplate, commitments: Commitments): Seq[Transaction] = {
val preImages = commitments.ourChanges.all.collect { case update_fulfill_htlc(id, r) => rval2bin(r) }
// TODO: FIXME !!!
//val htlcTemplates = txTemplate.htlcSent
val htlcTemplates = txTemplate.htlcReceived ++ txTemplate.htlcSent
// and sign them
val tx1 = tx.copy(txIn = inputs)
val witnesses = for (i <- 0 until tx1.txIn.length) yield {
val sig = Transaction.signInput(tx1, i, outputsToClaim(i).redeemScript, SIGHASH_ALL, outputsToClaim(i).amount, 1, privateKey)
val witness = ScriptWitness(sig :: revocationPreimage :: outputsToClaim(i).redeemScript :: Nil)
witness
}
@tailrec
def loop(htlcs: Seq[HtlcTemplate], acc: Seq[Transaction] = Seq.empty[Transaction]): Seq[Transaction] = {
tx1.updateWitnesses(witnesses)
}
/**
* claim an HTLC that we received using its payment preimage. This is used only when the other party publishes its
* current commit tx which contains pending HTLCs.
*
* @param tx commit tx published by the other party
* @param htlcTemplate HTLC template for an HTLC in the commit tx for which we have the preimage
* @param paymentPreimage HTLC preimage
* @param privateKey private key which matches the pubkey that the HTLC was sent to
* @return a signed transaction that spends the HTLC in their published commit tx.
* This tx is not spendable right away: it has both an absolute CLTV time-out and a relative CSV time-out
* before which it can be published
*/
def claimReceivedHtlc(tx: Transaction, htlcTemplate: HTLCTemplate, paymentPreimage: BinaryData, privateKey: BinaryData): Transaction = {
require(htlcTemplate.htlc.add.paymentHash == BinaryData(Crypto.sha256(paymentPreimage)), "invalid payment preimage")
// find its index in their tx
val index = tx.txOut.indexOf(htlcTemplate.txOut)
val tx1 = Transaction(version = 2,
txIn = TxIn(OutPoint(tx, index), BinaryData.empty, sequence = OldScripts.toSelfDelay2csv(htlcTemplate.delay)) :: Nil,
txOut = TxOut(htlcTemplate.amount, OldScripts.pay2pkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = ??? /*Scripts.locktime2long_cltv(htlcTemplate.htlc.add.expiry)*/)
val sig = Transaction.signInput(tx1, 0, htlcTemplate.redeemScript, SIGHASH_ALL, htlcTemplate.amount, 1, privateKey)
val witness = ScriptWitness(sig :: paymentPreimage :: htlcTemplate.redeemScript :: Nil)
val tx2 = tx1.updateWitness(0, witness)
tx2
}
/**
* claim all the HTLCs that we've received from their current commit tx
*
* @param txTemplate commit tx published by the other party
* @param commitments our commitment data, which include payment preimages
* @return a list of transactions (one per HTLC that we can claim)
*/
def claimReceivedHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = {
val preImages = commitments.localChanges.all.collect { case UpdateFulfillHtlc(_, id, paymentPreimage) => paymentPreimage }
// TODO: FIXME !!!
//val htlcTemplates = txTemplate.htlcSent
val htlcTemplates = txTemplate.htlcReceived ++ txTemplate.htlcSent
//@tailrec
def loop(htlcs: Seq[HTLCTemplate], acc: Seq[Transaction] = Seq.empty[Transaction]): Seq[Transaction] = Nil
/*{
htlcs.headOption match {
case Some(head) =>
preImages.find(preImage => head.htlc.add.rHash == bin2sha256(Crypto.sha256(preImage))) match {
@ -250,28 +190,33 @@ object Helpers {
}
case None => acc
}
}*/
loop(htlcTemplates)
}
loop(htlcTemplates)
}
def claimSentHtlc(tx: Transaction, htlcTemplate: HtlcTemplate, privateKey: BinaryData): Transaction = {
val index = tx.txOut.indexOf(htlcTemplate.txOut)
val tx1 = Transaction(
version = 2,
txIn = TxIn(OutPoint(tx, index), Array.emptyByteArray, sequence = Scripts.locktime2long_csv(htlcTemplate.delay)) :: Nil,
txOut = TxOut(htlcTemplate.amount, Scripts.pay2pkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = Scripts.locktime2long_cltv(htlcTemplate.htlc.add.expiry))
def claimSentHtlc(tx: Transaction, htlcTemplate: HTLCTemplate, privateKey: BinaryData): Transaction = {
val index = tx.txOut.indexOf(htlcTemplate.txOut)
val tx1 = Transaction(
version = 2,
txIn = TxIn(OutPoint(tx, index), Array.emptyByteArray, sequence = OldScripts.toSelfDelay2csv(htlcTemplate.delay)) :: Nil,
txOut = TxOut(htlcTemplate.amount, OldScripts.pay2pkh(Crypto.publicKeyFromPrivateKey(privateKey))) :: Nil,
lockTime = ??? /*Scripts.locktime2long_cltv(htlcTemplate.htlc.add.expiry)*/)
val sig = Transaction.signInput(tx1, 0, htlcTemplate.redeemScript, SIGHASH_ALL, htlcTemplate.amount, 1, privateKey)
val witness = ScriptWitness(sig :: Hash.Zeroes :: htlcTemplate.redeemScript :: Nil)
tx1.updateWitness(0, witness)
}
val sig = Transaction.signInput(tx1, 0, htlcTemplate.redeemScript, SIGHASH_ALL, htlcTemplate.amount, 1, privateKey)
val witness = ScriptWitness(sig :: Hash.Zeroes :: htlcTemplate.redeemScript :: Nil)
tx1.updateWitness(0, witness)
}
def claimSentHtlcs(tx: Transaction, txTemplate: TxTemplate, commitments: Commitments): Seq[Transaction] = {
// TODO: fix this!
def claimSentHtlcs(tx: Transaction, txTemplate: CommitTxTemplate, commitments: Commitments): Seq[Transaction] = Nil
/*{
// txTemplate could be our template (we published our commit tx) or their template (they published their commit tx)
val htlcs1 = txTemplate.htlcSent.filter(_.ourKey == commitments.ourParams.finalPubKey)
val htlcs2 = txTemplate.htlcReceived.filter(_.theirKey == commitments.ourParams.finalPubKey)
val htlcs = htlcs1 ++ htlcs2
htlcs.map(htlcTemplate => claimSentHtlc(tx, htlcTemplate, commitments.ourParams.finalPrivKey))
}*/
}
}
}

View file

@ -2,9 +2,10 @@ package fr.acinq.eclair.channel
import akka.actor._
import akka.util.Timeout
import fr.acinq.bitcoin.{BinaryData, DeterministicWallet, Satoshi}
import fr.acinq.eclair.io.AuthHandler
import fr.acinq.bitcoin.{BinaryData, Crypto, DeterministicWallet, Satoshi, Script}
import fr.acinq.eclair.Globals
import fr.acinq.eclair.io.AuthHandler
import fr.acinq.eclair.transactions.OldScripts
import scala.concurrent.duration._
@ -35,11 +36,25 @@ class Register(blockchain: ActorRef, paymentHandler: ActorRef) extends Actor wit
def receive: Receive = main(0L)
def main(counter: Long): Receive = {
case CreateChannel(connection, amount) =>
val commit_priv = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, 0L :: counter :: Nil)
val final_priv = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, 1L :: counter :: Nil)
val params = OurChannelParams(Globals.default_locktime, commit_priv.secretkey :+ 1.toByte, final_priv.secretkey :+ 1.toByte, Globals.default_mindepth, Globals.commit_fee, Globals.Node.seed, amount, Some(Globals.autosign_interval))
val channel = context.actorOf(AuthHandler.props(connection, blockchain, paymentHandler, params), name = s"auth-handler-${counter}")
case CreateChannel(connection, amount_opt) =>
def generateKey(index: Long): BinaryData = DeterministicWallet.derivePrivateKey(Globals.Node.extendedPrivateKey, index :: counter :: Nil).secretkey
val localParams = LocalParams(
dustLimitSatoshis = 542,
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = 10000,
toSelfDelay = 144,
maxAcceptedHtlcs = 100,
fundingPrivkey = generateKey(0),
revocationSecret = generateKey(1),
paymentSecret = generateKey(2),
delayedPaymentKey = generateKey(3),
finalPrivKey = generateKey(4),
shaSeed = Globals.Node.seed
)
val init = amount_opt.map(amount => Left(INPUT_INIT_FUNDER(amount.amount, 0))).getOrElse(Right(INPUT_INIT_FUNDEE()))
val channel = context.actorOf(AuthHandler.props(connection, blockchain, paymentHandler, localParams, init), name = s"auth-handler-${counter}")
context.become(main(counter + 1))
case ListChannels => sender ! context.children
case SendCommand(channelId, cmd) =>
@ -71,7 +86,7 @@ object Register {
* 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")
context.actorOf(Props(new AliasActor(context.self)), name = s"$node_id-$anchor_id")
def actorPathToNodeId(system: ActorSystem, nodeId: BinaryData): ActorPath =
system / "register" / "auth-handler-*" / "channel" / s"${nodeId}-*"

View file

@ -0,0 +1,22 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{BinaryData, Transaction}
/**
* Created by PM on 06/12/2016.
*/
trait TxDb {
def add(parentId: BinaryData, spending: Transaction): Unit
def get(parentId: BinaryData): Option[Transaction]
}
class BasicTxDb extends TxDb {
val db = collection.mutable.HashMap.empty[BinaryData, Transaction]
override def add(parentId: BinaryData, spending: Transaction): Unit = {
db += parentId -> spending
}
override def get(parentId: BinaryData): Option[Transaction] = db.get(parentId)
}

View file

@ -23,7 +23,7 @@ import scala.annotation.tailrec
case class Encryptor(key: BinaryData, nonce: Long)
object Encryptor {
def encrypt(encryptor: Encryptor, data: BinaryData) : (Encryptor, BinaryData) = {
def encrypt(encryptor: Encryptor, data: BinaryData): (Encryptor, BinaryData) = {
val header = Protocol.writeUInt32(data.length)
val (ciphertext1, mac1) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce), header, Array.emptyByteArray)
val (ciphertext2, mac2) = AeadChacha20Poly1305.encrypt(encryptor.key, Protocol.writeUInt64(encryptor.nonce + 1), data, Array.emptyByteArray)

View file

@ -0,0 +1,75 @@
package fr.acinq.eclair.crypto
import java.math.BigInteger
import fr.acinq.bitcoin.{BinaryData, Crypto}
import org.bouncycastle.math.ec.ECPoint
/**
* Created by PM on 07/12/2016.
*/
object Generators {
def fixSize(data: BinaryData): BinaryData = data.length match {
case 32 => data
case length if length < 32 => Array.fill(32 - length)(0.toByte) ++ data
}
case class Scalar(data: BinaryData) {
require(data.length == 32)
def point = Point(Crypto.publicKeyFromPrivateKey(data :+ 1.toByte))
def bigInteger: BigInteger = new BigInteger(1, data)
def add(scalar: Scalar): Scalar = Scalar(bigInteger.add(scalar.bigInteger))
def multiply(scalar: Scalar): Scalar = Scalar(bigInteger.multiply(scalar.bigInteger).mod(Crypto.curve.getN))
}
object Scalar {
def apply(value: BigInteger): Scalar = new Scalar(fixSize(value.toByteArray.dropWhile(_ == 0)))
}
case class Point(data: BinaryData) {
require(data.length == 33)
def ecPoint: ECPoint = Crypto.curve.getCurve.decodePoint(data)
def add(point: Point): Point = Point(ecPoint.add(point.ecPoint))
def multiply(scalar: Scalar): Point = Point(ecPoint.multiply(scalar.bigInteger))
}
object Point {
def apply(ecPoint: ECPoint): Point = new Point(ecPoint.getEncoded(true))
}
def perCommitSecret(seed: BinaryData, index: Int): Scalar = ShaChain.shaChainFromSeed(seed, index)
def perCommitPoint(seed: BinaryData, index: Int): Point = perCommitSecret(seed, index).point
def derivePrivKey(secret: Scalar, perCommitPoint: Point): Scalar = {
// secretkey = basepoint-secret + SHA256(per-commitment-point || basepoint)
secret.add(Scalar(Crypto.sha256(perCommitPoint.data ++ secret.point.data)))
}
def derivePubKey(basePoint: Point, perCommitPoint: Point): Point = {
//pubkey = basepoint + SHA256(per-commitment-point || basepoint)*G
val a = Scalar(Crypto.sha256(perCommitPoint.data ++ basePoint.data))
Point(basePoint.ecPoint.add(Crypto.curve.getG.multiply(a.bigInteger)))
}
def revocationPubKey(basePoint: Point, perCommitPoint: Point): Point = {
val a = Scalar(Crypto.sha256(basePoint.data ++ perCommitPoint.data))
val b = Scalar(Crypto.sha256(perCommitPoint.data ++ basePoint.data))
basePoint.multiply(a).add(perCommitPoint.multiply(b))
}
def revocationPrivKey(secret: Scalar, perCommitSecret: Scalar): Scalar = {
val a = Scalar(Crypto.sha256(secret.point.data ++ perCommitSecret.point.data))
val b = Scalar(Crypto.sha256(perCommitSecret.point.data ++ secret.point.data))
secret.multiply(a).add(perCommitSecret.multiply(b))
}
}

View file

@ -1,7 +1,7 @@
package fr.acinq.eclair.crypto
import java.math.BigInteger
import java.security.{SecureRandom, Security}
import java.security.Security
import javax.crypto.Cipher
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}
@ -16,12 +16,14 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider
import scala.util.Random
/**
* Created by PM on 27/10/2015.
*/
* Created by PM on 27/10/2015.
*/
object LightningCrypto {
Security.addProvider(new BouncyCastleProvider())
def sha256(bin: BinaryData): BinaryData = Crypto.sha256(bin)
def ecdh(pub: BinaryData, priv: BinaryData): BinaryData = {
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
val pubPoint = ecSpec.getCurve.decodePoint(pub)
@ -55,7 +57,7 @@ object LightningCrypto {
out
}
def chacha20Encrypt(plaintext: BinaryData, key: BinaryData, nonce: BinaryData, counter: Int = 0) : BinaryData = {
def chacha20Encrypt(plaintext: BinaryData, key: BinaryData, nonce: BinaryData, counter: Int = 0): BinaryData = {
val engine = new ChaChaEngine(20)
engine.init(true, new ParametersWithIV(new KeyParameter(key), nonce))
val ciphertext: BinaryData = new Array[Byte](plaintext.length)
@ -72,7 +74,7 @@ object LightningCrypto {
ciphertext
}
def chacha20Decrypt(ciphertext: BinaryData, key: BinaryData, nonce: BinaryData, counter: Int = 0) : BinaryData = {
def chacha20Decrypt(ciphertext: BinaryData, key: BinaryData, nonce: BinaryData, counter: Int = 0): BinaryData = {
val engine = new ChaChaEngine(20)
engine.init(false, new ParametersWithIV(new KeyParameter(key), nonce))
val plaintext: BinaryData = new Array[Byte](ciphertext.length)
@ -91,14 +93,14 @@ object LightningCrypto {
def poly1305KenGen(key: BinaryData, nonce: BinaryData): BinaryData = chacha20Encrypt(new Array[Byte](32), key, nonce)
def pad16(data: Seq[Byte]) : Seq[Byte] =
def pad16(data: Seq[Byte]): Seq[Byte] =
if (data.size % 16 == 0)
Seq.empty[Byte]
else
Seq.fill[Byte](16 - (data.size % 16))(0)
object AeadChacha20Poly1305 {
def encrypt(key: BinaryData, nonce: BinaryData, plaintext: BinaryData, aad: BinaryData) : (BinaryData, BinaryData) = {
def encrypt(key: BinaryData, nonce: BinaryData, plaintext: BinaryData, aad: BinaryData): (BinaryData, BinaryData) = {
val polykey: BinaryData = poly1305KenGen(key, nonce)
val ciphertext = chacha20Encrypt(plaintext, key, nonce, 1)
val data = aad ++ Protocol.writeUInt64(aad.length) ++ ciphertext ++ Protocol.writeUInt64(ciphertext.length)
@ -106,7 +108,7 @@ object LightningCrypto {
(ciphertext, tag)
}
def decrypt(key: BinaryData, nonce: BinaryData, ciphertext: BinaryData, aad: BinaryData, mac: BinaryData) : BinaryData = {
def decrypt(key: BinaryData, nonce: BinaryData, ciphertext: BinaryData, aad: BinaryData, mac: BinaryData): BinaryData = {
val polykey: BinaryData = poly1305KenGen(key, nonce)
val data = aad ++ Protocol.writeUInt64(aad.length) ++ ciphertext ++ Protocol.writeUInt64(ciphertext.length)
val tag = poly1305(polykey, data)

View file

@ -1,7 +1,5 @@
package fr.acinq.eclair.crypto
import java.security.SecureRandom
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.crypto.LightningCrypto._
@ -9,22 +7,22 @@ import scala.util.Random
/**
* Created by PM on 14/10/2015.
*/
* Created by PM on 14/10/2015.
*/
object Onion extends App {
lazy val zeroes: BinaryData = Array.fill(192)(0:Byte)
lazy val zeroes: BinaryData = Array.fill(192)(0: Byte)
def encPad(secrets: Secrets)(input: BinaryData) : BinaryData = aesEncrypt(input, secrets.aes_key, secrets.pad_iv)
def encPad(secrets: Secrets)(input: BinaryData): BinaryData = aesEncrypt(input, secrets.aes_key, secrets.pad_iv)
def decPad(secrets: Secrets)(input: BinaryData) : BinaryData = aesDecrypt(input, secrets.aes_key, secrets.pad_iv)
def decPad(secrets: Secrets)(input: BinaryData): BinaryData = aesDecrypt(input, secrets.aes_key, secrets.pad_iv)
def encMsg(secrets: Secrets)(input: BinaryData) : BinaryData = aesEncrypt(input, secrets.aes_key, secrets.iv)
def encMsg(secrets: Secrets)(input: BinaryData): BinaryData = aesEncrypt(input, secrets.aes_key, secrets.iv)
def decMsg(secrets: Secrets)(input: BinaryData) : BinaryData = aesDecrypt(input, secrets.aes_key, secrets.iv)
def decMsg(secrets: Secrets)(input: BinaryData): BinaryData = aesDecrypt(input, secrets.aes_key, secrets.iv)
def hmac(secrets: Secrets)(input: BinaryData) : BinaryData = hmac256(secrets.hmac_key, input)
def hmac(secrets: Secrets)(input: BinaryData): BinaryData = hmac256(secrets.hmac_key, input)
def makeLastMessage(clientPrivateKeys: Seq[BinaryData], nodePublicKeys: Seq[BinaryData], plaintext: Seq[BinaryData]) : BinaryData = {
def makeLastMessage(clientPrivateKeys: Seq[BinaryData], nodePublicKeys: Seq[BinaryData], plaintext: Seq[BinaryData]): BinaryData = {
val size = clientPrivateKeys.length
val secrets = for (i <- 0 until size) yield generate_secrets(ecdh(nodePublicKeys(i), clientPrivateKeys(i)))
var padding: BinaryData = Array.empty[Byte]
@ -37,14 +35,14 @@ object Onion extends App {
unsigned ++ hmac(secrets.last)(unsigned)
}
def makePreviousMessage(nextMessage: BinaryData, secret: Secrets, clientPrivateKey: BinaryData, plaintext: BinaryData) : BinaryData = {
def makePreviousMessage(nextMessage: BinaryData, secret: Secrets, clientPrivateKey: BinaryData, plaintext: BinaryData): BinaryData = {
val encrypted = encMsg(secret)(nextMessage.drop(192) ++ plaintext)
val pub: BinaryData = Crypto.publicKeyFromPrivateKey(clientPrivateKey :+ 1.toByte)
val unsigned = encrypted ++ pub.drop(1)
unsigned ++ hmac(secret)(unsigned)
}
def makeFirstMessage(clientPrivateKeys: Seq[BinaryData], nodePublicKeys: Seq[BinaryData], plaintext: Seq[BinaryData]) : BinaryData = {
def makeFirstMessage(clientPrivateKeys: Seq[BinaryData], nodePublicKeys: Seq[BinaryData], plaintext: Seq[BinaryData]): BinaryData = {
val size = clientPrivateKeys.length
val secrets = for (i <- 0 until size) yield generate_secrets(ecdh(nodePublicKeys(i), clientPrivateKeys(i)))
val lastMessage = makeLastMessage(clientPrivateKeys, nodePublicKeys, plaintext)
@ -66,7 +64,7 @@ object Onion extends App {
val sig2 = hmac256(secrets.hmac_key, buf.dropRight(32))
if (!sig.data.sameElements(sig2.data)) throw new RuntimeException("sig mismatch!")
val decrypted:BinaryData = aesDecrypt(buf.dropRight(64).toArray, secrets.aes_key, secrets.iv)
val decrypted: BinaryData = aesDecrypt(buf.dropRight(64).toArray, secrets.aes_key, secrets.iv)
val payload = decrypted.takeRight(128)
val payloadstring = new String(payload.toArray)
val newmsg = aesEncrypt(Array.fill[Byte](192)(0x00), secrets.aes_key, secrets.pad_iv) ++ decrypted.dropRight(128)

View file

@ -9,7 +9,9 @@ import scala.annotation.tailrec
*/
object ShaChain {
case class Node(value: BinaryData, height: Int, parent: Option[Node])
case class Node(value: BinaryData, height: Int, parent: Option[Node]) {
require(value.length == 32)
}
def flip(in: BinaryData, index: Int): BinaryData = in.data.updated(index / 8, (in.data(index / 8) ^ (1 << index % 8)).toByte)

View file

@ -5,9 +5,9 @@ import javafx.application.{Application, Platform}
import javafx.beans.value.{ChangeListener, ObservableValue}
import javafx.event.EventHandler
import javafx.fxml.FXMLLoader
import javafx.scene.{Group, Parent, Scene}
import javafx.scene.image.Image
import javafx.scene.text.Text
import javafx.scene.{Group, Parent, Scene}
import javafx.stage.{Modality, Stage, StageStyle, WindowEvent}
import akka.actor.Props

View file

@ -21,11 +21,12 @@ import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
/**
* Created by PM on 16/08/2016.
*/
class GUIUpdater(primaryStage: Stage, mainController:MainController, setup: Setup) extends Actor with ActorLogging {
class GUIUpdater(primaryStage: Stage, mainController: MainController, setup: Setup) extends Actor with ActorLogging {
class NamedEdge(val id: BinaryData) extends DefaultEdge {
override def toString: String = s"${id.toString.take(8)}..."
}
val graph = new SimpleGraph[BinaryData, NamedEdge](classOf[NamedEdge])
graph.addVertex(Globals.Node.publicKey)
@ -42,7 +43,7 @@ class GUIUpdater(primaryStage: Stage, mainController:MainController, setup: Setu
val root = loader.load[VBox]
channelPaneController.nodeId.setText(s"$theirNodeId")
channelPaneController.funder.setText(params.anchorAmount.map(_ => "Yes").getOrElse("No"))
channelPaneController.funder.setText("(deprecated)")
channelPaneController.close.setOnAction(new EventHandler[ActionEvent] {
override def handle(event: ActionEvent): Unit = channel ! CMD_CLOSE(None)
})
@ -78,10 +79,10 @@ class GUIUpdater(primaryStage: Stage, mainController:MainController, setup: Setu
case ChannelSignatureReceived(channel, commitments) =>
val channelPane = m(channel)
val bal = commitments.ourCommit.spec.amount_us_msat.toDouble / (commitments.ourCommit.spec.amount_us_msat.toDouble + commitments.ourCommit.spec.amount_them_msat.toDouble)
val bal = commitments.localCommit.spec.to_local_msat.toDouble / (commitments.localCommit.spec.to_local_msat.toDouble + commitments.localCommit.spec.to_remote_msat.toDouble)
Platform.runLater(new Runnable() {
override def run(): Unit = {
channelPane.amountUs.setText(s"${satoshi2millibtc(Satoshi(commitments.ourCommit.spec.amount_us_msat / 1000L)).amount}")
channelPane.amountUs.setText(s"${satoshi2millibtc(Satoshi(commitments.localCommit.spec.to_local_msat / 1000L)).amount}")
channelPane.balanceBar.setProgress(bal)
}
})

View file

@ -5,8 +5,8 @@ import javafx.application.Platform
import javafx.scene.control.{TextArea, TextField}
import fr.acinq.bitcoin.{BinaryData, Satoshi}
import fr.acinq.eclair.io.Client
import fr.acinq.eclair._
import fr.acinq.eclair.io.Client
import fr.acinq.eclair.payment.CreatePayment
import grizzled.slf4j.Logging

View file

@ -1,6 +1,5 @@
package fr.acinq.eclair.gui.controllers
import javafx.event.ActionEvent
import javafx.fxml.FXML
import javafx.scene.control.{Button, ContextMenu, Label, ProgressBar}
import javafx.scene.input.ContextMenuEvent

View file

@ -3,19 +3,16 @@ package fr.acinq.eclair.gui.controllers
import javafx.beans.value.{ChangeListener, ObservableValue}
import javafx.embed.swing.SwingNode
import javafx.fxml.FXML
import javafx.scene.Parent
import javafx.scene.control.{ContextMenu, Label, MenuItem, Tab}
import javafx.scene.input.ContextMenuEvent
import javafx.scene.layout.{BorderPane, HBox, TilePane, VBox}
import javafx.scene.layout.{BorderPane, TilePane, VBox}
import javafx.stage.Stage
import javafx.scene.Node
import com.mxgraph.swing.mxGraphComponent
import com.sun.javafx.scene.control.skin.LabeledText
import fr.acinq.eclair.{Globals, Setup}
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.{Globals, Setup}
import grizzled.slf4j.Logging
/**
@ -111,7 +108,7 @@ class MainController(val handlers: Handlers, val stage: Stage, val setup: Setup)
}
@FXML def handleNodeIdContext(event: ContextMenuEvent): Unit = {
contextMenu.show(labelNodeId, event.getScreenX, event.getScreenY)
contextMenu.show(labelNodeId, event.getScreenX, event.getScreenY)
}
def positionAtCenter(childStage: Stage): Unit = {

View file

@ -40,7 +40,8 @@ class OpenChannelController(val handlers: Handlers, val stage: Stage, val setup:
stage.close()
}
}
@FXML def handleClose (event: ActionEvent): Unit = {
@FXML def handleClose(event: ActionEvent): Unit = {
stage.close()
}
}

View file

@ -15,26 +15,27 @@ import scala.util.{Success, Try}
/**
* Created by DPA on 23/09/2016.
*/
class ReceivePaymentController(val handlers:Handlers, val stage:Stage, val setup:Setup) extends BaseController with Logging {
class ReceivePaymentController(val handlers: Handlers, val stage: Stage, val setup: Setup) extends BaseController with Logging {
@FXML var amount:TextField = _
@FXML var amountError:Label = _
@FXML var amount: TextField = _
@FXML var amountError: Label = _
// this field is generated and readonly
@FXML var paymentRequest:TextArea = _
@FXML var paymentRequest: TextArea = _
@FXML def initialize (): Unit = {
@FXML def initialize(): Unit = {
}
@FXML def handleGenerate (event: ActionEvent): Unit = {
@FXML def handleGenerate(event: ActionEvent): Unit = {
if (GUIValidators.validate(amount.getText, amountError, GUIValidators.amountRegex)) {
Try(amount.getText.toLong) match {
case Success (amountMsat) => handlers.getPaymentRequest(amount.getText.toLong, paymentRequest)
case Success(amountMsat) => handlers.getPaymentRequest(amount.getText.toLong, paymentRequest)
case _ => {}
}
}
}
@FXML def handleClose (event: ActionEvent): Unit = {
@FXML def handleClose(event: ActionEvent): Unit = {
stage.close()
}
}

View file

@ -15,15 +15,15 @@ import grizzled.slf4j.Logging
/**
* Created by DPA on 23/09/2016.
*/
class SendPaymentController(val handlers:Handlers, val stage:Stage, val setup:Setup) extends BaseController with Logging {
class SendPaymentController(val handlers: Handlers, val stage: Stage, val setup: Setup) extends BaseController with Logging {
@FXML var paymentRequest:TextArea = _
@FXML var paymentRequestError:Label = _
@FXML var nodeIdLabel:Label = _
@FXML var amountLabel:Label = _
@FXML var hashLabel:Label = _
@FXML var paymentRequest: TextArea = _
@FXML var paymentRequestError: Label = _
@FXML var nodeIdLabel: Label = _
@FXML var amountLabel: Label = _
@FXML var hashLabel: Label = _
@FXML def initialize (): Unit = {
@FXML def initialize(): Unit = {
paymentRequest.textProperty().addListener(new ChangeListener[String] {
def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String): Unit = {
@ -41,7 +41,7 @@ class SendPaymentController(val handlers:Handlers, val stage:Stage, val setup:Se
})
}
@FXML def handleSend (event: ActionEvent): Unit = {
@FXML def handleSend(event: ActionEvent): Unit = {
if (GUIValidators.validate(paymentRequest.getText, paymentRequestError, GUIValidators.paymentRequestRegex)) {
val Array(nodeId, amount, hash) = paymentRequest.getText.split(":")
handlers.send(nodeId, hash, amount)
@ -50,7 +50,7 @@ class SendPaymentController(val handlers:Handlers, val stage:Stage, val setup:Se
}
@FXML def handleClose (event: ActionEvent): Unit = {
@FXML def handleClose(event: ActionEvent): Unit = {
stage.close()
}
}

View file

@ -1,6 +1,5 @@
package fr.acinq.eclair.gui.stages
import javafx.application.HostServices
import javafx.fxml.FXMLLoader
import javafx.scene.image.Image
import javafx.scene.{Parent, Scene}

View file

@ -12,7 +12,7 @@ import fr.acinq.eclair.gui.controllers.OpenChannelController
/**
* Created by PM on 16/08/2016.
*/
class OpenChannelStage(handlers: Handlers, setup:Setup) extends Stage() {
class OpenChannelStage(handlers: Handlers, setup: Setup) extends Stage() {
initModality(Modality.WINDOW_MODAL)
initStyle(StageStyle.DECORATED)
getIcons().add(new Image("/gui/commons/images/eclair02.png", false))

View file

@ -12,7 +12,7 @@ import fr.acinq.eclair.gui.controllers.ReceivePaymentController
/**
* Created by PM on 16/08/2016.
*/
class ReceivePaymentStage(handlers: Handlers, setup:Setup) extends Stage() {
class ReceivePaymentStage(handlers: Handlers, setup: Setup) extends Stage() {
initModality(Modality.WINDOW_MODAL)
initStyle(StageStyle.DECORATED)
getIcons().add(new Image("/gui/commons/images/eclair02.png", false))

View file

@ -13,7 +13,7 @@ import grizzled.slf4j.Logging
/**
* Created by PM on 16/08/2016.
*/
class SendPaymentStage(handlers: Handlers, setup:Setup) extends Stage() with Logging {
class SendPaymentStage(handlers: Handlers, setup: Setup) extends Stage() with Logging {
initModality(Modality.WINDOW_MODAL)
initStyle(StageStyle.DECORATED)
getIcons().add(new Image("/gui/commons/images/eclair02.png", false))

View file

@ -12,6 +12,7 @@ object ContextMenuUtils {
/**
* Builds a Context Menu with a single Copy action.
*
* @param valueToCopy the value to copy to clipboard
* @return javafx context menu
*/

View file

@ -5,14 +5,16 @@ import javax.crypto.Cipher
import akka.actor._
import akka.io.Tcp.{ErrorClosed, Received, Register, Write}
import akka.util.ByteString
import com.trueaccord.scalapb.GeneratedMessage
import fr.acinq.bitcoin._
import fr.acinq.eclair._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.{Decryptor, Encryptor}
import fr.acinq.eclair.crypto.LightningCrypto._
import lightning._
import lightning.pkt.Pkt._
import fr.acinq.eclair.crypto.{Decryptor, Encryptor}
import fr.acinq.eclair.wire.Codecs._
import fr.acinq.eclair.wire.{ChannelMessage, Error, LightningMessage}
import lightning.pkt
import lightning.pkt.Pkt.Auth
import scodec.bits.BitVector
import scala.annotation.tailrec
@ -38,7 +40,7 @@ case object IO_NORMAL extends State
// @formatter:on
class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef, our_params: OurChannelParams) extends LoggingFSM[State, Data] with Stash {
class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef, localParams: LocalParams, init: Either[INPUT_INIT_FUNDER, INPUT_INIT_FUNDEE]) extends LoggingFSM[State, Data] with Stash {
val session_key = randomKeyPair()
@ -56,6 +58,8 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef
def send(encryptor: Encryptor, message: pkt): Encryptor = send(encryptor, message.toByteArray)
def send(encryptor: Encryptor, message: LightningMessage): Encryptor = send(encryptor, lightningMessageCodec.encode(message).toOption.get.toByteArray)
startWith(IO_WAITING_FOR_SESSION_KEY_LENGTH, WaitingForKeyLength(ByteString.empty))
when(IO_WAITING_FOR_SESSION_KEY_LENGTH) {
@ -122,8 +126,10 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef
log.error(s"cannot verify peer signature $their_sig for public key $their_nodeid")
context.stop(self)
}
val channel = context.actorOf(Channel.props(self, blockchain, paymentHandler, our_params, their_nodeid.toString()), name = "channel")
val channel = context.actorOf(Channel.props(self, blockchain, paymentHandler, localParams, their_nodeid.toString()), name = "channel")
context.watch(channel)
val msg = if (init.isLeft) init.left else init.right
channel ! msg
goto(IO_NORMAL) using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, bodies = decryptor1.bodies.tail)))
}
}
@ -133,46 +139,22 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef
log.debug(s"received chunk=${BinaryData(chunk)}")
val decryptor1 = Decryptor.add(decryptor, chunk)
decryptor1.bodies.map(plaintext => {
val packet = pkt.parseFrom(plaintext)
self ! packet
// TODO: redo this
val msg = lightningMessageCodec.decode(BitVector(plaintext.data)).toOption.get.value
self ! msg
})
stay using Normal(channel, s.copy(decryptor = decryptor1.copy(header = None, bodies = Vector.empty[BinaryData])))
case Event(packet: pkt, n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) =>
log.debug(s"receiving $packet")
(packet.pkt: @unchecked) match {
case Open(o) => channel ! o
case OpenAnchor(o) => channel ! o
case OpenCommitSig(o) => channel ! o
case OpenComplete(o) => channel ! o
case UpdateAddHtlc(o) => channel ! o
case UpdateFulfillHtlc(o) => channel ! o
case UpdateFailHtlc(o) => channel ! o
case UpdateCommit(o) => channel ! o
case UpdateRevocation(o) => channel ! o
case CloseShutdown(o) => channel ! o
case CloseSignature(o) => channel ! o
case Error(o) => channel ! o
case Event(msg: LightningMessage, n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) if sender == self =>
log.debug(s"receiving $msg")
(msg: @unchecked) match {
case o: ChannelMessage => channel ! o
}
stay
case Event(msg: GeneratedMessage, n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) =>
val packet = (msg: @unchecked) match {
case o: open_channel => pkt(Open(o))
case o: open_anchor => pkt(OpenAnchor(o))
case o: open_commit_sig => pkt(OpenCommitSig(o))
case o: open_complete => pkt(OpenComplete(o))
case o: update_add_htlc => pkt(UpdateAddHtlc(o))
case o: update_fulfill_htlc => pkt(UpdateFulfillHtlc(o))
case o: update_fail_htlc => pkt(UpdateFailHtlc(o))
case o: update_commit => pkt(UpdateCommit(o))
case o: update_revocation => pkt(UpdateRevocation(o))
case o: close_shutdown => pkt(CloseShutdown(o))
case o: close_signature => pkt(CloseSignature(o))
case o: error => pkt(Error(o))
}
log.debug(s"sending $packet")
val encryptor1 = send(encryptor, packet)
case Event(msg: LightningMessage, n@Normal(channel, s@SessionData(theirpub, decryptor, encryptor))) =>
log.debug(s"sending $msg")
val encryptor1 = send(encryptor, msg)
stay using n.copy(sessionData = s.copy(encryptor = encryptor1))
case Event(cmd: Command, n@Normal(channel, _)) =>
@ -181,7 +163,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef
case Event(ErrorClosed(cause), n@Normal(channel, _)) =>
// we transform connection closed events into application error so that it triggers a uniclose
channel ! error(Some(cause))
channel ! Error(0, cause.getBytes())
stay
case Event(Terminated(subject), n@Normal(channel, _)) if subject == channel =>
@ -194,7 +176,7 @@ class AuthHandler(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef
object AuthHandler {
def props(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef, our_params: OurChannelParams) = Props(new AuthHandler(them, blockchain, paymentHandler, our_params))
def props(them: ActorRef, blockchain: ActorRef, paymentHandler: ActorRef, localParams: LocalParams, init: Either[INPUT_INIT_FUNDER, INPUT_INIT_FUNDEE]) = Props(new AuthHandler(them, blockchain, paymentHandler, localParams, init))
case class Secrets(aes_key: BinaryData, hmac_key: BinaryData, aes_iv: BinaryData)

View file

@ -24,7 +24,7 @@ class Client(remote: InetSocketAddress, amount: Satoshi, register: ActorRef) ext
log.info(s"connected to $remote")
val connection = sender()
register ! CreateChannel(connection, Some(amount))
// TODO : kill this actor ?
// TODO: kill this actor ?
}
}

View file

@ -3,69 +3,29 @@ package fr.acinq
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.math.BigInteger
import _root_.lightning._
import com.google.protobuf.ByteString
import fr.acinq.bitcoin._
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
import lightning.{bitcoin_pubkey, signature}
import scala.annotation.tailrec
package object eclair {
implicit def bin2sha256(in: BinaryData): sha256_hash = {
require(in.data.size == 32)
val bis = new ByteArrayInputStream(in)
sha256_hash(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
}
implicit def seq2sha256(in: Seq[Byte]): sha256_hash = {
require(in.data.size == 32)
val bis = new ByteArrayInputStream(in.toArray)
sha256_hash(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
}
implicit def array2sha256(in: Array[Byte]): sha256_hash = bin2sha256(in)
implicit def sha2562bin(in: sha256_hash): BinaryData = {
val bos = new ByteArrayOutputStream()
Protocol.writeUInt64(in.a, bos)
Protocol.writeUInt64(in.b, bos)
Protocol.writeUInt64(in.c, bos)
Protocol.writeUInt64(in.d, bos)
bos.toByteArray
}
implicit def seq2rval(in: Seq[Byte]): rval = {
require(in.data.size == 32)
val bis = new ByteArrayInputStream(in.toArray)
rval(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
}
implicit def bin2rval(in: BinaryData): rval = {
require(in.data.size == 32)
val bis = new ByteArrayInputStream(in)
rval(Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis), Protocol.uint64(bis))
}
implicit def rval2bin(in: rval): BinaryData = {
val bos = new ByteArrayOutputStream()
Protocol.writeUInt64(in.a, bos)
Protocol.writeUInt64(in.b, bos)
Protocol.writeUInt64(in.c, bos)
Protocol.writeUInt64(in.d, bos)
bos.toByteArray
}
implicit def rval2seq(in: rval): Seq[Byte] = rval2bin(in)
// TODO : redundant with above, needed for seamless Crypto.sha256(sha256_hash)
implicit def sha2562seq(in: sha256_hash): Seq[Byte] = sha2562bin(in)
implicit def bin2pubkey(in: BinaryData) = bitcoin_pubkey(ByteString.copyFrom(in))
implicit def array2pubkey(in: Array[Byte]) = bin2pubkey(in)
implicit def pubkey2bin(in: bitcoin_pubkey): BinaryData = in.key.toByteArray
implicit def point2bin(in: Point): BinaryData = in.data
implicit def bin2point(in: BinaryData): Point = Point(in)
implicit def scalar2bin(in: Scalar): BinaryData = in.data
implicit def bin2scalar(in: BinaryData): Scalar = Scalar(in)
private def fixSize(in: Array[Byte]): Array[Byte] = in.size match {
case 32 => in
case s if s < 32 => Array.fill(32 - s)(0: Byte) ++ in
@ -128,9 +88,9 @@ package object eclair {
/**
*
* @param base fixed fee
* @param base fixed fee
* @param proportional proportional fee
* @param msat amount in millisatoshi
* @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

View file

@ -2,9 +2,8 @@ package fr.acinq.eclair.payment
import akka.actor.{Actor, ActorLogging}
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FULFILL_HTLC, CMD_SIGN}
import lightning.update_add_htlc
import fr.acinq.eclair.wire.UpdateAddHtlc
import scala.util.Random
@ -22,9 +21,10 @@ class LocalPaymentHandler extends Actor with ActorLogging {
random.nextBytes(r)
r
}
override def receive: Receive = run(Map())
//TODO: store this map on file ?
// TODO: store this map on file ?
def run(h2r: Map[BinaryData, BinaryData]): Receive = {
case 'genh =>
val r = generateR()
@ -32,14 +32,14 @@ class LocalPaymentHandler extends Actor with ActorLogging {
sender ! h
context.become(run(h2r + (h -> r)))
case htlc: update_add_htlc if h2r.contains(htlc.rHash) =>
val r = h2r(htlc.rHash)
case htlc: UpdateAddHtlc if h2r.contains(htlc.paymentHash) =>
val r = h2r(htlc.paymentHash)
sender ! CMD_SIGN
sender ! CMD_FULFILL_HTLC(htlc.id, r)
sender ! CMD_SIGN
context.become(run(h2r - htlc.rHash))
context.become(run(h2r - htlc.paymentHash))
case htlc: update_add_htlc =>
case htlc: UpdateAddHtlc =>
sender ! CMD_SIGN
sender ! CMD_FAIL_HTLC(htlc.id, "unkown H")
sender ! CMD_SIGN

View file

@ -7,13 +7,12 @@ 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.router._
import lightning.locktime.Locktime.Blocks
import lightning.route_step
import lightning.route_step.Next
import lightning.{locktime, route_step, sha256_hash}
// @formatter:off
case class CreatePayment(amountMsat: Int, h: sha256_hash, targetNodeId: BinaryData)
case class CreatePayment(amountMsat: Int, h: BinaryData, targetNodeId: BinaryData)
sealed trait Data
case class WaitingForRequest(currentBlockCount: Long) extends Data
@ -67,7 +66,7 @@ class PaymentLifecycle(router: ActorRef, selector: ActorRef, initialBlockCount:
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, locktime(Blocks(currentBlockCount.toInt + 100 + route.steps.size - 2)), route.copy(steps = route.steps.tail), commit = true)
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])
@ -81,11 +80,11 @@ class PaymentLifecycle(router: ActorRef, selector: ActorRef, initialBlockCount:
when(WAITING_FOR_PAYMENT_COMPLETE) {
case Event("ok", _) => stay()
case Event(e@PaymentSent(_, h), WaitingForComplete(s, cmd, channel)) if h == cmd.rHash =>
case Event(e@PaymentSent(_, h), WaitingForComplete(s, cmd, channel)) if h == cmd.paymentHash =>
s ! "sent"
stop(FSM.Normal)
case Event(e@PaymentFailed(_, h, reason), WaitingForComplete(s, cmd, channel)) if h == cmd.rHash =>
case Event(e@PaymentFailed(_, h, reason), WaitingForComplete(s, cmd, channel)) if h == cmd.paymentHash =>
s ! Status.Failure(new RuntimeException(reason))
stop(FSM.Failure(reason))
@ -101,7 +100,7 @@ object PaymentLifecycle {
def buildRoute(finalAmountMsat: Int, nodeIds: Seq[BinaryData]): lightning.route = {
// TODO : use actual fee parameters that are specific to each node
// TODO: use actual fee parameters that are specific to each node
def fee(amountMsat: Int) = nodeFee(Globals.base_fee, Globals.proportional_fee, amountMsat).toInt
var amountMsat = finalAmountMsat

View file

@ -18,13 +18,13 @@ class ChannelSelector extends Actor with ActorLogging {
def main(node2channels: Map[BinaryData, Set[ActorRef]], channel2balance: Map[ActorRef, Long]): Receive = {
case ChannelChangedState(channel, theirNodeId, _, NORMAL, d: DATA_NORMAL) =>
val bal = d.commitments.theirCommit.spec.amount_them_msat
val bal = d.commitments.remoteCommit.spec.to_remote_msat
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.theirCommit.spec.amount_them_msat
val bal = commitments.remoteCommit.spec.to_remote_msat
log.info(s"channel $channel now has availableMsat=$bal")
context become main(node2channels, channel2balance + (channel -> bal))

View file

@ -5,7 +5,6 @@ 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.element.ISupportParameter.Network
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
@ -13,8 +12,8 @@ import org.kitteh.irc.client.library.event.helper.ChannelUserListChangeEvent.Cha
import org.kitteh.irc.client.library.event.user.PrivateMessageEvent
import org.kitteh.irc.lib.net.engio.mbassy.listener.Handler
import scala.util.Random
import scala.collection.JavaConversions._
import scala.util.Random
/**
* Created by PM on 25/08/2016.

View file

@ -1,15 +1,13 @@
package fr.acinq.eclair.router
import akka.actor.{Actor, ActorLogging, Status}
import akka.actor.{Actor, ActorLogging}
import akka.pattern.pipe
import fr.acinq.bitcoin.BinaryData
import org.jgrapht.alg.DijkstraShortestPath
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
import scala.concurrent.{ExecutionContext, Future}
import scala.collection.JavaConversions._
import scala.util.{Failure, Success}
import scala.concurrent.{ExecutionContext, Future}
/**
* Created by PM on 24/05/2016.
@ -33,7 +31,7 @@ class Router extends Actor with ActorLogging {
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
case RouteRequest(start, end) => findRoute(start, end, channels) map (RouteResponse(_)) pipeTo sender
}
}

View file

@ -0,0 +1,94 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, TxIn}
import fr.acinq.eclair.channel.{LocalParams, RemoteParams}
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.crypto.Generators.Point
import fr.acinq.eclair.crypto.LightningCrypto.sha256
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc, UpdateMessage}
import fr.acinq.eclair._
/**
* Created by PM on 07/12/2016.
*/
// @formatter:off
sealed trait Direction
case object IN extends Direction
case object OUT extends Direction
// @formatter:on
case class Htlc(direction: Direction, add: UpdateAddHtlc, val previousChannelId: Option[BinaryData])
final case class CommitmentSpec(htlcs: Set[Htlc], feeRate: Long, to_local_msat: Long, to_remote_msat: Long) {
val totalFunds = to_local_msat + to_remote_msat + htlcs.toSeq.map(_.add.amountMsat).sum
}
object CommitmentSpec {
def removeHtlc(changes: List[UpdateMessage], id: Long): List[UpdateMessage] = changes.filterNot(_ match {
case u: UpdateAddHtlc if u.id == id => true
case _ => false
})
def addHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateAddHtlc): CommitmentSpec = {
val htlc = Htlc(direction, update, previousChannelId = None)
direction match {
case OUT => spec.copy(to_local_msat = spec.to_local_msat - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
case IN => spec.copy(to_remote_msat = spec.to_remote_msat - htlc.add.amountMsat, htlcs = spec.htlcs + htlc)
}
}
// OUT means we are sending an UpdateFulfillHtlc message which means that we are fulfilling an HTLC that they sent
def fulfillHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateFulfillHtlc): CommitmentSpec = {
spec.htlcs.find(htlc => htlc.add.id == update.id && htlc.add.paymentHash == sha256(update.paymentPreimage)) match {
case Some(htlc) if direction == OUT => spec.copy(to_local_msat = spec.to_local_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(to_remote_msat = spec.to_remote_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${update.id}")
}
}
// OUT means we are sending an UpdateFailHtlc message which means that we are failing an HTLC that they sent
def failHtlc(spec: CommitmentSpec, direction: Direction, update: UpdateFailHtlc): CommitmentSpec = {
spec.htlcs.find(_.add.id == update.id) match {
case Some(htlc) if direction == OUT => spec.copy(to_remote_msat = spec.to_remote_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case Some(htlc) if direction == IN => spec.copy(to_local_msat = spec.to_local_msat + htlc.add.amountMsat, htlcs = spec.htlcs - htlc)
case None => throw new RuntimeException(s"cannot find htlc id=${update.id}")
}
}
def reduce(ourCommitSpec: CommitmentSpec, localChanges: List[UpdateMessage], remoteChanges: List[UpdateMessage]): CommitmentSpec = {
val spec1 = localChanges.foldLeft(ourCommitSpec) {
case (spec, u: UpdateAddHtlc) => addHtlc(spec, OUT, u)
case (spec, _) => spec
}
val spec2 = remoteChanges.foldLeft(spec1) {
case (spec, u: UpdateAddHtlc) => addHtlc(spec, IN, u)
case (spec, _) => spec
}
val spec3 = localChanges.foldLeft(spec2) {
case (spec, u: UpdateFulfillHtlc) => fulfillHtlc(spec, OUT, u)
case (spec, u: UpdateFailHtlc) => failHtlc(spec, OUT, u)
case (spec, _) => spec
}
val spec4 = remoteChanges.foldLeft(spec3) {
case (spec, u: UpdateFulfillHtlc) => fulfillHtlc(spec, IN, u)
case (spec, u: UpdateFailHtlc) => failHtlc(spec, IN, u)
case (spec, _) => spec
}
spec4
}
def makeLocalTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], localPerCommitmentPoint: Point, spec: CommitmentSpec): CommitTxTemplate = {
val localPubkey = Generators.derivePubKey(localParams.delayedPaymentKey.point, localPerCommitmentPoint)
val remotePubkey = Generators.derivePubKey(remoteParams.paymentBasepoint, localPerCommitmentPoint)
val localRevocationPubkey = Generators.revocationPubKey(localParams.revocationSecret.point, localPerCommitmentPoint)
CommitTxTemplate.makeCommitTxTemplate(inputs, localRevocationPubkey, localParams.toSelfDelay, localPubkey, remotePubkey, spec)
}
def makeRemoteTxTemplate(localParams: LocalParams, remoteParams: RemoteParams, inputs: Seq[TxIn], remotePerCommitmentPoint: Point, spec: CommitmentSpec): CommitTxTemplate = {
val localPubkey = Generators.derivePubKey(localParams.paymentSecret.point, remotePerCommitmentPoint)
val remotePubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint)
val remoteRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, remotePerCommitmentPoint)
CommitTxTemplate.makeCommitTxTemplate(inputs, remoteRevocationPubkey, remoteParams.toSelfDelay, remotePubkey, localPubkey, spec)
}
}

View file

@ -1,25 +1,27 @@
package fr.acinq.eclair.channel
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.Crypto._
import fr.acinq.bitcoin._
import fr.acinq.eclair._
import lightning.locktime.Locktime.{Seconds, Blocks}
import lightning.{locktime, update_add_htlc, open_anchor, open_channel}
/**
* Created by PM on 21/01/2016.
*/
object Scripts {
object OldScripts {
def locktime2long_csv(in: locktime): Long = in match {
case locktime(Blocks(blocks)) => blocks
case locktime(Seconds(seconds)) => TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG | (seconds >> TxIn.SEQUENCE_LOCKTIME_GRANULARITY)
}
def toSelfDelay2csv(in: Int): Long = ???
def locktime2long_cltv(in: locktime): Long = in match {
case locktime(Blocks(blocks)) => blocks
case locktime(Seconds(seconds)) => seconds
}
/*in match {
case locktime(Blocks(blocks)) => blocks
case locktime(Seconds(seconds)) => TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG | (seconds >> TxIn.SEQUENCE_LOCKTIME_GRANULARITY)
}*/
def expiry2cltv(in: Long): Long = ???
/*in match {
case locktime(Blocks(blocks)) => blocks
case locktime(Seconds(seconds)) => seconds
}*/
def isLess(a: Seq[Byte], b: Seq[Byte]): Boolean = memcmp(a.dropWhile(_ == 0).toList, b.dropWhile(_ == 0).toList) < 0
@ -73,12 +75,12 @@ object Scripts {
*
* @param pubkey1 public key for A
* @param pubkey2 public key for B
* @param amount anchor tx amount
* @param previousTx tx that will fund the anchor; it * must * be a P2PWPK embedded in a standard P2SH tx: the p2sh
* @param amount funding tx amount
* @param previousTx tx that will fund the funding tx; it * must * be a P2PWPK embedded in a standard P2SH tx: the p2sh
* script is just the P2WPK script for the public key that matches our "key" parameter
* @param outputIndex index of the output in the funding tx
* @param key private key that can redeem the funding tx
* @return a signed anchor tx
* @return a signed funding tx
*/
def makeAnchorTx(pubkey1: BinaryData, pubkey2: BinaryData, amount: Long, previousTx: Transaction, outputIndex: Int, key: BinaryData): (Transaction, Int) = {
val tx = Transaction(version = 2,
@ -158,9 +160,6 @@ object Scripts {
// @formatter:on
}
def makeCommitTx(ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, anchorTxId: BinaryData, anchorOutputIndex: Int, revocationHash: BinaryData, spec: CommitmentSpec): Transaction =
makeCommitTx(inputs = TxIn(OutPoint(anchorTxId, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil, ourFinalKey, theirFinalKey, theirDelay, revocationHash, spec)
def applyFees(amount_us: Satoshi, amount_them: Satoshi, fee: Satoshi) = {
val (amount_us1: Satoshi, amount_them1: Satoshi) = (amount_us, amount_them) match {
case (Satoshi(us), Satoshi(them)) if us >= fee.toLong / 2 && them >= fee.toLong / 2 => (Satoshi(us - fee.toLong / 2), Satoshi(them - fee.toLong / 2))
@ -170,100 +169,11 @@ object Scripts {
(amount_us1, amount_them1)
}
sealed trait OutputTemplate {
def amount: Satoshi
def txOut: TxOut
// this is the actual script that must be used to claim this output
def redeemScript: BinaryData
}
case class HtlcTemplate(htlc: Htlc, ourKey: BinaryData, theirKey: BinaryData, delay: locktime, revocationHash: BinaryData) extends OutputTemplate {
override def amount = Satoshi(htlc.add.amountMsat / 1000)
override def redeemScript = htlc.direction match {
case IN => Script.write(Scripts.scriptPubKeyHtlcReceive(ourKey, theirKey, locktime2long_cltv(htlc.add.expiry), locktime2long_csv(delay), htlc.add.rHash, revocationHash))
case OUT => Script.write(Scripts.scriptPubKeyHtlcSend(ourKey, theirKey, locktime2long_cltv(htlc.add.expiry), locktime2long_csv(delay), htlc.add.rHash, revocationHash))
}
override def txOut = TxOut(amount, pay2wsh(redeemScript))
}
case class P2WSH(amount: Satoshi, script: BinaryData) extends OutputTemplate {
override def txOut: TxOut = TxOut(amount, pay2wsh(script))
override def redeemScript = script
}
object P2WSH {
def apply(amount: Satoshi, script: Seq[ScriptElt]): P2WSH = new P2WSH(amount, Script.write(script))
}
case class P2WPKH(amount: Satoshi, publicKey: BinaryData) extends OutputTemplate {
override def txOut: TxOut = TxOut(amount, pay2wpkh(publicKey))
override def redeemScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(publicKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
}
case class TxTemplate(inputs: Seq[TxIn], ourOutput: Option[OutputTemplate], theirOutput: Option[OutputTemplate], htlcSent: Seq[HtlcTemplate], htlcReceived: Seq[HtlcTemplate]) {
def makeTx: Transaction = {
val outputs = ourOutput.toSeq ++ theirOutput.toSeq ++ htlcSent ++ htlcReceived
val tx = Transaction(
version = 2,
txIn = inputs,
txOut = outputs.map(_.txOut),
lockTime = 0
)
permuteOutputs(tx)
}
/**
*
* @return true is their is an output that we can claim: either our output or any HTLC (even the ones that we sent
* could be claimed by us if the tx is revoked and we have the revocation preimage)
*/
def weHaveAnOutput: Boolean = ourOutput.isDefined || !htlcReceived.isEmpty || !htlcSent.isEmpty
}
def makeCommitTxTemplate(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, commitmentSpec: CommitmentSpec): TxTemplate = {
val redeemScript = redeemSecretOrDelay(ourFinalKey, locktime2long_csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
val htlcs = commitmentSpec.htlcs.filter(_.add.amountMsat >= 546000).toSeq
val fee_msat = computeFee(commitmentSpec.feeRate, htlcs.size) * 1000
val (amount_us_msat: Long, amount_them_msat: Long) = (commitmentSpec.amount_us_msat, commitmentSpec.amount_them_msat) match {
case (us, them) if us >= fee_msat / 2 && them >= fee_msat / 2 => (us - fee_msat / 2, them - fee_msat / 2)
case (us, them) if us < fee_msat / 2 => (0L, Math.max(0L, them - fee_msat + us))
case (us, them) if them < fee_msat / 2 => (Math.max(us - fee_msat + them, 0L), 0L)
}
// our output is a pay2wsh output than can be claimed by them if they know the preimage, or by us after a delay
// when * they * publish a revoked commit tx, we use the preimage that they sent us to claim it
val ourOutput = if (amount_us_msat >= 546000) Some(P2WSH(Satoshi(amount_us_msat / 1000), redeemScript)) else None
// their output is a simple pay2pkh output that sends money to their final key and can only be claimed by them
// when * they * publish a revoked commit tx we don't have anything special to do about it
val theirOutput = if (amount_them_msat >= 546000) Some(P2WPKH(Satoshi(amount_them_msat / 1000), theirFinalKey)) else None
val sendOuts: Seq[HtlcTemplate] = htlcs.filter(_.direction == OUT).map(htlc => {
HtlcTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
val receiveOuts: Seq[HtlcTemplate] = htlcs.filter(_.direction == IN).map(htlc => {
HtlcTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
TxTemplate(inputs, ourOutput, theirOutput, sendOuts, receiveOuts)
}
def makeCommitTx(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: locktime, revocationHash: BinaryData, commitmentSpec: CommitmentSpec): Transaction = {
val txTemplate = makeCommitTxTemplate(inputs, ourFinalKey, theirFinalKey, theirDelay, revocationHash, commitmentSpec)
val tx = txTemplate.makeTx
tx
}
/**
* Create a "final" channel transaction that will be published when the channel is closed
*
* @param inputs inputs to include in the tx. In most cases, there's only one input that points to the output of
* the anchor tx
* the funding tx
* @param ourPubkeyScript our public key script
* @param theirPubkeyScript their public key script
* @param amount_us pay to us
@ -283,7 +193,7 @@ object Scripts {
lockTime = 0))
}
def isFunder(o: open_channel): Boolean = o.anch == open_channel.anchor_offer.WILL_CREATE_ANCHOR
//def isFunder(o: open_channel): Boolean = o.anch == open_channel.anchor_offer.WILL_CREATE_FUNDING
def findPublicKeyScriptIndex(tx: Transaction, publicKeyScript: BinaryData): Option[Int] =
tx.txOut.zipWithIndex.find {
@ -313,6 +223,7 @@ object Scripts {
sequence & TxIn.SEQUENCE_LOCKTIME_MASK
}
}
if (tx.version < 2) 0
else tx.txIn.map(_.sequence).map(sequenceToBlockHeight).max
}

View file

@ -0,0 +1,66 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, OP_2, OP_CHECKLOCKTIMEVERIFY, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_CHECKSIG, OP_DROP, OP_ELSE, OP_ENDIF, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160, OP_IF, OP_NOTIF, OP_PUSHDATA, OP_SIZE, OP_SWAP, Script}
/**
* Created by PM on 02/12/2016.
*/
object OutputScripts {
def toLocal(revocationPubKey: BinaryData, toSelfDelay: Int, localDelayedKey: BinaryData) = {
// @formatter:off
OP_IF ::
OP_PUSHDATA(revocationPubKey) ::
OP_ELSE ::
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
OP_PUSHDATA(localDelayedKey) ::
OP_ENDIF ::
OP_CHECKSIG :: Nil
// @formatter:on
}
def toRemote(remoteKey: BinaryData) = remoteKey
def htlcOffered(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData) = {
// @formatter:off
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
OP_NOTIF ::
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
OP_ELSE ::
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
OP_CHECKSIG ::
OP_ENDIF :: Nil
// @formatter:on
}
def htlcReceived(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData, lockTime: Long) = {
// @formatter:off
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
OP_IF ::
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
OP_ELSE ::
OP_DROP :: OP_PUSHDATA(Script.encodeNumber(lockTime)) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG ::
OP_ENDIF :: Nil
// @formatter:on
}
def htlcSuccessOrTimeout(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = {
// @formatter:off
OP_IF ::
OP_PUSHDATA(revocationPubKey) ::
OP_ELSE ::
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
OP_PUSHDATA(localDelayedKey) ::
OP_ENDIF ::
OP_CHECKSIG :: Nil
// @formatter:on
}
}
object InputScripts {
}

View file

@ -0,0 +1,34 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, SIGHASH_ALL, Satoshi, ScriptFlags, Transaction, TxOut}
import fr.acinq.eclair.channel.{LocalParams, RemoteParams}
import fr.acinq.eclair.crypto.Generators.{Point, Scalar}
import scala.util.Try
/**
* Created by PM on 07/12/2016.
*/
object Signature {
def sign(localParams: LocalParams, remoteParams: RemoteParams, fundingSatoshis: Satoshi, tx: Transaction): BinaryData = {
// this is because by convention in bitcoin-core-speak 32B keys are 'uncompressed' and 33B keys (ending by 0x01) are 'compressed'
val localCompressedFundingPrivkey: Seq[Byte] = localParams.fundingPrivkey.data.toSeq :+ 1.toByte
Transaction.signInput(tx, 0, OldScripts.multiSig2of2(localParams.fundingPrivkey.point, remoteParams.fundingPubkey), SIGHASH_ALL, fundingSatoshis, 1, localCompressedFundingPrivkey)
}
def addSigs(tx: Transaction, localFundingPubkey: Point, remoteFundingPubkey: Point, localSig: BinaryData, remoteSig: BinaryData): Transaction = {
val witness = OldScripts.witness2of2(localSig, remoteSig, localFundingPubkey, remoteFundingPubkey)
tx.updateWitness(0, witness)
}
def checksig(anchorOutput: TxOut, tx: Transaction): Try[Unit] =
Try(Transaction.correctlySpends(tx, Map(tx.txIn(0).outPoint -> anchorOutput), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS))
def signAndCheckSig(localParams: LocalParams, remoteParams: RemoteParams, anchorOutput: TxOut, localPerCommitmentPoint: Point, tx: Transaction, remoteSig: BinaryData): Try[Transaction] = {
val localSig = sign(localParams: LocalParams, remoteParams: RemoteParams, anchorOutput.amount, tx)
val signedTx = addSigs(tx, localParams.fundingPrivkey.point, remoteParams.fundingPubkey, localSig, remoteSig)
checksig(anchorOutput, signedTx).map(_ => signedTx)
}
}

View file

@ -0,0 +1,125 @@
package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, Crypto, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Satoshi, Script, ScriptElt, Transaction, TxIn, TxOut}
import fr.acinq.eclair.transactions.OldScripts._
/**
* Created by PM on 06/12/2016.
*/
sealed trait TxTemplate
case class CommitTxTemplate(inputs: Seq[TxIn], localOutput: Option[OutputTemplate], remoteOutput: Option[OutputTemplate], htlcSent: Seq[HTLCTemplate], htlcReceived: Seq[HTLCTemplate]) extends TxTemplate {
def makeTx: Transaction = {
val outputs = localOutput.toSeq ++ remoteOutput.toSeq ++ htlcSent ++ htlcReceived
val tx = Transaction(
version = 2,
txIn = inputs,
txOut = outputs.map(_.txOut),
lockTime = 0
)
permuteOutputs(tx)
}
/**
*
* @return true is their is an output that we can claim: either our output or any HTLC (even the ones that we sent
* could be claimed by us if the tx is revoked and we have the revocation preimage)
*/
def weHaveAnOutput: Boolean = localOutput.isDefined || !htlcReceived.isEmpty || !htlcSent.isEmpty
}
object CommitTxTemplate {
/**
* Creates a commitment publishable by 'Local' (meaning that main output to local is delayed)
* @param inputs
* @param localRevocationPubkey
* @param toLocalDelay
* @param localPubkey
* @param remotePubkey
* @param spec
* @return
*/
def makeCommitTxTemplate(inputs: Seq[TxIn], localRevocationPubkey: BinaryData, toLocalDelay: Int, localPubkey: BinaryData, remotePubkey: BinaryData, spec: CommitmentSpec): CommitTxTemplate = {
// TODO: no fees!!!
val (toLocal: Satoshi, toRemote: Satoshi) = (Satoshi(spec.to_local_msat / 1000), Satoshi(spec.to_remote_msat / 1000))
val toLocalDelayedOutputScript = OutputScripts.toLocal(localRevocationPubkey, toLocalDelay, localPubkey)
val toLocalDelayedOutput_opt = if (spec.to_local_msat >= 546000) Some(P2WSHTemplate(toLocal, toLocalDelayedOutputScript)) else None
val toRemoteOutputScript = OutputScripts.toRemote(remotePubkey)
val toRemoteOutput_opt = if (spec.to_remote_msat >= 546000) Some(P2WSHTemplate(toRemote, toRemoteOutputScript)) else None
assert(spec.htlcs.isEmpty, "not implemented")
CommitTxTemplate(inputs, toLocalDelayedOutput_opt, toRemoteOutput_opt, Nil, Nil)
}
/*def makeCommitTxTemplate(inputs: Seq[TxIn], ourFinalKey: BinaryData, theirFinalKey: BinaryData, theirDelay: Int, revocationHash: BinaryData, commitmentSpec: CommitmentSpec): CommitTxTemplate = {
val redeemScript = redeemSecretOrDelay(ourFinalKey, toSelfDelay2csv(theirDelay), theirFinalKey, revocationHash: BinaryData)
val htlcs = commitmentSpec.htlcs.filter(_.add.amountMsat >= 546000).toSeq
val fee_msat = computeFee(commitmentSpec.feeRate, htlcs.size) * 1000
val (amount_us_msat: Long, amount_them_msat: Long) = (commitmentSpec.to_local_msat, commitmentSpec.to_remote_msat) match {
case (us, them) if us >= fee_msat / 2 && them >= fee_msat / 2 => (us - fee_msat / 2, them - fee_msat / 2)
case (us, them) if us < fee_msat / 2 => (0L, Math.max(0L, them - fee_msat + us))
case (us, them) if them < fee_msat / 2 => (Math.max(us - fee_msat + them, 0L), 0L)
}
// our output is a pay2wsh output than can be claimed by them if they know the preimage, or by us after a delay
// when * they * publish a revoked commit tx, we use the preimage that they sent us to claim it
val ourOutput = if (amount_us_msat >= 546000) Some(P2WSHTemplate(Satoshi(amount_us_msat / 1000), redeemScript)) else None
// their output is a simple pay2pkh output that sends money to their final key and can only be claimed by them
// when * they * publish a revoked commit tx we don't have anything special to do about it
val theirOutput = if (amount_them_msat >= 546000) Some(P2WPKHTemplate(Satoshi(amount_them_msat / 1000), theirFinalKey)) else None
val sendOuts: Seq[HTLCTemplate] = htlcs.filter(_.direction == OUT).map(htlc => {
HTLCTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
val receiveOuts: Seq[HTLCTemplate] = htlcs.filter(_.direction == IN).map(htlc => {
HTLCTemplate(htlc, ourFinalKey, theirFinalKey, theirDelay, revocationHash)
})
CommitTxTemplate(inputs, ourOutput, theirOutput, sendOuts, receiveOuts)
}*/
}
case class HTLCSuccessTxTemplate() extends TxTemplate
case class HTLCTimeoutTxTemplate() extends TxTemplate
sealed trait OutputTemplate {
def amount: Satoshi
def txOut: TxOut
// this is the actual script that must be used to claim this output
def redeemScript: BinaryData
}
case class HTLCTemplate(htlc: Htlc, ourKey: BinaryData, theirKey: BinaryData, delay: Int, revocationHash: BinaryData) extends OutputTemplate {
override def amount = Satoshi(htlc.add.amountMsat / 1000)
override def redeemScript = htlc.direction match {
case IN => Script.write(OldScripts.scriptPubKeyHtlcReceive(ourKey, theirKey, expiry2cltv(htlc.add.expiry), toSelfDelay2csv(delay), htlc.add.paymentHash, revocationHash))
case OUT => Script.write(OldScripts.scriptPubKeyHtlcSend(ourKey, theirKey, expiry2cltv(htlc.add.expiry), toSelfDelay2csv(delay), htlc.add.paymentHash, revocationHash))
}
override def txOut = TxOut(amount, pay2wsh(redeemScript))
}
case class P2WSHTemplate(amount: Satoshi, script: BinaryData) extends OutputTemplate {
override def txOut: TxOut = TxOut(amount, pay2wsh(script))
override def redeemScript = script
}
object P2WSHTemplate {
def apply(amount: Satoshi, script: Seq[ScriptElt]): P2WSHTemplate = new P2WSHTemplate(amount, Script.write(script))
}
case class P2WPKHTemplate(amount: Satoshi, publicKey: BinaryData) extends OutputTemplate {
override def txOut: TxOut = TxOut(amount, pay2wpkh(publicKey))
override def redeemScript = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Crypto.hash160(publicKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
}

View file

@ -0,0 +1,190 @@
package fr.acinq.eclair.wire
import java.net.{Inet4Address, Inet6Address, InetAddress}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.wire
import scodec.bits.{BitVector, ByteVector, HexStringSyntax}
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
/**
* Created by PM on 15/11/2016.
*/
object Codecs {
// this codec can be safely used for values < 2^63 and will fail otherwise
// (for something smarter see https://github.com/yzernik/bitcoin-scodec/blob/master/src/main/scala/io/github/yzernik/bitcoinscodec/structures/UInt64.scala)
val uint64: Codec[Long] = int64.narrow(l => if (l >= 0) Attempt.Successful(l) else Attempt.failure(Err(s"overflow for value $l")), l => l)
def binarydata(size: Int): Codec[BinaryData] = bytes(size).xmap(d => BinaryData(d.toSeq), d => ByteVector(d.data))
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 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 rgb: Codec[(Byte, Byte, Byte)] = bytes(3).xmap(buf => (buf(0), buf(1), buf(2)), t => ByteVector(t._1, t._2, t._3))
def zeropaddedstring(size: Int): Codec[String] = fixedSizeBytes(32, utf8).xmap(s => s.takeWhile(_ != '\0'), s => s)
val initCodec: Codec[Init] = (
("globalFeatures" | varsizebinarydata) ::
("localFeatures" | varsizebinarydata)).as[Init]
val errorCodec: Codec[Error] = (
("channelId" | int64) ::
("data" | varsizebinarydata)).as[Error]
val openChannelCodec: Codec[OpenChannel] = (
("temporaryChannelId" | int64) ::
("fundingSatoshis" | uint64) ::
("pushMsat" | uint64) ::
("dustLimitSatoshis" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("channelReserveSatoshis" | uint64) ::
("htlcMinimumMsat" | uint32) ::
("feeratePerKw" | uint32) ::
("toSelfDelay" | uint16) ::
("maxAcceptedHtlcs" | uint16) ::
("fundingPubkey" | binarydata(33)) ::
("revocationBasepoint" | binarydata(33)) ::
("paymentBasepoint" | binarydata(33)) ::
("delayedPaymentBasepoint" | binarydata(33)) ::
("firstPerCommitmentPoint" | binarydata(33))).as[OpenChannel]
val acceptChannelCodec: Codec[AcceptChannel] = (
("temporaryChannelId" | int64) ::
("dustLimitSatoshis" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("channelReserveSatoshis" | uint64) ::
("minimumDepth" | uint32) ::
("htlcMinimumMsat" | uint32) ::
("toSelfDelay" | uint16) ::
("maxAcceptedHtlcs" | uint16) ::
("fundingPubkey" | binarydata(33)) ::
("revocationBasepoint" | binarydata(33)) ::
("paymentBasepoint" | binarydata(33)) ::
("delayedPaymentBasepoint" | binarydata(33)) ::
("firstPerCommitmentPoint" | binarydata(33))).as[AcceptChannel]
val fundingCreatedCodec: Codec[FundingCreated] = (
("temporaryChannelId" | int64) ::
("txid" | binarydata(32)) ::
("outputIndex" | uint16) ::
("signature" | binarydata(64))).as[FundingCreated]
val fundingSignedCodec: Codec[FundingSigned] = (
("temporaryChannelId" | int64) ::
("signature" | binarydata(64))).as[FundingSigned]
val fundingLockedCodec: Codec[FundingLocked] = (
("temporaryChannelId" | int64) ::
("channelId" | int64) ::
("announcementNodeSignature" | binarydata(64)) ::
("announcementBitcoinSignature" | binarydata(64)) ::
("nextPerCommitmentPoint" | binarydata(33))).as[FundingLocked]
val shutdownCodec: Codec[wire.Shutdown] = (
("channelId" | int64) ::
("scriptPubKey" | varsizebinarydata)).as[Shutdown]
val closingSignedCodec: Codec[ClosingSigned] = (
("channelId" | int64) ::
("feeSatoshis" | uint64) ::
("signature" | binarydata(64))).as[ClosingSigned]
val updateAddHtlcCodec: Codec[UpdateAddHtlc] = (
("channelId" | int64) ::
("id" | uint64) ::
("amountMsat" | uint32) ::
("expiry" | uint32) ::
("paymentHash" | binarydata(32)) ::
("onionRoutingPacket" | binarydata(1254))).as[UpdateAddHtlc]
val updateFulfillHtlcCodec: Codec[UpdateFulfillHtlc] = (
("channelId" | int64) ::
("id" | uint64) ::
("paymentPreimage" | binarydata(32))).as[UpdateFulfillHtlc]
val updateFailHtlcCodec: Codec[UpdateFailHtlc] = (
("channelId" | int64) ::
("id" | uint64) ::
("reason" | binarydata(154))).as[UpdateFailHtlc]
val commitSigCodec: Codec[CommitSig] = (
("channelId" | int64) ::
("signature" | binarydata(64)) ::
("htlcSignatures" | listofbinarydata(64))).as[CommitSig]
val revokeAndAckCodec: Codec[RevokeAndAck] = (
("channelId" | int64) ::
("perCommitmentSecret" | binarydata(32)) ::
("nextPerCommitmentPoint" | binarydata(33)) ::
("padding" | ignore(3)) ::
("htlcTimeoutSignature" | listofbinarydata(64))
).as[RevokeAndAck]
val updateFeeCodec: Codec[UpdateFee] = (
("channelId" | int64) ::
("feeratePerKw" | uint32)).as[UpdateFee]
val channelAnnouncementCodec: Codec[ChannelAnnouncement] = (
("nodeSignature1" | binarydata(64)) ::
("nodeSignature2" | binarydata(64)) ::
("channelId" | int64) ::
("bitcoinSignature1" | binarydata(64)) ::
("bitcoinSignature2" | binarydata(64)) ::
("nodeId1" | binarydata(33)) ::
("nodeId2" | binarydata(33)) ::
("bitcoinKey1" | binarydata(33)) ::
("bitcoinKey2" | binarydata(33))).as[ChannelAnnouncement]
val nodeAnnouncementCodec: Codec[NodeAnnouncement] = (
("signature" | binarydata(64)) ::
("timestamp" | uint32) ::
("ip" | ipv6) ::
("port" | uint16) ::
("nodeId" | binarydata(33)) ::
("rgbColor" | rgb) ::
("alias" | zeropaddedstring(32))).as[NodeAnnouncement]
val channelUpdateCodec: Codec[ChannelUpdate] = (
("signature" | binarydata(64)) ::
("channelId" | int64) ::
("timestamp" | uint32) ::
("flags" | binarydata(2)) ::
("expiry" | uint16) ::
("htlcMinimumMsat" | uint32) ::
("feeBaseMsat" | uint32) ::
("feeProportionalMillionths" | uint32)).as[ChannelUpdate]
val lightningMessageCodec = discriminated[LightningMessage].by(uint16)
.typecase(16, initCodec)
.typecase(17, errorCodec)
.typecase(32, openChannelCodec)
.typecase(33, acceptChannelCodec)
.typecase(34, fundingCreatedCodec)
.typecase(35, fundingSignedCodec)
.typecase(36, fundingLockedCodec)
.typecase(38, shutdownCodec)
.typecase(39, closingSignedCodec)
.typecase(128, updateAddHtlcCodec)
.typecase(130, updateFulfillHtlcCodec)
.typecase(131, updateFailHtlcCodec)
.typecase(132, commitSigCodec)
.typecase(133, revokeAndAckCodec)
.typecase(134, updateFeeCodec)
.typecase(256, channelAnnouncementCodec)
.typecase(257, nodeAnnouncementCodec)
.typecase(258, channelUpdateCodec)
}

View file

@ -0,0 +1,129 @@
package fr.acinq.eclair.wire
import java.net.{Inet6Address, InetAddress}
import fr.acinq.bitcoin.BinaryData
/**
* Created by PM on 15/11/2016.
*/
// @formatter:off
sealed trait LightningMessage
sealed trait SetupMessage extends LightningMessage
sealed trait ChannelMessage extends LightningMessage
sealed trait HtlcMessage extends LightningMessage
sealed trait UpdateMessage extends HtlcMessage // <- not in the spec
sealed trait RoutingMessage extends LightningMessage
// @formatter:on
case class Init(globalFeatures: BinaryData,
localFeatures: BinaryData) extends SetupMessage
case class Error(channelId: Long,
data: BinaryData) extends SetupMessage
case class OpenChannel(temporaryChannelId: Long,
fundingSatoshis: Long,
pushMsat: Long,
dustLimitSatoshis: Long,
maxHtlcValueInFlightMsat: Long,
channelReserveSatoshis: Long,
htlcMinimumMsat: Long,
feeratePerKw: Long,
toSelfDelay: Int,
maxAcceptedHtlcs: Int,
fundingPubkey: BinaryData,
revocationBasepoint: BinaryData,
paymentBasepoint: BinaryData,
delayedPaymentBasepoint: BinaryData,
firstPerCommitmentPoint: BinaryData) extends ChannelMessage
case class AcceptChannel(temporaryChannelId: Long,
dustLimitSatoshis: Long,
maxHtlcValueInFlightMsat: Long,
channelReserveSatoshis: Long,
minimumDepth: Long,
htlcMinimumMsat: Long,
toSelfDelay: Int,
maxAcceptedHtlcs: Int,
fundingPubkey: BinaryData,
revocationBasepoint: BinaryData,
paymentBasepoint: BinaryData,
delayedPaymentBasepoint: BinaryData,
firstPerCommitmentPoint: BinaryData) extends ChannelMessage
case class FundingCreated(temporaryChannelId: Long,
txid: BinaryData,
outputIndex: Int,
signature: BinaryData) extends ChannelMessage
case class FundingSigned(temporaryChannelId: Long,
signature: BinaryData) extends ChannelMessage
case class FundingLocked(temporaryChannelId: Long,
channelId: Long,
announcementNodeSignature: BinaryData,
announcementBitcoinSignature: BinaryData,
nextPerCommitmentPoint: BinaryData) extends ChannelMessage
case class Shutdown(channelId: Long,
scriptPubKey: BinaryData) extends ChannelMessage
case class ClosingSigned(channelId: Long,
feeSatoshis: Long,
signature: BinaryData) extends ChannelMessage
case class UpdateAddHtlc(channelId: Long,
id: Long,
amountMsat: Long,
expiry: Long,
paymentHash: BinaryData,
onionRoutingPacket: BinaryData) extends HtlcMessage with UpdateMessage
case class UpdateFulfillHtlc(channelId: Long,
id: Long,
paymentPreimage: BinaryData) extends HtlcMessage with UpdateMessage
case class UpdateFailHtlc(channelId: Long,
id: Long,
reason: BinaryData) extends HtlcMessage with UpdateMessage
case class CommitSig(channelId: Long,
signature: BinaryData,
htlcSignatures: List[BinaryData]) extends HtlcMessage
case class RevokeAndAck(channelId: Long,
perCommitmentSecret: BinaryData,
nextPerCommitmentPoint: BinaryData,
htlcTimeoutSignatures: List[BinaryData]) extends HtlcMessage
case class UpdateFee(channelId: Long,
feeratePerKw: Long) extends ChannelMessage with UpdateMessage
case class ChannelAnnouncement(nodeSignature1: BinaryData,
nodeSignature2: BinaryData,
channelId: Long,
bitcoinSignature1: BinaryData,
bitcoinSignature2: BinaryData,
nodeId1: BinaryData,
nodeId2: BinaryData,
bitcoinKey1: BinaryData,
bitcoinKey2: BinaryData) extends RoutingMessage
case class NodeAnnouncement(signature: BinaryData,
timestamp: Long,
ip: InetAddress,
port: Int,
nodeId: BinaryData,
rgbColor: (Byte, Byte, Byte),
alias: String) extends RoutingMessage
case class ChannelUpdate(signature: BinaryData,
channelId: Long,
timestamp: Long,
flags: BinaryData,
expiry: Int,
htlcMinimumMsat: Long,
feeBaseMsat: Long,
feeProportionalMillionths: Long) extends RoutingMessage

View file

@ -0,0 +1,68 @@
package fr.acinq.protos
import fr.acinq.bitcoin._
import fr.acinq.eclair.transactions.OldScripts
object Bolt3 {
// TODO: sort tx according to BIP69 (lexicographical ordering)
def baseSize(tx: Transaction) = Transaction.write(tx, Protocol.PROTOCOL_VERSION | Transaction.SERIALIZE_TRANSACTION_NO_WITNESS).length
def totalSize(tx: Transaction) = Transaction.write(tx, Protocol.PROTOCOL_VERSION).length
def weight(tx: Transaction) = 3 * baseSize(tx) + totalSize(tx)
def fundingScript(pubKey1: BinaryData, pubKey2: BinaryData) = OldScripts.multiSig2of2(pubKey1, pubKey2)
def toLocal(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = {
// @formatter:off
OP_IF ::
OP_PUSHDATA(revocationPubKey) ::
OP_ELSE ::
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
OP_PUSHDATA(localDelayedKey) ::
OP_ENDIF ::
OP_CHECKSIG :: Nil
// @formatter:on
}
def toRemote(remoteKey: BinaryData) = remoteKey
def htlcOffered(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData) = {
// @formatter:off
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
OP_NOTIF ::
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
OP_ELSE ::
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
OP_CHECKSIG ::
OP_ENDIF :: Nil
// @formatter:on
}
def htlcReceived(localKey: BinaryData, remoteKey: BinaryData, paymentHash: BinaryData, lockTime: Long) = {
// @formatter:off
OP_PUSHDATA(remoteKey) :: OP_SWAP ::
OP_SIZE :: OP_PUSHDATA(Script.encodeNumber(32)) :: OP_EQUAL ::
OP_IF ::
OP_HASH160 :: OP_PUSHDATA(paymentHash) :: OP_EQUALVERIFY ::
OP_2 :: OP_SWAP :: OP_PUSHDATA(localKey) :: OP_2 :: OP_CHECKMULTISIG ::
OP_ELSE ::
OP_DROP :: OP_PUSHDATA(Script.encodeNumber(lockTime)) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP :: OP_CHECKSIG ::
OP_ENDIF :: Nil
// @formatter:on
}
def htlcSuccessOrTimeout(revocationPubKey: BinaryData, toSelfDelay: Long, localDelayedKey: BinaryData) = {
// @formatter:off
OP_IF ::
OP_PUSHDATA(revocationPubKey) ::
OP_ELSE ::
OP_PUSHDATA(Script.encodeNumber(toSelfDelay)) :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
OP_PUSHDATA(localDelayedKey) ::
OP_ENDIF ::
OP_CHECKSIG :: Nil
// @formatter:on
}
}

View file

@ -95,7 +95,7 @@ object NewChannel extends App {
val pipe = system.actorOf(Props(new TestPipe()))
val a = system.actorOf(Props(new ChannelMock(pipe)), name = "a")
val b = system.actorOf(Props(new ChannelMock(pipe)), name = "b")
pipe !(a, b, new File("eclair-node/rusty-scripts/15-fee-twice-back-to-back.script"))
pipe ! (a, b, new File("eclair-node/rusty-scripts/15-fee-twice-back-to-back.script"))
}
@ -118,6 +118,7 @@ class TestPipe() extends Actor with ActorLogging with Stash {
def exec(script: List[String], a: ActorRef, b: ActorRef): Unit = {
def resolve(x: String) = if (x == "A") a else b
script match {
case offer(x, i) :: rest =>
resolve(x) ! CmdOffer(i.toInt)
@ -188,8 +189,11 @@ class TestPipe() extends Actor with ActorLogging with Stash {
s" Offered htlcs: ${my_commit.selected.filter(_ % 2 != even).mkString(" ")}",
s" Received htlcs: ${my_commit.selected.filter(_ % 2 == even).mkString(" ")}",
s" Fee level ${my_commit.fee_level}",
s" SIGNED").filterNot(_ == " Fee level 0") // TODO ???
def rtrim(s: String) = s.replaceAll("\\s+$", "")
s" SIGNED").filterNot(_ == " Fee level 0")
// TODO ???
def rtrim(s: String) = s.replaceAll("\\s+$", "")
l.foreach(s => {
fout.write(rtrim(s))
fout.newLine()

View file

@ -4,18 +4,21 @@ import java.util.concurrent.atomic.AtomicLong
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props, Stash}
import scala.util.Random
import scala.concurrent.duration._
import scala.util.Random
/**
* Created by PM on 18/08/2016.
*/
case class CMD_SendChange(change: String)
case class CMD_SendSig()
case class PKT_ReceiveChange(change: String)
case class PKT_ReceiveSig(sig: Set[Change])
case class PKT_ReceiveRev()
class BilateralCommitActor(counterparty: ActorRef) extends Actor with ActorLogging {
@ -90,12 +93,14 @@ object BilateralCommitActorTest extends App {
var i = new AtomicLong(0)
val random = new Random()
def msg = random.nextInt(100) % 5 match {
case 0 | 1 | 2 | 3 => CMD_SendChange(s"A${i.incrementAndGet()}")
case 4 => CMD_SendSig()
}
import scala.concurrent.ExecutionContext.Implicits.global
system.scheduler.schedule(0 seconds, 5 milliseconds, new Runnable() {
override def run(): Unit = a ! msg
})

View file

@ -1,6 +1,6 @@
package fr.acinq.protos.bilateralcommit
import fr.acinq.eclair.channel.{Direction, IN, OUT}
import fr.acinq.eclair.transactions.{Direction, IN, OUT}
/**
* Created by PM on 18/08/2016.

View file

@ -1,41 +0,0 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.{BinaryData, Crypto}
import lightning.{sha256_hash, signature}
import org.junit.runner.RunWith
import org.scalatest.{FunSuite, Ignore}
import org.scalatest.junit.JUnitRunner
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class ConversionSpec extends FunSuite {
val random = new Random()
def randomData(size: Int) = {
val data = new Array[Byte](size)
random.nextBytes(data)
data
}
test("sha256 conversion") {
val hash: BinaryData = randomData(32)
val sha256: sha256_hash = hash
val hash1: BinaryData = sha256
assert(hash === hash1)
}
test("signature conversion") {
for (i <- 0 to 100) {
val priv: BinaryData = randomData(32)
val pub: BinaryData = Crypto.publicKeyFromPrivateKey(priv)
val data: BinaryData = randomData(73)
val sig: BinaryData = Crypto.encodeSignature(Crypto.sign(data, priv))
assert(Crypto.verifySignature(data, sig, pub))
val protosig: signature = sig
val sig1: BinaryData = protosig
assert(Crypto.verifySignature(data, sig1, pub))
assert(sig1.take(32) === sig.take(32))
}
}
}

View file

@ -2,15 +2,13 @@ package fr.acinq.eclair
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.actor.{ActorSystem, Props, Status}
import akka.testkit.{TestFSMRef, TestKit, TestProbe}
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment._
import fr.acinq.eclair.router._
import lightning.sha256_hash
import org.junit.runner.RunWith
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
/**
* Created by PM on 29/08/2016.
@ -45,12 +43,13 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val sender = TestProbe()
sender.send(paymentFsm, CreatePayment(42000000, sha256_hash(1, 2, 3, 4), node_c))
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]
}
test("payment succeeded") {
//TODO re-enable
/*test("payment succeeded") {
val router = system.actorOf(Props[Router])
val selector = system.actorOf(Props[ChannelSelector])
val channel00 = TestProbe()
@ -64,8 +63,8 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
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_OURANCHOR, 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))
selector ! ChannelChangedState(channel01.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, 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))
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))
@ -74,7 +73,7 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val sender = TestProbe()
val req = CreatePayment(42000000, sha256_hash(1, 2, 3, 4), node_c)
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[_]])
@ -83,9 +82,10 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
sender.send(paymentFsm, PaymentSent(channel01.ref, req.h))
sender.expectMsg("sent")
}
}*/
test("payment failed") {
//TODO re-enable
/*test("payment failed") {
val router = system.actorOf(Props[Router])
val selector = system.actorOf(Props[ChannelSelector])
val channel00 = TestProbe()
@ -99,8 +99,8 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
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_OURANCHOR, 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))
selector ! ChannelChangedState(channel01.ref, node_b, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, 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))
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))
@ -109,7 +109,7 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
val CurrentState(_, WAITING_FOR_REQUEST) = monitor.expectMsgClass(classOf[CurrentState[_]])
val sender = TestProbe()
val req = CreatePayment(42000000, sha256_hash(1, 2, 3, 4), node_c)
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[_]])
@ -117,6 +117,6 @@ class PaymentFSMSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with
channel01.expectMsgType[CMD_ADD_HTLC]
sender.send(paymentFsm, PaymentFailed(channel01.ref, req.h, "some reason"))
sender.expectMsgType[Status.Failure]
}
}*/
}

View file

@ -1,130 +0,0 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.Crypto._
import fr.acinq.bitcoin._
import fr.acinq.eclair.channel.CommitmentSpec
import fr.acinq.eclair.channel.Scripts._
import lightning._
import lightning.locktime.Locktime.Blocks
import lightning.open_channel.anchor_offer
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ProtocolSpec extends FunSuite {
val previousTx = Transaction.read("010000000190b491456fe93621c0576784bca98a2a63a0cb72035a34b4ffdd48a086dfee18000000006a473044022042ccc84c0faa8f3013862eb1e4327f73766c2c8a1a923ecd8b09a0e50b37449e022076476d1ce3240af8636adc5f6fd550fbed6bff61fbc2f530098120af5aba64660121038d847f4ecb4c297457b149485814d6bb8fa52fb86733bcc4d8f1a302437bfc01feffffff0240420f000000000017a914b5494294ea8ec4c4a00906c69187744d924b61de87e8387c44000000001976a914e3e20826a5dc4dfb7bb7b236a7ba62b55ec0ea6a88accf010000")
val key: BinaryData = "9a d3 b5 0e fb 03 d9 de 58 7b df 91 8c dd 42 d8 69 03 2d 15 4d 4c 22 1c 88 ac ca f0 d4 a7 8c a0 01".filterNot(_.isSpaceChar)
object Alice {
val (_, commitKey) = Base58Check.decode("cVuzKWCszfvjkoJyUasvsrRdECriz8hSd1BDinRNzytwnXmX7m1g")
val (_, finalKey) = Base58Check.decode("cRUfvpbRtMSqCFD1ADdvgPn5HfRLYuHCFYAr2noWnaRDNger2AoA")
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
val R: BinaryData = "this is Alice's R".getBytes("UTF-8")
val H: BinaryData = Crypto.sha256(R)
}
object Bob {
val (_, commitKey) = Base58Check.decode("cSupnaiBh6jgTcQf9QANCB5fZtXojxkJQczq5kwfSBeULjNd5Ypo")
val (_, finalKey) = Base58Check.decode("cQLk5fMydgVwJjygt9ta8GcUU4GXLumNiXJCQviibs2LE5vyMXey")
val commitPubKey = Crypto.publicKeyFromPrivateKey(commitKey)
val finalPubKey = Crypto.publicKeyFromPrivateKey(finalKey)
val R: BinaryData = "this is Bob's R".getBytes("UTF-8")
val H: BinaryData = Crypto.sha256(R)
}
test("create anchor tx pubscript") {
val pubkey1: BinaryData = "02eb1a4be1a738f1808093279c7b055a944acdb573f22748cb262b9e374441dcbc"
val pubkey2: BinaryData = "0255952997073d71d0912b140fe43dc13b93889db5223312076efce173b8188a69"
assert(anchorPubkeyScript(pubkey1, pubkey2) === BinaryData("0020ee5923cf81831016ae7fe319a8b5b33596b23b25363fbd1e6246e142a5b3d6a8"))
}
test("create and spend anchor tx") {
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 10 * 1000, previousTx, 0, key)
val spending = Transaction(version = 1,
txIn = TxIn(OutPoint(anchor, anchorOutputIndex), Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Alice.commitPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
lockTime = 0)
// we only need 2 signatures because this is a 2-on-3 multisig
val redeemScript = multiSig2of2(Alice.commitPubKey, Bob.commitPubKey)
val sig1 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount, 1, Alice.commitKey)
val sig2 = Transaction.signInput(spending, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount, 1, Bob.commitKey)
val witness = if (isLess(Alice.commitPubKey, Bob.commitPubKey))
ScriptWitness(Seq(BinaryData.empty, sig1, sig2, redeemScript))
else
ScriptWitness(Seq(BinaryData.empty, sig2, sig1, redeemScript))
val signedTx = spending.updateWitness(0, witness)
Transaction.correctlySpends(signedTx, Seq(anchor), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
test("create and spend commit tx") {
val (anchor, anchorOutputIndex) = makeAnchorTx(Alice.commitPubKey, Bob.commitPubKey, 100000, previousTx, 0, key)
val ours = open_channel(
delay = locktime(Blocks(100)),
revocationHash = Alice.H,
commitKey = Alice.commitPubKey,
finalKey = Alice.finalPubKey,
anch = anchor_offer.WILL_CREATE_ANCHOR,
nextRevocationHash = null,
initialFeeRate = 1)
val theirs = open_channel(
delay = locktime(Blocks(100)),
revocationHash = Bob.H,
commitKey = Bob.commitPubKey,
finalKey = Bob.finalPubKey,
anch = anchor_offer.WONT_CREATE_ANCHOR,
nextRevocationHash = null,
initialFeeRate = 1)
// we assume that Alice knows Bob's H
val openAnchor = open_anchor(anchor.hash, anchorOutputIndex, 1000 * 1000)
val spec = CommitmentSpec(Set(), ours.initialFeeRate, 1000 * 1000, 0)
val tx = makeCommitTx(ours.finalKey, theirs.finalKey, theirs.delay, openAnchor.txid, openAnchor.outputIndex, Bob.H, spec)
val redeemScript = multiSig2of2(Alice.commitPubKey, Bob.commitPubKey)
val sigA: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount, 1, Alice.commitKey)
// now Bob receives open anchor, creates Alice's commit tx and sends backs its signature.
// this first commit tx sends all the funds to Alice and nothing to Bob
val sigB: BinaryData = Transaction.signInput(tx, 0, redeemScript, SIGHASH_ALL, anchor.txOut(anchorOutputIndex).amount, 1, Bob.commitKey)
val witness = witness2of2(sigA, sigB, Alice.commitPubKey, Bob.commitPubKey)
val commitTx = tx.updateWitness(0, witness)
Transaction.correctlySpends(commitTx, Seq(anchor), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// how do we spend our commit tx ?
// we can spend it by providing Bob's R and his signature
val spendingTx = {
val tx = Transaction(version = 2,
txIn = TxIn(OutPoint(commitTx, 0), Array.emptyByteArray, 0xffffffffL) :: Nil,
txOut = TxOut(10 satoshi, OP_DUP :: OP_HASH160 :: OP_PUSHDATA(hash160(Bob.finalPubKey)) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil) :: Nil,
lockTime = 0)
val redeemScript = redeemSecretOrDelay(ours.finalKey, locktime2long_csv(theirs.delay), theirs.finalKey, Bob.H)
val sig: BinaryData = Transaction.signInput(tx, 0, Script.write(redeemScript), SIGHASH_ALL, commitTx.txOut(0).amount, 1, Bob.finalKey)
val witness = ScriptWitness(sig :: Bob.R :: BinaryData(Script.write(redeemScript)) :: Nil)
val sigScript = OP_PUSHDATA(sig) :: OP_PUSHDATA(Bob.R) :: OP_PUSHDATA(Script.write(redeemScript)) :: Nil
//tx.updateSigScript(0, Script.write(sigScript))
tx.updateWitness(0, witness)
}
// or
Transaction.correctlySpends(spendingTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS | ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY | ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
}
test("sort binary data") {
assert(!isLess(Array.emptyByteArray, Array.emptyByteArray))
assert(isLess(fromHexString("aa"), fromHexString("bb")))
assert(isLess(fromHexString("aabbcc"), fromHexString("bbbbcc")))
assert(isLess(fromHexString("aa"), fromHexString("11aa")))
}
test("compute fees") {
// from https://github.com/rustyrussell/lightning-rfc/blob/master/bolts/02-wire-protocol.md
assert(computeFee(1112, 2) === 446)
}
}

View file

@ -5,10 +5,10 @@ import fr.acinq.bitcoin.{BinaryData, Satoshi, Transaction, TxIn, TxOut}
import fr.acinq.eclair.blockchain.ExtendedBitcoinClient
import fr.acinq.eclair.blockchain.peer.{NewBlock, NewTransaction}
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
import fr.acinq.eclair.channel.Scripts
import fr.acinq.eclair.transactions.OldScripts
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
/**
* Created by PM on 26/04/2016.
@ -16,6 +16,7 @@ import scala.concurrent.duration._
class TestBitcoinClient()(implicit system: ActorSystem) extends ExtendedBitcoinClient(new BitcoinJsonRPCClient("", "", "", 0)) {
import scala.concurrent.ExecutionContext.Implicits.global
system.scheduler.schedule(100 milliseconds, 100 milliseconds, new Runnable {
override def run(): Unit = system.eventStream.publish(NewBlock(null)) // blocks are not actually interpreted
})
@ -23,7 +24,7 @@ class TestBitcoinClient()(implicit system: ActorSystem) extends ExtendedBitcoinC
override def makeAnchorTx(ourCommitPub: BinaryData, theirCommitPub: BinaryData, amount: Satoshi)(implicit ec: ExecutionContext): Future[(Transaction, Int)] = {
val anchorTx = Transaction(version = 1,
txIn = Seq.empty[TxIn],
txOut = TxOut(amount, Scripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)) :: Nil,
txOut = TxOut(amount, OldScripts.anchorPubkeyScript(ourCommitPub, theirCommitPub)) :: Nil,
lockTime = 0
)
Future.successful((anchorTx, 0))

View file

@ -1,10 +1,8 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.{Base58, Base58Check, BinaryData, Crypto, Hash, OutPoint, Satoshi, TxIn, TxOut}
import fr.acinq.eclair.channel.{TheirChanges, _}
import fr.acinq.eclair.crypto.ShaChain
import lightning.locktime
import lightning.locktime.Locktime.Blocks
import fr.acinq.bitcoin.Crypto
import fr.acinq.eclair.channel._
import fr.acinq.eclair.crypto.Generators.Scalar
/**
* Created by PM on 26/04/2016.
@ -12,53 +10,40 @@ import lightning.locktime.Locktime.Blocks
object TestConstants {
val anchorAmount = 1000000L
lazy val anchorOutput = TxOut(Satoshi(anchorAmount), publicKeyScript = Scripts.anchorPubkeyScript(Alice.channelParams.commitPubKey, Bob.channelParams.commitPubKey))
// Alice is funder, Bob is not
object Alice {
val (Base58.Prefix.SecretKeyTestnet, commitPrivKey) = Base58Check.decode("cQPmcNr6pwBQPyGfab3SksE9nTCtx9ism9T4dkS9dETNU2KKtJHk")
val (Base58.Prefix.SecretKeyTestnet, finalPrivKey) = Base58Check.decode("cUrAtLtV7GGddqdkhUxnbZVDWGJBTducpPoon3eKp9Vnr1zxs6BG")
val channelParams = OurChannelParams(locktime(Blocks(300)), commitPrivKey, finalPrivKey, 1, 10000, Crypto.sha256("alice-seed".getBytes()), Some(Satoshi(anchorAmount)))
val finalPubKey = channelParams.finalPubKey
def revocationHash(index: Long) = Helpers.revocationHash(channelParams.shaSeed, index)
def ourSpec = CommitmentSpec(Set.empty[Htlc], feeRate = Alice.channelParams.initialFeeRate, amount_them_msat = 0, amount_us_msat = anchorAmount * 1000)
def theirSpec = CommitmentSpec(Set.empty[Htlc], feeRate = Bob.channelParams.initialFeeRate, amount_them_msat = anchorAmount * 1000, amount_us_msat = 0)
val ourTx = Helpers.makeOurTx(channelParams, TheirChannelParams(Bob.channelParams), TxIn(OutPoint(Hash.One, 0), Array.emptyByteArray, 0xffffffffL) :: Nil, revocationHash(0), ourSpec)
val commitments = Commitments(
Alice.channelParams,
TheirChannelParams(Bob.channelParams),
OurCommit(0, ourSpec, ourTx), TheirCommit(0, theirSpec, BinaryData(""), Bob.revocationHash(0)),
OurChanges(Nil, Nil, Nil), TheirChanges(Nil, Nil), 0L,
Right(Bob.revocationHash(1)), anchorOutput, ShaChain.init, new BasicTxDb)
val channelParams = LocalParams(
dustLimitSatoshis = 542,
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = 10000,
toSelfDelay = 144,
maxAcceptedHtlcs = 100,
fundingPrivkey = Scalar(Array.fill[Byte](32)(1)),
revocationSecret = Scalar(Array.fill[Byte](32)(2)),
paymentSecret = Scalar(Array.fill[Byte](32)(3)),
delayedPaymentKey = Scalar(Array.fill[Byte](32)(4)),
finalPrivKey = Scalar(Array.fill[Byte](32)(5)),
shaSeed = Crypto.sha256("alice-seed".getBytes())
)
}
object Bob {
val (Base58.Prefix.SecretKeyTestnet, commitPrivKey) = Base58Check.decode("cSUwLtdZ2tht9ZmHhdQue48pfe7tY2GT2TGWJDtjoZgo6FHrubGk")
val (Base58.Prefix.SecretKeyTestnet, finalPrivKey) = Base58Check.decode("cPR7ZgXpUaDPA3GwGceMDS5pfnSm955yvks3yELf3wMJwegsdGTg")
val channelParams = OurChannelParams(locktime(Blocks(350)), commitPrivKey, finalPrivKey, 2, 10000, Crypto.sha256("bob-seed".getBytes()), None)
val finalPubKey = channelParams.finalPubKey
def revocationHash(index: Long) = Helpers.revocationHash(channelParams.shaSeed, index)
def ourSpec = Alice.theirSpec
def theirSpec = Alice.ourSpec
val ourTx = Helpers.makeOurTx(channelParams, TheirChannelParams(Alice.channelParams), TxIn(OutPoint(Hash.One, 0), Array.emptyByteArray, 0xffffffffL) :: Nil, revocationHash(0), ourSpec)
val commitments = Commitments(
Bob.channelParams,
TheirChannelParams(Alice.channelParams),
OurCommit(0, ourSpec, ourTx), TheirCommit(0, theirSpec, BinaryData(""), Alice.revocationHash(0)),
OurChanges(Nil, Nil, Nil), TheirChanges(Nil, Nil), 0L,
Right(Alice.revocationHash(1)), anchorOutput, ShaChain.init, new BasicTxDb)
val channelParams = LocalParams(
dustLimitSatoshis = 542,
maxHtlcValueInFlightMsat = Long.MaxValue,
channelReserveSatoshis = 0,
htlcMinimumMsat = 0,
feeratePerKw = 10000,
toSelfDelay = 144,
maxAcceptedHtlcs = 100,
fundingPrivkey = Scalar(Array.fill[Byte](32)(11)),
revocationSecret = Scalar(Array.fill[Byte](32)(12)),
paymentSecret = Scalar(Array.fill[Byte](32)(13)),
delayedPaymentKey = Scalar(Array.fill[Byte](32)(14)),
finalPrivKey = Scalar(Array.fill[Byte](32)(15)),
shaSeed = Crypto.sha256("alice-seed".getBytes())
)
}
}

View file

@ -1,17 +1,13 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import akka.actor.{ActorRef, ActorSystem, PoisonPill, Props, Terminated}
import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import akka.testkit.{TestFSMRef, TestKit, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.PeerWatcher
import TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.peer.NewBlock
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment.NoopPaymentHandler
import org.scalatest.{BeforeAndAfterAll, Matchers, fixture}
import scala.concurrent.duration._
/**
* Created by PM on 28/04/2016.
*/

View file

@ -1,87 +0,0 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin._
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, routing, update_add_htlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class HandleTheirCurrentCommitTxSpec extends FunSuite {
def signAndReceiveRevocation(sender: Commitments, receiver: Commitments): (Commitments, Commitments) = {
val (sender1, commit1) = Commitments.sendCommit(sender)
val (receiver1, rev1) = Commitments.receiveCommit(receiver, commit1)
val sender2 = Commitments.receiveRevocation(sender1, rev1)
(sender2, receiver1)
}
def addHtlc(sender: Commitments, receiver: Commitments, htlc: update_add_htlc): (Commitments, Commitments) = {
(Commitments.sendAdd(sender, CMD_ADD_HTLC(id = Some(htlc.id), amountMsat = htlc.amountMsat, rHash = htlc.rHash, expiry = htlc.expiry))._1, Commitments.receiveAdd(receiver, htlc))
}
test("claim received htlcs in their current commit tx") {
val alice = Alice.commitments
val bob = Bob.commitments
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
val R1: BinaryData = "0202030405060708010203040506070801020304050607080102030405060708"
val H1 = Crypto.sha256(R1)
val (alice0, bob0) = addHtlc(alice, bob, update_add_htlc(1, 70000000, H, locktime(Blocks(400)), routing.defaultInstance))
val (alice1, bob1) = addHtlc(alice0, bob0, update_add_htlc(2, 80000000, H1, locktime(Blocks(350)), routing.defaultInstance))
val (alice2, bob2) = signAndReceiveRevocation(alice1, bob1)
val (bob3, alice3) = signAndReceiveRevocation(bob2, alice2)
// Alice publishes her current commit tx
val tx = alice3.ourCommit.publishableTx
// suppose we have the payment preimage, what do we do ?
val (bob4, _) = Commitments.sendFulfill(bob3, CMD_FULFILL_HTLC(1, R))
val (bob5, _) = Commitments.sendFulfill(bob4, CMD_FULFILL_HTLC(2, R1))
// we're Bob. Check that our view of Alice's commit tx is right
val theirTxTemplate = Commitments.makeTheirTxTemplate(bob5)
val theirTx = theirTxTemplate.makeTx
assert(theirTx.txOut === tx.txOut)
val Seq(tx1, tx2) = Helpers.claimReceivedHtlcs(tx, theirTxTemplate, bob5)
assert(tx1.txIn.length == 1 && tx1.txOut.length == 1 && tx2.txIn.length == 1 && tx2.txOut.length == 1)
assert(Set(tx1.txOut(0).amount, tx2.txOut(0).amount) == Set(Satoshi(70000), Satoshi(80000)))
Transaction.correctlySpends(tx1, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
test("claim sent htlcs in their current commit tx") {
val alice = Alice.commitments
val bob = Bob.commitments
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
val R1: BinaryData = "0202030405060708010203040506070801020304050607080102030405060708"
val H1 = Crypto.sha256(R1)
val (alice0, bob0) = addHtlc(alice, bob, update_add_htlc(1, 70000000, H, locktime(Blocks(400)), routing.defaultInstance))
val (alice1, bob1) = addHtlc(alice0, bob0, update_add_htlc(1, 80000000, H1, locktime(Blocks(350)), routing.defaultInstance))
val (alice2, bob2) = signAndReceiveRevocation(alice1, bob1)
val (bob3, alice3) = signAndReceiveRevocation(bob2, alice2)
// Bob publishes his current commit tx
val tx = bob3.ourCommit.publishableTx
// we're Alice. Check that our view of Bob's commit tx is right
val theirTxTemplate = Commitments.makeTheirTxTemplate(alice3)
val theirTx = theirTxTemplate.makeTx
assert(theirTx.txOut === tx.txOut)
val Seq(tx1, tx2) = Helpers.claimSentHtlcs(tx, theirTxTemplate, alice3)
assert(tx1.txIn.length == 1 && tx1.txOut.length == 1 && tx2.txIn.length == 1 && tx2.txOut.length == 1)
assert(Set(tx1.txOut(0).amount, tx2.txOut(0).amount) == Set(Satoshi(70000), Satoshi(80000)))
Transaction.correctlySpends(tx1, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
Transaction.correctlySpends(tx2, Seq(tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
}
}

View file

@ -1,41 +0,0 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.Crypto
import fr.acinq.eclair._
import lightning._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class HelpersSpec extends FunSuite {
test("add, fulfill and fail htlcs") {
val spec = CommitmentSpec(Set(), 1000, 2000 * 1000, 0)
val R1: rval = Crypto.sha256("foo".getBytes())
val H1: sha256_hash = Crypto.sha256(R1)
val R2: rval = Crypto.sha256("bar".getBytes())
val H2: sha256_hash = Crypto.sha256(R2)
val ours1 = update_add_htlc(1, 1000, H1, locktime.defaultInstance, routing.defaultInstance)
val spec1 = Helpers.reduce(spec, ours1 :: Nil, Nil)
assert(spec1.htlcs.size == 1 && spec1.htlcs.head.add.id == 1 && spec1.htlcs.head.add.rHash == H1)
assert(spec1.amount_us_msat == spec.amount_us_msat - ours1.amountMsat)
assert(spec1.amount_them_msat == spec.amount_them_msat)
assert(spec1.totalFunds == spec.totalFunds)
val theirs1 = update_fulfill_htlc(ours1.id, R1)
val spec2 = Helpers.reduce(spec1, Nil, theirs1 :: Nil)
assert(spec2.htlcs.isEmpty && spec2.amount_them_msat == 1000 && spec2.totalFunds == spec.totalFunds)
val theirs2 = update_add_htlc(2, 1000, H2, locktime.defaultInstance, routing.defaultInstance)
val spec3 = Helpers.reduce(spec2, Nil, theirs2 :: Nil)
assert(spec3.htlcs.size == 1)
assert(spec3.amount_us_msat == spec2.amount_us_msat)
assert(spec3.amount_them_msat == spec2.amount_them_msat - theirs2.amountMsat)
assert(spec3.totalFunds == spec.totalFunds)
val ours2 = update_fail_htlc(theirs2.id, fail_reason.defaultInstance)
val spec4 = Helpers.reduce(spec3, ours2 :: Nil, Nil)
assert(spec4 == spec2)
}
}

View file

@ -1,13 +1,10 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.testkit.TestProbe
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.peer.NewBlock
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_SPENT, CLOSED, CLOSING, NEGOTIATING, _}
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, update_add_htlc}
import fr.acinq.eclair.wire.UpdateAddHtlc
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -22,27 +19,27 @@ class NominalChannelSpec extends BaseChannelTestClass {
test("open channel and reach normal state") { case (alice, bob, pipe) =>
val monitorA = TestProbe()
alice ! SubscribeTransitionCallBack(monitorA.ref)
val CurrentState(_, OPEN_WAIT_FOR_OPEN_WITHANCHOR) = monitorA.expectMsgClass(classOf[CurrentState[_]])
val CurrentState(_, WAIT_FOR_ACCEPT_CHANNEL) = monitorA.expectMsgClass(classOf[CurrentState[_]])
val monitorB = TestProbe()
bob ! SubscribeTransitionCallBack(monitorB.ref)
val CurrentState(_, OPEN_WAIT_FOR_OPEN_NOANCHOR) = monitorB.expectMsgClass(classOf[CurrentState[_]])
val CurrentState(_, WAIT_FOR_OPEN_CHANNEL) = monitorB.expectMsgClass(classOf[CurrentState[_]])
pipe ! (alice, bob) // this starts the communication between alice and bob
within(30 seconds) {
val Transition(_, OPEN_WAIT_FOR_OPEN_WITHANCHOR, OPEN_WAIT_FOR_COMMIT_SIG) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAIT_FOR_OPEN_NOANCHOR, OPEN_WAIT_FOR_ANCHOR) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_SIGNED) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_FUNDING_CREATED) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAIT_FOR_COMMIT_SIG, OPEN_WAITING_OURANCHOR) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAIT_FOR_ANCHOR, OPEN_WAITING_THEIRANCHOR) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_SIGNED, WAIT_FOR_FUNDING_LOCKED_INTERNAL) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_CREATED, WAIT_FOR_FUNDING_LOCKED_INTERNAL) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_COMPLETE_OURANCHOR) = monitorA.expectMsgClass(5 seconds, classOf[Transition[_]])
val Transition(_, OPEN_WAITING_THEIRANCHOR, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_LOCKED_INTERNAL, WAIT_FOR_FUNDING_LOCKED) = monitorA.expectMsgClass(5 seconds, classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_LOCKED_INTERNAL, WAIT_FOR_FUNDING_LOCKED) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, NORMAL) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR, NORMAL) = monitorB.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_LOCKED, NORMAL) = monitorA.expectMsgClass(classOf[Transition[_]])
val Transition(_, WAIT_FOR_FUNDING_LOCKED, NORMAL) = monitorB.expectMsgClass(classOf[Transition[_]])
}
}
@ -55,20 +52,20 @@ class NominalChannelSpec extends BaseChannelTestClass {
awaitCond(bob.stateName == NORMAL)
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
val H: BinaryData = Crypto.sha256(R)
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(400)))
alice ! CMD_ADD_HTLC(60000000, H, 400)
Thread.sleep(100)
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val List(update_add_htlc(_, _, h, _, _)) = d.commitments.ourChanges.proposed
assert(h == bin2sha256(H))
val List(UpdateAddHtlc(_, _, _, _, h, _)) = d.commitments.localChanges.proposed
assert(h == H)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val List(update_add_htlc(_, _, h, _, _)) = d.commitments.theirChanges.proposed
assert(h == bin2sha256(H))
val List(UpdateAddHtlc(_, _, _, _, h, _)) = d.commitments.remoteChanges.proposed
assert(h == H)
}
alice ! CMD_SIGN
@ -76,13 +73,13 @@ class NominalChannelSpec extends BaseChannelTestClass {
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val htlc = d.commitments.theirCommit.spec.htlcs.head
assert(htlc.add.rHash == bin2sha256(H))
val htlc = d.commitments.remoteCommit.spec.htlcs.head
assert(htlc.add.paymentHash == H)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val htlc = d.commitments.ourCommit.spec.htlcs.head
assert(htlc.add.rHash == bin2sha256(H))
val htlc = d.commitments.localCommit.spec.htlcs.head
assert(htlc.add.paymentHash == H)
}
bob ! CMD_FULFILL_HTLC(1, R)
@ -94,33 +91,33 @@ class NominalChannelSpec extends BaseChannelTestClass {
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
assert(d.commitments.ourCommit.spec.htlcs.isEmpty)
assert(d.commitments.ourCommit.spec.amount_us_msat == TestConstants.anchorAmount * 1000 - 60000000)
assert(d.commitments.ourCommit.spec.amount_them_msat == 60000000)
assert(d.commitments.localCommit.spec.htlcs.isEmpty)
assert(d.commitments.localCommit.spec.to_local_msat == TestConstants.anchorAmount * 1000 - 60000000)
assert(d.commitments.localCommit.spec.to_remote_msat == 60000000)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
assert(d.commitments.ourCommit.spec.htlcs.isEmpty)
assert(d.commitments.ourCommit.spec.amount_us_msat == 60000000)
assert(d.commitments.ourCommit.spec.amount_them_msat == TestConstants.anchorAmount * 1000 - 60000000)
assert(d.commitments.localCommit.spec.htlcs.isEmpty)
assert(d.commitments.localCommit.spec.to_local_msat == 60000000)
assert(d.commitments.localCommit.spec.to_remote_msat == TestConstants.anchorAmount * 1000 - 60000000)
}
// send another HTLC
val R1 = Crypto.sha256(H)
val H1 = Crypto.sha256(R1)
val R1: BinaryData = Crypto.sha256(H)
val H1: BinaryData = Crypto.sha256(R1)
alice ! CMD_ADD_HTLC(60000000, H1, locktime(Blocks(400)))
alice ! CMD_ADD_HTLC(60000000, H1, 400)
Thread.sleep(500)
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val List(update_add_htlc(_, _, h, _, _)) = d.commitments.ourChanges.proposed
assert(h == bin2sha256(H1))
val List(UpdateAddHtlc(_, _, _, _, h, _)) = d.commitments.localChanges.proposed
assert(h == H1)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val List(update_add_htlc(_, _, h, _, _)) = d.commitments.theirChanges.proposed
assert(h == bin2sha256(H1))
val List(UpdateAddHtlc(_, _, _, _, h, _)) = d.commitments.remoteChanges.proposed
assert(h == H1)
}
alice ! CMD_SIGN
@ -128,13 +125,13 @@ class NominalChannelSpec extends BaseChannelTestClass {
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val htlc = d.commitments.theirCommit.spec.htlcs.head
assert(htlc.add.rHash == bin2sha256(H1))
val htlc = d.commitments.remoteCommit.spec.htlcs.head
assert(htlc.add.paymentHash == H1)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
val htlc = d.commitments.ourCommit.spec.htlcs.head
assert(htlc.add.rHash == bin2sha256(H1))
val htlc = d.commitments.localCommit.spec.htlcs.head
assert(htlc.add.paymentHash == H1)
}
bob ! CMD_FULFILL_HTLC(2, R1)
@ -147,15 +144,15 @@ class NominalChannelSpec extends BaseChannelTestClass {
(alice.stateData: @unchecked) match {
case d: DATA_NORMAL =>
assert(d.commitments.ourCommit.spec.htlcs.isEmpty)
assert(d.commitments.ourCommit.spec.amount_us_msat == TestConstants.anchorAmount * 1000 - 2 * 60000000)
assert(d.commitments.ourCommit.spec.amount_them_msat == 2 * 60000000)
assert(d.commitments.localCommit.spec.htlcs.isEmpty)
assert(d.commitments.localCommit.spec.to_local_msat == TestConstants.anchorAmount * 1000 - 2 * 60000000)
assert(d.commitments.localCommit.spec.to_remote_msat == 2 * 60000000)
}
(bob.stateData: @unchecked) match {
case d: DATA_NORMAL =>
assert(d.commitments.ourCommit.spec.htlcs.isEmpty)
assert(d.commitments.ourCommit.spec.amount_us_msat == 2 * 60000000)
assert(d.commitments.ourCommit.spec.amount_them_msat == TestConstants.anchorAmount * 1000 - 2 * 60000000)
assert(d.commitments.localCommit.spec.htlcs.isEmpty)
assert(d.commitments.localCommit.spec.to_local_msat == 2 * 60000000)
assert(d.commitments.localCommit.spec.to_remote_msat == TestConstants.anchorAmount * 1000 - 2 * 60000000)
}
}
}
@ -195,9 +192,9 @@ class NominalChannelSpec extends BaseChannelTestClass {
}
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
val H: BinaryData = Crypto.sha256(R)
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(400)))
alice ! CMD_ADD_HTLC(60000000, H, 400)
alice ! CMD_SIGN
bob ! CMD_SIGN
alice ! CMD_CLOSE(None)
@ -230,14 +227,14 @@ class NominalChannelSpec extends BaseChannelTestClass {
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(400)))
alice ! CMD_ADD_HTLC(60000000, H, 400)
alice ! CMD_SIGN
Thread.sleep(500)
bob ! CMD_SIGN
Thread.sleep(500)
val commitTx = (alice.stateData: @unchecked) match {
case d: DATA_NORMAL => d.commitments.ourCommit.publishableTx
case d: DATA_NORMAL => d.commitments.localCommit.publishableTx
}
bob ! CMD_FULFILL_HTLC(1, R)
@ -248,7 +245,7 @@ class NominalChannelSpec extends BaseChannelTestClass {
Thread.sleep(500)
// alice publishes a revoked tx
bob ! (BITCOIN_ANCHOR_SPENT, commitTx)
bob ! (BITCOIN_FUNDING_SPENT, commitTx)
awaitCond(bob.stateName == CLOSED)
}
}

View file

@ -1,10 +1,6 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.channel._
import fr.acinq.eclair._
import lightning.locktime
import lightning.locktime.Locktime.Blocks
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -27,14 +23,14 @@ class OurCommitChannelSpec extends BaseChannelTestClass {
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(400)))
alice ! CMD_ADD_HTLC(60000000, H, 400)
alice ! CMD_SIGN
Thread.sleep(500)
bob ! CMD_SIGN
Thread.sleep(500)
val commitTx = (alice.stateData: @unchecked) match {
case d: DATA_NORMAL => d.commitments.ourCommit.publishableTx
case d: DATA_NORMAL => d.commitments.localCommit.publishableTx
}
bob ! CMD_FULFILL_HTLC(1, R)
@ -44,7 +40,7 @@ class OurCommitChannelSpec extends BaseChannelTestClass {
alice ! CMD_SIGN
Thread.sleep(500)
bob ! (BITCOIN_ANCHOR_SPENT, commitTx)
bob ! (BITCOIN_FUNDING_SPENT, commitTx)
awaitCond(bob.stateName == CLOSED)
}
}

View file

@ -1,4 +1,4 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import akka.actor.{Actor, ActorRef, Stash}

View file

@ -1,12 +1,6 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import akka.actor.FSM.{CurrentState, SubscribeTransitionCallBack, Transition}
import akka.testkit.TestProbe
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair._
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_SPENT, CLOSED, CLOSING, NEGOTIATING, _}
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, update_add_htlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -19,7 +13,7 @@ import scala.concurrent.duration._
class StealChannelSpec extends BaseChannelTestClass {
test("steal revoked commit tx") { case (alice, bob, pipe) =>
pipe !(alice, bob) // this starts the communication between alice and bob
pipe ! (alice, bob) // this starts the communication between alice and bob
within(30 seconds) {
@ -29,14 +23,14 @@ class StealChannelSpec extends BaseChannelTestClass {
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
alice ! CMD_ADD_HTLC(60000000, H, locktime(Blocks(4)))
alice ! CMD_ADD_HTLC(60000000, H, 4)
alice ! CMD_SIGN
Thread.sleep(500)
bob ! CMD_SIGN
Thread.sleep(500)
val commitTx = (alice.stateData: @unchecked) match {
case d: DATA_NORMAL => d.commitments.ourCommit.publishableTx
case d: DATA_NORMAL => d.commitments.localCommit.publishableTx
}
bob ! CMD_FULFILL_HTLC(1, R)
@ -46,7 +40,7 @@ class StealChannelSpec extends BaseChannelTestClass {
alice ! CMD_SIGN
Thread.sleep(500)
bob !(BITCOIN_ANCHOR_SPENT, commitTx)
bob ! (BITCOIN_FUNDING_SPENT, commitTx)
awaitCond(bob.stateName == CLOSING)
}
}

View file

@ -1,64 +0,0 @@
package fr.acinq.eclair.channel
import fr.acinq.bitcoin.{BinaryData, Crypto, ScriptFlags, Transaction}
import fr.acinq.eclair._
import TestConstants.{Alice, Bob}
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, routing, update_add_htlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class StealRevokedCommitmentSpec extends FunSuite {
def signAndReceiveRevocation(sender: Commitments, receiver: Commitments): (Commitments, Commitments) = {
val (sender1, commit1) = Commitments.sendCommit(sender)
val (receiver1, rev1) = Commitments.receiveCommit(receiver, commit1)
val sender2 = Commitments.receiveRevocation(sender1, rev1)
(sender2, receiver1)
}
def addHtlc(sender: Commitments, receiver: Commitments, htlc: update_add_htlc): (Commitments, Commitments) = {
(Commitments.sendAdd(sender, CMD_ADD_HTLC(id = Some(htlc.id), amountMsat = htlc.amountMsat, rHash = htlc.rHash, expiry = htlc.expiry))._1, Commitments.receiveAdd(receiver, htlc))
}
def fulfillHtlc(sender: Commitments, receiver: Commitments, id: Long, paymentPreimage: BinaryData): (Commitments, Commitments) = {
val (sender1, fulfill) = Commitments.sendFulfill(sender, CMD_FULFILL_HTLC(id, paymentPreimage))
val (receiver1, _) = Commitments.receiveFulfill(receiver, fulfill)
(sender1, receiver1)
}
test("steal a revoked commit tx") {
val alice = Alice.commitments
val bob = Bob.commitments
val R: BinaryData = "0102030405060708010203040506070801020304050607080102030405060708"
val H = Crypto.sha256(R)
val htlc = update_add_htlc(1, 70000000, H, locktime(Blocks(400)), routing.defaultInstance)
val (alice1, bob1) = addHtlc(alice, bob, htlc)
val (alice2, bob2) = signAndReceiveRevocation(alice1, bob1)
val (bob3, alice3) = signAndReceiveRevocation(bob2, alice2)
val (bob4, alice4) = fulfillHtlc(bob3, alice3, 1, R)
val (bob5, alice5) = signAndReceiveRevocation(bob4, alice4)
val theirTxTemplate = Commitments.makeTheirTxTemplate(bob3)
val theirTx = theirTxTemplate.makeTx
assert(theirTx.txIn.map(_.outPoint) == alice3.ourCommit.publishableTx.txIn.map(_.outPoint))
assert(theirTx.txOut == alice3.ourCommit.publishableTx.txOut)
val preimage = bob5.theirPreimages.getHash(0xFFFFFFFFFFFFFFFFL - bob3.theirCommit.index).get
val punishTx = Helpers.claimRevokedCommitTx(theirTxTemplate, preimage, bob3.ourParams.finalPrivKey)
Transaction.correctlySpends(punishTx, Seq(alice3.ourCommit.publishableTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// now what if Alice published a revoked commit tx ?
val stealTx = bob5.txDb.get(alice4.ourCommit.publishableTx.txid)
Transaction.correctlySpends(stealTx.get, Seq(alice4.ourCommit.publishableTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
// but we cannot steal Alice's current commit tx
assert(bob5.txDb.get(alice5.ourCommit.publishableTx.txid) == None)
}
}

View file

@ -1,4 +1,4 @@
package fr.acinq.eclair.channel.simulator
package fr.acinq.eclair.channel
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicLong
@ -8,9 +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.channel._
import lightning.locktime.Locktime.Blocks
import lightning.{locktime, update_add_htlc}
import fr.acinq.eclair.wire.UpdateAddHtlc
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@ -37,20 +35,20 @@ class ThroughputSpec extends FunSuite {
override def receive: Receive = ???
//TODO: store this map on file ?
// TODO: store this map on file ?
def run(h2r: Map[BinaryData, BinaryData]): Receive = {
case ('add, tgt: ActorRef) =>
val r = generateR()
val h: BinaryData = Crypto.sha256(r)
tgt ! CMD_ADD_HTLC(1, h, locktime(Blocks(1)))
tgt ! CMD_ADD_HTLC(1, h, 1)
context.become(run(h2r + (h -> r)))
case ('sig, tgt: ActorRef) => tgt ! CMD_SIGN
case htlc: update_add_htlc if h2r.contains(htlc.rHash) =>
val r = h2r(htlc.rHash)
case htlc: UpdateAddHtlc if h2r.contains(htlc.paymentHash) =>
val r = h2r(htlc.paymentHash)
sender ! CMD_FULFILL_HTLC(htlc.id, r)
context.become(run(h2r - htlc.rHash))
context.become(run(h2r - htlc.paymentHash))
}
}), "payment-handler")
val alice = system.actorOf(Channel.props(pipe, blockchain, paymentHandler, Alice.channelParams, "B"), "a")
@ -69,6 +67,7 @@ class ThroughputSpec extends FunSuite {
var i = new AtomicLong(0)
val random = new Random()
def msg = random.nextInt(100) % 5 match {
case 0 | 1 | 2 | 3 => 'add
case 4 => 'sig

View file

@ -1,112 +0,0 @@
package fr.acinq.eclair.channel.simulator.states.c
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{ERR_INFORMATION_LEAK, OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, _}
import lightning._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitingOurAnchorStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
val bob2alice = TestProbe()
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, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
within(30 seconds) {
alice2bob.expectMsgType[open_channel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.forward(alice)
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.send(blockchainA, watch.copy(channel = system.deadLetters)) // so that we can control when BITCOIN_ANCHOR_DEPTHOK arrives
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[Publish]
alice2blockchain.forward(blockchainA)
bob ! BITCOIN_ANCHOR_DEPTHOK
awaitCond(alice.stateName == OPEN_WAITING_OURANCHOR)
}
test((alice, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv open_complete") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val msg = bob2alice.expectMsgType[open_complete]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_OPEN_WAITING].deferred == Some(msg))
awaitCond(alice.stateName == OPEN_WAITING_OURANCHOR)
}
}
test("recv BITCOIN_ANCHOR_DEPTHOK") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
alice ! BITCOIN_ANCHOR_DEPTHOK
awaitCond(alice.stateName == OPEN_WAIT_FOR_COMPLETE_OURANCHOR)
alice2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
}
}
test("recv BITCOIN_ANCHOR_TIMEOUT") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
alice ! BITCOIN_ANCHOR_TIMEOUT
alice2bob.expectMsgType[error]
awaitCond(alice.stateName == CLOSED)
}
}
test("recv BITCOIN_ANCHOR_SPENT") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_OPEN_WAITING].commitments.ourCommit.publishableTx
alice ! (BITCOIN_ANCHOR_SPENT, null)
alice2bob.expectMsgType[error]
alice2blockchain.expectMsg(Publish(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
}
test("recv error") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_OPEN_WAITING].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_OPEN_WAITING].commitments.ourCommit.publishableTx
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
}

View file

@ -1,99 +0,0 @@
package fr.acinq.eclair.channel.simulator.states.c
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{PeerWatcher, WatchConfirmed, WatchLost, WatchSpent}
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, OPEN_WAITING_THEIRANCHOR, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR, _}
import lightning._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitingTheirAnchorStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
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, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice2bob.expectMsgType[open_channel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
alice2bob.expectMsgType[open_anchor]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.forward(alice)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
awaitCond(bob.stateName == OPEN_WAITING_THEIRANCHOR)
test((alice, bob, alice2bob, bob2alice, bob2blockchain))
}
test("recv open_complete") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
val msg = alice2bob.expectMsgType[open_complete]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_OPEN_WAITING].deferred == Some(msg))
awaitCond(bob.stateName == OPEN_WAITING_THEIRANCHOR)
}
}
test("recv BITCOIN_ANCHOR_DEPTHOK") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! BITCOIN_ANCHOR_DEPTHOK
awaitCond(bob.stateName == OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR)
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
}
}
test("recv BITCOIN_ANCHOR_TIMEOUT") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! BITCOIN_ANCHOR_TIMEOUT
bob2alice.expectMsgType[error]
awaitCond(bob.stateName == CLOSED)
}
}
test("recv BITCOIN_ANCHOR_SPENT") { case (alice, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
// this is the fully signed tx that alice could decide to publish
alice2bob.expectMsgType[open_complete]
awaitCond(alice.stateName == OPEN_WAIT_FOR_COMPLETE_OURANCHOR)
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
// we have nothing at stake so we don't do anything with the tx
bob ! (BITCOIN_ANCHOR_SPENT, tx)
bob2alice.expectMsgType[error]
awaitCond(bob.stateName == CLOSED)
}
}
test("recv error") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! error(Some("oops"))
awaitCond(bob.stateName == CLOSED)
}
}
test("recv CMD_CLOSE") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! CMD_CLOSE(None)
awaitCond(bob.stateName == CLOSED)
}
}
}

View file

@ -1,84 +0,0 @@
package fr.acinq.eclair.channel.simulator.states.d
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{PeerWatcher, WatchConfirmed, WatchLost, WatchSpent}
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR, _}
import lightning._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForCompleteTheirAnchorStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
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, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice2bob.expectMsgType[open_channel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
alice2bob.expectMsgType[open_anchor]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.forward(alice)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR)
test((alice, bob, alice2bob, bob2alice, bob2blockchain))
}
test("recv open_complete") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
alice2bob.expectMsgType[open_complete]
alice2bob.forward(bob)
awaitCond(bob.stateName == NORMAL)
}
}
test("recv BITCOIN_ANCHOR_SPENT") { case (alice, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
// this is the fully signed tx that alice could decide to publish
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
// we have nothing at stake so we don't do anything with the tx
bob ! (BITCOIN_ANCHOR_SPENT, tx)
bob2alice.expectMsgType[error]
awaitCond(bob.stateName == CLOSED)
}
}
test("recv CMD_CLOSE") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! CMD_CLOSE(None)
awaitCond(bob.stateName == CLOSED)
}
}
test("recv error") { case (_, bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! error(Some("oops"))
awaitCond(bob.stateName == CLOSED)
}
}
}

View file

@ -1,7 +1,7 @@
package fr.acinq.eclair.channel.simulator.states
package fr.acinq.eclair.channel.states
import akka.actor.{ActorNotFound, ActorRef, ActorSystem, PoisonPill, Terminated}
import akka.testkit.{TestKit, TestProbe}
import akka.actor.{ActorNotFound, ActorSystem, PoisonPill}
import akka.testkit.TestKit
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, fixture}
import scala.concurrent.Await

View file

@ -1,11 +1,9 @@
package fr.acinq.eclair.channel.simulator.states
package fr.acinq.eclair.channel.states
import akka.testkit.{TestFSMRef, TestKitBase, TestProbe}
import fr.acinq.bitcoin.Crypto
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.channel._
import fr.acinq.eclair._
import lightning.locktime.Locktime.Blocks
import lightning._
import fr.acinq.eclair.wire.{CommitSig, RevokeAndAck, UpdateAddHtlc, UpdateFulfillHtlc}
import scala.util.Random
@ -14,38 +12,39 @@ import scala.util.Random
*/
trait StateTestsHelperMethods extends TestKitBase {
def addHtlc(amountMsat: Int, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe): (rval, update_add_htlc) = {
def addHtlc(amountMsat: Int, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe): (BinaryData, UpdateAddHtlc) = {
val rand = new Random()
val R = rval(rand.nextInt(), rand.nextInt(), rand.nextInt(), rand.nextInt())
val H: sha256_hash = Crypto.sha256(R)
val R: BinaryData = Array.fill[Byte](32)(0)
rand.nextBytes(R)
val H: BinaryData = Crypto.sha256(R)
val sender = TestProbe()
sender.send(s, CMD_ADD_HTLC(amountMsat, H, locktime(Blocks(1440))))
sender.send(s, CMD_ADD_HTLC(amountMsat, H, 1440))
sender.expectMsg("ok")
val htlc = s2r.expectMsgType[update_add_htlc]
val htlc = s2r.expectMsgType[UpdateAddHtlc]
s2r.forward(r)
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.theirChanges.proposed.contains(htlc))
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.remoteChanges.proposed.contains(htlc))
(R, htlc)
}
def fulfillHtlc(id: Long, R: rval, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe) = {
def fulfillHtlc(id: Long, R: BinaryData, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe) = {
val sender = TestProbe()
sender.send(s, CMD_FULFILL_HTLC(id, R))
sender.expectMsg("ok")
val fulfill = s2r.expectMsgType[update_fulfill_htlc]
val fulfill = s2r.expectMsgType[UpdateFulfillHtlc]
s2r.forward(r)
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.theirChanges.proposed.contains(fulfill))
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.remoteChanges.proposed.contains(fulfill))
}
def sign(s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe) = {
val sender = TestProbe()
val rCommitIndex = r.stateData.asInstanceOf[HasCommitments].commitments.ourCommit.index
val rCommitIndex = r.stateData.asInstanceOf[HasCommitments].commitments.localCommit.index
sender.send(s, CMD_SIGN)
sender.expectMsg("ok")
s2r.expectMsgType[update_commit]
s2r.expectMsgType[CommitSig]
s2r.forward(r)
r2s.expectMsgType[update_revocation]
r2s.expectMsgType[RevokeAndAck]
r2s.forward(s)
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.ourCommit.index == rCommitIndex + 1)
awaitCond(r.stateData.asInstanceOf[HasCommitments].commitments.localCommit.index == rCommitIndex + 1)
}
}

View file

@ -0,0 +1,76 @@
package fr.acinq.eclair.channel.states.a
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{MakeFundingTx, PeerWatcher}
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_CREATED_INTERNAL, _}
import fr.acinq.eclair.channel.states.StateSpecBaseClass
import fr.acinq.eclair.wire.{AcceptChannel, Error, OpenChannel}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class WaitForAcceptChannelStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
val bob2alice = TestProbe()
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, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL)
}
test((alice, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv AcceptChannel") { case (alice, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CREATED_INTERNAL)
}
}
/*test("recv funding tx") { case (alice, alice2bob, bob2alice, alice2blockchain, blockchain) =>
within(30 seconds) {
bob2alice.expectMsgType[OpenChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchain)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
alice2bob.expectMsgType[OpenChannel]
}
}*/
test("recv Error") { case (bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
bob ! Error(0, "oops".getBytes)
awaitCond(bob.stateName == CLOSED)
}
}
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSED)
}
}
}

View file

@ -1,10 +1,11 @@
package fr.acinq.eclair.channel.simulator.states.a
package fr.acinq.eclair.channel.states.a
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestConstants
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import lightning.{error, open_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
@ -14,7 +15,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForOpenNoAnchorStateSpec extends StateSpecBaseClass {
class WaitForOpenChannelStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
@ -26,24 +27,25 @@ class OpenWaitForOpenNoAnchorStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
bob2alice.expectMsgType[open_channel]
awaitCond(bob.stateName == OPEN_WAIT_FOR_OPEN_NOANCHOR)
awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL)
}
test((bob, alice2bob, bob2alice, bob2blockchain))
}
test("recv open_channel") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
test("recv OpenChannel") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
alice2bob.expectMsgType[open_channel]
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
awaitCond(bob.stateName == OPEN_WAIT_FOR_ANCHOR)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}
}
test("recv error") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
test("recv Error") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! error(Some("oops"))
bob ! Error(0, "oops".getBytes())
awaitCond(bob.stateName == CLOSED)
}
}

View file

@ -1,13 +1,13 @@
package fr.acinq.eclair.channel.simulator.states.a
package fr.acinq.eclair.channel.states.b
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{MakeAnchor, PeerWatcher}
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{OPEN_WAIT_FOR_OPEN_WITHANCHOR, _}
import lightning.{error, open_anchor, open_channel}
import fr.acinq.eclair.blockchain.{MakeFundingTx, 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, FundingCreated, OpenChannel}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -17,7 +17,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForOpenWithAnchorStateSpec extends StateSpecBaseClass {
class WaitForFundingCreatedInternalStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
@ -30,35 +30,30 @@ class OpenWaitForOpenWithAnchorStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[open_channel]
awaitCond(alice.stateName == OPEN_WAIT_FOR_OPEN_WITHANCHOR)
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}
test((alice, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv open_channel") { case (alice, alice2bob, bob2alice, _, _) =>
test("recv funding transaction") { case (alice, alice2bob, bob2alice, alice2blockchain, blockchain) =>
within(30 seconds) {
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
awaitCond(alice.stateName == OPEN_WAIT_FOR_OPEN_WITHANCHOR)
}
}
test("recv anchor") { case (alice, alice2bob, bob2alice, alice2blockchain, blockchain) =>
within(30 seconds) {
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchain)
awaitCond(alice.stateName == OPEN_WAIT_FOR_COMMIT_SIG)
alice2bob.expectMsgType[open_anchor]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
alice2bob.expectMsgType[FundingCreated]
}
}
test("recv error") { case (bob, alice2bob, bob2alice, _, _) =>
test("recv Error") { case (bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
bob ! error(Some("oops"))
bob ! Error(0, "oops".getBytes)
awaitCond(bob.stateName == CLOSED)
}
}

View file

@ -1,13 +1,13 @@
package fr.acinq.eclair.channel.simulator.states.b
package fr.acinq.eclair.channel.states.b
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.{PeerWatcher, WatchConfirmed, WatchSpent}
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{OPEN_WAITING_THEIRANCHOR, _}
import lightning.{error, open_anchor, open_channel, open_commit_sig}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateSpecBaseClass
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -17,7 +17,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForAnchorStateSpec extends StateSpecBaseClass {
class WaitForFundingCreatedStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
@ -29,28 +29,32 @@ class OpenWaitForAnchorStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, blockchainA, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice2bob.expectMsgType[open_channel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
awaitCond(bob.stateName == OPEN_WAIT_FOR_ANCHOR)
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}
test((bob, alice2bob, bob2alice, bob2blockchain))
}
test("recv open_anchor") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
test("recv FundingCreated") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
awaitCond(bob.stateName == OPEN_WAITING_THEIRANCHOR)
bob2alice.expectMsgType[open_commit_sig]
bob2blockchain.expectMsgType[WatchConfirmed]
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED_INTERNAL)
bob2alice.expectMsgType[FundingSigned]
bob2blockchain.expectMsgType[WatchSpent]
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv error") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
test("recv Error") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
within(30 seconds) {
bob ! error(Some("oops"))
bob ! Error(0, "oops".getBytes)
awaitCond(bob.stateName == CLOSED)
}
}

View file

@ -1,13 +1,14 @@
package fr.acinq.eclair.channel.simulator.states.b
package fr.acinq.eclair.channel.states.b
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.bitcoin.BinaryData
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{OPEN_WAITING_OURANCHOR, _}
import lightning._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateSpecBaseClass
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, OpenChannel}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -17,7 +18,7 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForCommitSigStateSpec extends StateSpecBaseClass {
class WaitForFundingSignedStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
@ -30,37 +31,39 @@ class OpenWaitForCommitSigStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[open_channel]
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
awaitCond(alice.stateName == OPEN_WAIT_FOR_COMMIT_SIG)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
}
test((alice, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv open_commit_sig with valid signature") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv FundingSigned with valid signature") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
awaitCond(alice.stateName == OPEN_WAITING_OURANCHOR)
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED_INTERNAL)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.expectMsgType[Publish]
}
}
test("recv open_commit_sig with invalid signature") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv FundingSigned with invalid signature") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
// sending an invalid sig
alice ! open_commit_sig(signature(0, 0, 0, 0, 0, 0, 0, 0))
alice ! FundingSigned(0, BinaryData("00" * 64))
awaitCond(alice.stateName == CLOSED)
alice2bob.expectMsgType[error]
alice2bob.expectMsgType[Error]
}
}

View file

@ -0,0 +1,122 @@
package fr.acinq.eclair.channel.states.c
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
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 org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class WaitForFundingLockedInternalStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
val bob2alice = TestProbe()
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, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.expectMsgType[Publish]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED_INTERNAL)
}
test((alice, bob, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv FundingLocked") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
// make bob send a FundingLocked msg
bob ! BITCOIN_FUNDING_DEPTHOK
val msg = bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].deferred == Some(msg))
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED_INTERNAL)
}
}
test("recv BITCOIN_FUNDING_DEPTHOK") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
alice ! BITCOIN_FUNDING_DEPTHOK
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
alice2blockchain.expectMsgType[WatchLost]
alice2bob.expectMsgType[FundingLocked]
}
}
test("recv BITCOIN_FUNDING_TIMEOUT") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
alice ! BITCOIN_FUNDING_TIMEOUT
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSED)
}
}
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_WAIT_FOR_FUNDING_LOCKED_INTERNAL].commitments.localCommit.publishableTx
alice ! (BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
}
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.publishableTx
alice ! (BITCOIN_FUNDING_SPENT, null)
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(Publish(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
}
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv CMD_CLOSE") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL].commitments.localCommit.publishableTx
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
}

View file

@ -1,13 +1,13 @@
package fr.acinq.eclair.channel.simulator.states.d
package fr.acinq.eclair.channel.states.c
import akka.actor.{ActorRef, Props}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{OPEN_WAIT_FOR_COMPLETE_OURANCHOR, _}
import lightning._
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.states.StateSpecBaseClass
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -17,9 +17,9 @@ import scala.concurrent.duration._
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
class OpenWaitForCompleteOurAnchorStateSpec extends StateSpecBaseClass {
class WaitForFundingLockedStateSpec extends StateSpecBaseClass {
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, ActorRef]
override def withFixture(test: OneArgTest) = {
val alice2bob = TestProbe()
@ -30,44 +30,77 @@ class OpenWaitForCompleteOurAnchorStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[open_channel]
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[Publish]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.forward(bob)
bob ! BITCOIN_ANCHOR_DEPTHOK
bob2blockchain.expectMsgType[WatchSpent]
bob2blockchain.expectMsgType[WatchConfirmed]
bob ! BITCOIN_FUNDING_DEPTHOK
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
awaitCond(alice.stateName == OPEN_WAIT_FOR_COMPLETE_OURANCHOR)
bob2blockchain.expectMsgType[WatchLost]
alice2bob.expectMsgType[FundingLocked]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED)
}
test((alice, alice2bob, bob2alice, alice2blockchain, blockchainA))
test((alice, bob, alice2bob, bob2alice, alice2blockchain, blockchainA))
}
test("recv open_complete") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv FundingLocked") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
}
}
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! (BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
}
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! (BITCOIN_FUNDING_SPENT, null)
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(Publish(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
}
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv CMD_CLOSE") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
@ -75,24 +108,4 @@ class OpenWaitForCompleteOurAnchorStateSpec extends StateSpecBaseClass {
}
}
test("recv BITCOIN_ANCHOR_SPENT") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
alice ! (BITCOIN_ANCHOR_SPENT, null)
alice2bob.expectMsgType[error]
alice2blockchain.expectMsg(Publish(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
}
test("recv error") { case (alice, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
}

View file

@ -1,16 +1,15 @@
package fr.acinq.eclair.channel.simulator.states.e
package fr.acinq.eclair.channel.states.e
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import com.google.protobuf.ByteString
import fr.acinq.bitcoin.{Crypto, Satoshi, Script, ScriptFlags, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Script, ScriptFlags, Transaction, TxOut}
import fr.acinq.eclair.{TestBitcoinClient, TestConstants}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.{TestBitcoinClient, _}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, Data, State, _}
import lightning._
import lightning.locktime.Locktime.Blocks
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, Data, State, _}
import fr.acinq.eclair.transactions.{IN, OldScripts}
import fr.acinq.eclair.wire.{AcceptChannel, ClosingSigned, CommitSig, Error, FundingCreated, FundingLocked, FundingSigned, OpenChannel, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -33,34 +32,36 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice2bob.expectMsgType[open_channel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[Publish]
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
alice ! INPUT_INIT_FUNDER(TestConstants.anchorAmount, 0)
bob ! INPUT_INIT_FUNDEE()
within(30 seconds) {
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
alice2blockchain.expectMsgType[Publish]
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchSpent]
bob2blockchain.expectMsgType[WatchConfirmed]
bob ! BITCOIN_FUNDING_DEPTHOK
alice2blockchain.expectMsgType[WatchLost]
bob2blockchain.expectMsgType[WatchLost]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
}
// note : alice is funder and bob is fundee, so alice has all the money
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain))
}
@ -69,13 +70,13 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
within(30 seconds) {
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
val sender = TestProbe()
val h = sha256_hash(1, 2, 3, 4)
sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(144))))
val h = BinaryData("00112233445566778899aabbccddeeff")
sender.send(alice, CMD_ADD_HTLC(500000, h, 144))
sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[update_add_htlc]
assert(htlc.id == 1 && htlc.rHash == h)
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
assert(htlc.id == 1 && htlc.paymentHash == h)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(ourCurrentHtlcId = 1, ourChanges = initialState.commitments.ourChanges.copy(proposed = htlc :: Nil)),
commitments = initialState.commitments.copy(localCurrentHtlcId = 1, localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil)),
downstreams = Map(htlc.id -> None)))
}
}
@ -83,7 +84,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _) =>
within(30 seconds) {
val sender = TestProbe()
sender.send(alice, CMD_ADD_HTLC(Int.MaxValue, sha256_hash(1, 1, 1, 1), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 144))
sender.expectMsg("insufficient funds (available=1000000000 msat)")
}
}
@ -91,11 +92,11 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
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, sha256_hash(1, 1, 1, 1), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(500000000, "11" * 32, 144))
sender.expectMsg("ok")
sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(500000000, "22" * 32, 144))
sender.expectMsg("ok")
sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(500000000, "33" * 32, 144))
sender.expectMsg("insufficient funds (available=0 msat)")
}
}
@ -103,69 +104,69 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
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, sha256_hash(1, 1, 1, 1), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 144))
sender.expectMsg("ok")
sender.send(alice, CMD_ADD_HTLC(300000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(300000000, "22" * 32, 144))
sender.expectMsg("ok")
sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(500000000, "33" * 32, 144))
sender.expectMsg("insufficient funds (available=400000000 msat)")
}
}
test("recv CMD_ADD_HTLC (while waiting for close_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))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].ourShutdown.isDefined)
// actual test starts here
sender.send(alice, CMD_ADD_HTLC(300000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 144))
sender.expectMsg("cannot send new htlcs, closing in progress")
}
}
test("recv update_add_htlc") { case (_, bob, alice2bob, _, _, _) =>
test("recv UpdateAddHtlc") { case (_, bob, alice2bob, _, _, _) =>
within(30 seconds) {
val initialData = bob.stateData.asInstanceOf[DATA_NORMAL]
val htlc = update_add_htlc(42, 150, sha256_hash(1, 2, 3, 4), locktime(Blocks(144)), routing(ByteString.EMPTY))
val htlc = UpdateAddHtlc(0, 42, 150, 144, BinaryData("00112233445566778899aabbccddeeff"), "")
bob ! htlc
awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(theirChanges = initialData.commitments.theirChanges.copy(proposed = initialData.commitments.theirChanges.proposed :+ htlc))))
awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc))))
}
}
test("recv update_add_htlc (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.ourCommit.publishableTx
val htlc = update_add_htlc(42, Int.MaxValue, sha256_hash(1, 2, 3, 4), locktime(Blocks(144)), routing(ByteString.EMPTY))
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val htlc = UpdateAddHtlc(0, 42, Long.MaxValue, 144, BinaryData("00112233445566778899aabbccddeeff"), "")
alice2bob.forward(bob, htlc)
bob2alice.expectMsgType[error]
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_add_htlc (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.ourCommit.publishableTx
alice2bob.forward(bob, update_add_htlc(42, 500000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(144)), routing(ByteString.EMPTY)))
alice2bob.forward(bob, update_add_htlc(43, 500000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(144)), routing(ByteString.EMPTY)))
alice2bob.forward(bob, update_add_htlc(44, 500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(144)), routing(ByteString.EMPTY)))
bob2alice.expectMsgType[error]
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice2bob.forward(bob, UpdateAddHtlc(0, 42, 500000000, 144, "11" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc(0, 43, 500000000, 144, "22" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc(0, 44, 500000000, 144, "33" * 32, ""))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_add_htlc (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.ourCommit.publishableTx
alice2bob.forward(bob, update_add_htlc(42, 300000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(144)), routing(ByteString.EMPTY)))
alice2bob.forward(bob, update_add_htlc(43, 300000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(144)), routing(ByteString.EMPTY)))
alice2bob.forward(bob, update_add_htlc(44, 500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(144)), routing(ByteString.EMPTY)))
bob2alice.expectMsgType[error]
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice2bob.forward(bob, UpdateAddHtlc(0, 42, 300000000, 144, "11" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc(0, 43, 300000000, 144, "22" * 32, ""))
alice2bob.forward(bob, UpdateAddHtlc(0, 44, 500000000, 144, "33" * 32, ""))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
@ -178,8 +179,8 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isLeft)
alice2bob.expectMsgType[CommitSig]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft)
}
}
@ -192,22 +193,22 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
ignore("recv CMD_SIGN (while waiting for update_revocation)") { 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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isLeft)
alice2bob.expectMsgType[CommitSig]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft)
sender.send(alice, CMD_SIGN)
sender.expectMsg("cannot sign until next revocation hash is received")
}
}
test("recv update_commit") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv CommitSig") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
@ -217,71 +218,71 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
// actual test begins
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN))
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.amount_us_msat == initialState.commitments.ourCommit.spec.amount_us_msat)
bob2alice.expectMsgType[RevokeAndAck]
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN))
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.to_local_msat == initialState.commitments.localCommit.spec.to_local_msat)
}
}
test("recv update_commit (two htlcs with same r)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv CommitSig (two htlcs with same r)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
val r = sha256_hash(1, 2, 3, 4)
val h: sha256_hash = Crypto.sha256(r)
val r = BinaryData("00112233445566778899aabbccddeeff")
val h: BinaryData = Crypto.sha256(r)
sender.send(alice, CMD_ADD_HTLC(5000000, h, locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(5000000, h, 144))
sender.expectMsg("ok")
val htlc1 = alice2bob.expectMsgType[update_add_htlc]
val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
sender.send(alice, CMD_ADD_HTLC(5000000, h, locktime(Blocks(144))))
sender.send(alice, CMD_ADD_HTLC(5000000, h, 144))
sender.expectMsg("ok")
val htlc2 = alice2bob.expectMsgType[update_add_htlc]
val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc1 :: htlc2 :: Nil)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc1 :: htlc2 :: Nil)
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sign(alice, bob, alice2bob, bob2alice)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.htlcs.exists(h => h.add.id == htlc1.id && h.direction == IN))
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.htlcs.exists(h => h.add.id == htlc2.id && h.direction == IN))
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.amount_us_msat == initialState.commitments.ourCommit.spec.amount_us_msat)
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx.txOut.count(_.amount == Satoshi(5000)) == 2)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc1.id && h.direction == IN))
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc2.id && h.direction == IN))
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.to_local_msat == initialState.commitments.localCommit.spec.to_local_msat)
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx.txOut.count(_.amount == Satoshi(5000)) == 2)
}
}
test("recv update_commit (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.ourCommit.publishableTx
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
// signature is invalid but it doesn't matter
sender.send(bob, update_commit(Some(signature(0, 0, 0, 0, 0, 0, 0, 0))))
bob2alice.expectMsgType[error]
sender.send(bob, CommitSig(0, "00" * 64, Nil))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_commit (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(500000, alice, bob, alice2bob, bob2alice)
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
// actual test begins
sender.send(bob, update_commit(Some(signature(0, 0, 0, 0, 0, 0, 0, 0))))
bob2alice.expectMsgType[error]
sender.send(bob, CommitSig(0, "00" * 64, Nil))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_revocation") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv RevokeAndAck") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
@ -289,45 +290,45 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
// actual test begins
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isLeft)
bob2alice.expectMsgType[update_revocation]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft)
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight)
}
}
test("recv update_revocation (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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
// actual test begins
bob2alice.expectMsgType[update_revocation]
sender.send(alice, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1)))
alice2bob.expectMsgType[error]
bob2alice.expectMsgType[RevokeAndAck]
sender.send(alice, RevokeAndAck(0, "11" * 32, "22" * 32, Nil))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_revocation (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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight)
sender.send(alice, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1)))
alice2bob.expectMsgType[error]
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight)
sender.send(alice, RevokeAndAck(0, "11" * 32, "22" * 32, Nil))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -344,15 +345,15 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fulfill_htlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(ourChanges = initialState.commitments.ourChanges.copy(initialState.commitments.ourChanges.proposed :+ fulfill))))
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill))))
}
}
test("recv CMD_FULFILL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
val r: rval = rval(1, 2, 3, 4)
val r: BinaryData = "11" * 32
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FULFILL_HTLC(42, r))
@ -369,13 +370,13 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
// actual test begins
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, rval(0, 0, 0, 0)))
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, "00" * 32))
sender.expectMsg("invalid htlc preimage for htlc id=1")
assert(initialState == bob.stateData)
}
}
test("recv update_fulfill_htlc (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(500000, alice, bob, alice2bob, bob2alice)
@ -383,18 +384,18 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fulfill_htlc]
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
// actual test begins
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
bob2alice.forward(alice)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fulfill)),
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
downstreams = Map()))
}
}
test("recv update_fulfill_htlc (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(500000, alice, bob, alice2bob, bob2alice)
@ -402,39 +403,39 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sign(bob, alice, bob2alice, alice2bob)
sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fulfill_htlc]
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
// actual test begins
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
bob2alice.forward(alice)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fulfill)),
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
downstreams = Map()))
}
}
test("recv update_fulfill_htlc (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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(alice, update_fulfill_htlc(42, rval(0, 0, 0, 0)))
alice2bob.expectMsgType[error]
sender.send(alice, UpdateFulfillHtlc(0, 42, "00" * 32))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_fulfill_htlc (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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
sign(alice, bob, alice2bob, bob2alice)
// actual test begins
sender.send(alice, update_fulfill_htlc(42, rval(0, 0, 0, 0)))
alice2bob.expectMsgType[error]
sender.send(alice, UpdateFulfillHtlc(0, 42, "00" * 32))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -451,15 +452,15 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FAIL_HTLC(htlc.id, "some reason"))
sender.expectMsg("ok")
val fail = bob2alice.expectMsgType[update_fail_htlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(ourChanges = initialState.commitments.ourChanges.copy(initialState.commitments.ourChanges.proposed :+ fail))))
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail))))
}
}
test("recv CMD_FAIL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
val r: rval = rval(1, 2, 3, 4)
val r: BinaryData = "11" * 32
val initialState = bob.stateData.asInstanceOf[DATA_NORMAL]
sender.send(bob, CMD_FAIL_HTLC(42, "some reason"))
@ -468,7 +469,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv update_fail_htlc (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(500000, alice, bob, alice2bob, bob2alice)
@ -476,18 +477,18 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(bob, CMD_FAIL_HTLC(htlc.id, "some reason"))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fail_htlc]
val fulfill = bob2alice.expectMsgType[UpdateFailHtlc]
// actual test begins
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
bob2alice.forward(alice)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fulfill)),
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
downstreams = Map()))
}
}
test("recv update_fail_htlc (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(500000, alice, bob, alice2bob, bob2alice)
@ -496,23 +497,23 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(bob, CMD_FAIL_HTLC(htlc.id, "some reason"))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fail_htlc]
val fulfill = bob2alice.expectMsgType[UpdateFailHtlc]
// actual test begins
val initialState = alice.stateData.asInstanceOf[DATA_NORMAL]
bob2alice.forward(alice)
awaitCond(alice.stateData == initialState.copy(
commitments = initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fulfill)),
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)),
downstreams = Map()))
}
}
test("recv update_fail_htlc (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.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(alice, update_fail_htlc(42, fail_reason(ByteString.copyFromUtf8("some reason"))))
alice2bob.expectMsgType[error]
sender.send(alice, UpdateFailHtlc(0, 42, "some reason".getBytes()))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -524,7 +525,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].ourShutdown.isEmpty)
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateName == NORMAL)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].ourShutdown.isDefined)
}
@ -535,7 +536,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val sender = TestProbe()
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].ourShutdown.isEmpty)
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateName == NORMAL)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].ourShutdown.isDefined)
sender.send(alice, CMD_CLOSE(None))
@ -543,12 +544,12 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv close_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, close_shutdown(ByteString.EMPTY))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[close_signature]
sender.send(alice, Shutdown(0, "00" * 25))
alice2bob.expectMsgType[Shutdown]
alice2bob.expectMsgType[ClosingSigned]
awaitCond(alice.stateName == NEGOTIATING)
}
}
@ -556,13 +557,13 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
/**
* see https://github.com/ElementsProject/lightning/issues/29
*/
ignore("recv close_shutdown (with unacked received htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
ignore("recv Shutdown (with unacked received htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
// actual test begins
sender.send(alice, close_shutdown(ByteString.EMPTY))
alice2bob.expectMsgType[close_shutdown]
sender.send(alice, Shutdown(0, "00" * 25))
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateName == SHUTDOWN)
}
}
@ -570,42 +571,45 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
/**
* see https://github.com/ElementsProject/lightning/issues/29
*/
ignore("recv close_shutdown (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
ignore("recv Shutdown (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val sender = TestProbe()
val (r, htlc) = addHtlc(500000, alice, bob, alice2bob, bob2alice)
// actual test begins
sender.send(alice, close_shutdown(ByteString.EMPTY))
alice2bob.expectMsgType[close_shutdown]
sender.send(alice, Shutdown(0, "00" * 25))
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateName == SHUTDOWN)
}
}
test("recv close_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(500000, alice, bob, alice2bob, bob2alice)
sign(alice, bob, alice2bob, bob2alice)
// actual test begins
sender.send(alice, close_shutdown(ByteString.EMPTY))
alice2bob.expectMsgType[close_shutdown]
sender.send(alice, Shutdown(0, "00" * 25))
alice2bob.expectMsgType[Shutdown]
awaitCond(alice.stateName == SHUTDOWN)
}
}
test("recv BITCOIN_ANCHOR_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()
val (r1, htlc1) = addHtlc(300000000, alice, bob, alice2bob, bob2alice) // id 1
val (r2, htlc2) = addHtlc(200000000, alice, bob, alice2bob, bob2alice) // id 2
val (r1, htlc1) = addHtlc(300000000, alice, bob, alice2bob, bob2alice)
// id 1
val (r2, htlc2) = addHtlc(200000000, alice, bob, alice2bob, bob2alice)
// id 2
val (r3, htlc3) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) // id 3
sign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(1, r1, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
val (r4, htlc4) = addHtlc(150000000, bob, alice, bob2alice, alice2bob) // id 1
val (r4, htlc4) = addHtlc(150000000, bob, alice, bob2alice, alice2bob)
// id 1
val (r5, htlc5) = addHtlc(120000000, bob, alice, bob2alice, alice2bob) // id 2
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
@ -623,9 +627,9 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
// bob -> alice : 120 000 000 (alice does not have the r) => nothing to do, bob will get his money back after the timeout
// bob publishes his current commit tx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
alice ! (BITCOIN_ANCHOR_SPENT, bobCommitTx)
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
@ -645,7 +649,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_ANCHOR_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()
@ -656,7 +660,7 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
sender.send(bob, CMD_FULFILL_HTLC(1, r))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fulfill_htlc]
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
bob2alice.forward(alice)
sign(bob, alice, bob2alice, alice2bob)
@ -670,8 +674,9 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val (r, htlc) = addHtlc(1000000, alice, bob, alice2bob, bob2alice)
sign(alice, bob, alice2bob, bob2alice)
bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
}
val txs = for (i <- 0 until 10) yield send()
// bob now has 10 spendable tx, 9 of them being revoked
@ -681,8 +686,8 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
// alice = 696 000
// bob = 300 000
// a->b = 4 000
alice ! (BITCOIN_ANCHOR_SPENT, revokedTx)
alice2bob.expectMsgType[error]
alice ! (BITCOIN_FUNDING_SPENT, revokedTx)
alice2bob.expectMsgType[Error]
val punishTx = alice2blockchain.expectMsgType[Publish].tx
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
@ -691,21 +696,24 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
assert(revokedTx.txOut.size == 6)
// the punishment tx consumes all output but ours (which already goes to our final key)
assert(punishTx.txIn.size == 5)
// TODO : when changefee is implemented we should set fee = 0 and check against 304 000
assert(punishTx.txOut == Seq(TxOut(Satoshi(301670), Script.write(Scripts.pay2wpkh(Alice.finalPubKey)))))
// TODO: when changefee is implemented we should set fee = 0 and check against 304 000
assert(punishTx.txOut == Seq(TxOut(Satoshi(301670), Script.write(OldScripts.pay2wpkh(Alice.channelParams.finalPrivKey.point)))))
}
}
test("recv error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv Error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val (r1, htlc1) = addHtlc(300000000, alice, bob, alice2bob, bob2alice) // id 1
val (r2, htlc2) = addHtlc(200000000, alice, bob, alice2bob, bob2alice) // id 2
val (r1, htlc1) = addHtlc(300000000, alice, bob, alice2bob, bob2alice)
// id 1
val (r2, htlc2) = addHtlc(200000000, alice, bob, alice2bob, bob2alice)
// id 2
val (r3, htlc3) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) // id 3
sign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(1, r1, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
val (r4, htlc4) = addHtlc(150000000, bob, alice, bob2alice, alice2bob) // id 1
val (r4, htlc4) = addHtlc(150000000, bob, alice, bob2alice, alice2bob)
// id 1
val (r5, htlc5) = addHtlc(120000000, bob, alice, bob2alice, alice2bob) // id 2
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
@ -723,8 +731,8 @@ class NormalStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
// bob -> alice : 120 000 000 (alice does not have the r) => nothing to do, bob will get his money back after the timeout
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes())
alice2blockchain.expectMsg(Publish(aliceCommitTx))
assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs

View file

@ -1,16 +1,15 @@
package fr.acinq.eclair.channel.simulator.states.f
package fr.acinq.eclair.channel.states.f
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import com.google.protobuf.ByteString
import fr.acinq.bitcoin.{Crypto, Satoshi, Script, ScriptFlags, Transaction, TxOut}
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Script, ScriptFlags, Transaction, TxOut}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, Data, State, _}
import fr.acinq.eclair.{TestBitcoinClient, _}
import lightning._
import lightning.locktime.Locktime.Blocks
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, Data, State, _}
import fr.acinq.eclair.transactions.OldScripts
import fr.acinq.eclair.wire.{AcceptChannel, CommitSig, Error, FundingCreated, FundingLocked, FundingSigned, OpenChannel, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -33,15 +32,15 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
val paymentHandler = TestProbe()
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams, "A"))
alice2bob.expectMsgType[open_channel]
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
@ -51,48 +50,48 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob ! BITCOIN_FUNDING_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
// note : alice is funder and bob is fundee, so alice has all the money
val sender = TestProbe()
// alice sends an HTLC to bob
val r1: rval = rval(1, 1, 1, 1)
val h1: sha256_hash = Crypto.sha256(r1)
val r1: BinaryData = "11" * 32
val h1: BinaryData = Crypto.sha256(r1)
val amount1 = 300000000
sender.send(alice, CMD_ADD_HTLC(amount1, h1, locktime(Blocks(1440))))
sender.send(alice, CMD_ADD_HTLC(amount1, h1, 1440))
sender.expectMsg("ok")
val htlc1 = alice2bob.expectMsgType[update_add_htlc]
val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc1 :: Nil)
val r2: rval = rval(2, 2, 2, 2)
val h2: sha256_hash = Crypto.sha256(r2)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc1 :: Nil)
val r2: BinaryData = "22" * 32
val h2: BinaryData = Crypto.sha256(r1)
val amount2 = 200000000
sender.send(alice, CMD_ADD_HTLC(amount2, h2, locktime(Blocks(1440))))
sender.send(alice, CMD_ADD_HTLC(amount2, h2, 1440))
sender.expectMsg("ok")
val htlc2 = alice2bob.expectMsgType[update_add_htlc]
val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc1 :: htlc2 :: Nil)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc1 :: htlc2 :: Nil)
// alice signs
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == Nil && bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.acked == htlc1 :: htlc2 :: Nil)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == Nil && bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked == htlc1 :: htlc2 :: Nil)
// alice initiates a closing
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
alice2bob.forward(bob)
bob2alice.expectMsgType[close_shutdown]
bob2alice.expectMsgType[Shutdown]
bob2alice.forward(alice)
awaitCond(alice.stateName == SHUTDOWN)
awaitCond(bob.stateName == SHUTDOWN)
@ -103,10 +102,10 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
within(30 seconds) {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FULFILL_HTLC(1, rval(1, 1, 1, 1)))
sender.send(bob, CMD_FULFILL_HTLC(1, "11" * 32))
sender.expectMsg("ok")
val fulfill = bob2alice.expectMsgType[update_fulfill_htlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(ourChanges = initialState.commitments.ourChanges.copy(initialState.commitments.ourChanges.proposed :+ fulfill))))
val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill))))
}
}
@ -114,7 +113,7 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
within(30 seconds) {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FULFILL_HTLC(42, rval(1, 2, 3, 4)))
sender.send(bob, CMD_FULFILL_HTLC(42, "12" * 32))
sender.expectMsg("unknown htlc id=42")
assert(initialState == bob.stateData)
}
@ -124,41 +123,41 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
within(30 seconds) {
val sender = TestProbe()
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FULFILL_HTLC(1, rval(0, 0, 0, 0)))
sender.send(bob, CMD_FULFILL_HTLC(1, "00" * 32))
sender.expectMsg("invalid htlc preimage for htlc id=1")
assert(initialState == bob.stateData)
}
}
test("recv update_fulfill_htlc") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv UpdateFulfillHtlc") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
val fulfill = update_fulfill_htlc(1, rval(1, 1, 1, 1))
val fulfill = UpdateFulfillHtlc(0, 1, "11" * 32)
sender.send(alice, fulfill)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fulfill)))
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)))
}
}
test("recv update_fulfill_htlc (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv UpdateFulfillHtlc (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
val fulfill = update_fulfill_htlc(42, rval(0, 0, 0, 0))
val fulfill = UpdateFulfillHtlc(0, 42, "00" * 32)
sender.send(alice, fulfill)
alice2bob.expectMsgType[error]
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_fulfill_htlc (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_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(alice, update_fulfill_htlc(42, rval(0, 0, 0, 0)))
alice2bob.expectMsgType[error]
sender.send(alice, UpdateFulfillHtlc(0, 42, "00" * 32))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -171,8 +170,8 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN]
sender.send(bob, CMD_FAIL_HTLC(1, "some reason"))
sender.expectMsg("ok")
val fail = bob2alice.expectMsgType[update_fail_htlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(ourChanges = initialState.commitments.ourChanges.copy(initialState.commitments.ourChanges.proposed :+ fail))))
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail))))
}
}
@ -186,22 +185,22 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
}
}
test("recv update_fail_htlc") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv UpdateFailHtlc") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN]
val fail = update_fail_htlc(1, fail_reason(ByteString.copyFromUtf8("some reason")))
val fail = UpdateFailHtlc(0, 1, "some reason".getBytes())
sender.send(alice, fail)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(theirChanges = initialState.commitments.theirChanges.copy(initialState.commitments.theirChanges.proposed :+ fail)))
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))
}
}
test("recv update_fail_htlc (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_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(alice, update_fail_htlc(42, fail_reason(ByteString.copyFromUtf8("some reason"))))
alice2bob.expectMsgType[error]
sender.send(alice, UpdateFailHtlc(0, 42, "some reason".getBytes()))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
@ -212,18 +211,18 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
within(30 seconds) {
val sender = TestProbe()
// we need to have something to sign so we first send a fulfill and acknowledge (=sign) it
sender.send(bob, CMD_FULFILL_HTLC(1, rval(1, 1, 1, 1)))
bob2alice.expectMsgType[update_fulfill_htlc]
sender.send(bob, CMD_FULFILL_HTLC(1, "11" * 32))
bob2alice.expectMsgType[UpdateFulfillHtlc]
bob2alice.forward(alice)
sender.send(bob, CMD_SIGN)
bob2alice.expectMsgType[update_commit]
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
alice2bob.expectMsgType[update_revocation]
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirNextCommitInfo.isLeft)
alice2bob.expectMsgType[CommitSig]
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft)
}
}
@ -236,155 +235,155 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
}
}
ignore("recv CMD_SIGN (while waiting for update_revocation)") { 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_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(alice, update_fulfill_htlc(1, rval(1, 2, 3, 4)))
sender.send(alice, UpdateFulfillHtlc(0, 1, "12" * 32))
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirNextCommitInfo.isLeft)
alice2bob.expectMsgType[CommitSig]
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft)
sender.send(alice, CMD_SIGN)
sender.expectMsg("cannot sign until next revocation hash is received")
}
}
test("recv update_commit") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv CommitSig") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val sender = TestProbe()
sender.send(bob, CMD_FULFILL_HTLC(1, rval(1, 1, 1, 1)))
sender.send(bob, CMD_FULFILL_HTLC(1, "11" * 32))
sender.expectMsg("ok")
bob2alice.expectMsgType[update_fulfill_htlc]
bob2alice.expectMsgType[UpdateFulfillHtlc]
bob2alice.forward(alice)
sender.send(bob, CMD_SIGN)
sender.expectMsg("ok")
bob2alice.expectMsgType[update_commit]
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
alice2bob.expectMsgType[update_revocation]
alice2bob.expectMsgType[RevokeAndAck]
}
}
test("recv update_commit (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_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
// signature is invalid but it doesn't matter
sender.send(bob, update_commit(Some(signature(0, 0, 0, 0, 0, 0, 0, 0))))
bob2alice.expectMsgType[error]
sender.send(bob, CommitSig(0, "00" * 64, Nil))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_commit (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv CommitSig (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(bob, update_commit(Some(signature(0, 0, 0, 0, 0, 0, 0, 0))))
bob2alice.expectMsgType[error]
sender.send(bob, CommitSig(0, "00" * 64, Nil))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_revocation (with remaining htlcs on both sides)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv RevokeAndAck (with remaining htlcs on both sides)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
fulfillHtlc(2, rval(2, 2, 2, 2), bob, alice, bob2alice, alice2bob)
fulfillHtlc(2, "22" * 32, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
val sender = TestProbe()
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
// actual test starts here
bob2alice.forward(bob)
assert(alice.stateName == SHUTDOWN)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.spec.htlcs.size == 1)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirCommit.spec.htlcs.size == 2)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.size == 1)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 2)
}
}
test("recv update_revocation (with remaining htlcs on one side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv RevokeAndAck (with remaining htlcs on one side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
fulfillHtlc(1, rval(1, 1, 1, 1), bob, alice, bob2alice, alice2bob)
fulfillHtlc(2, rval(2, 2, 2, 2), bob, alice, bob2alice, alice2bob)
fulfillHtlc(1, "11" * 32, bob, alice, bob2alice, alice2bob)
fulfillHtlc(2, "22" * 32, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
val sender = TestProbe()
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
// actual test starts here
bob2alice.forward(bob)
assert(alice.stateName == SHUTDOWN)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.spec.htlcs.isEmpty)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirCommit.spec.htlcs.size == 2)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.isEmpty)
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 2)
}
}
test("recv update_revocation (no more htlcs on either side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv RevokeAndAck (no more htlcs on either side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
fulfillHtlc(1, rval(1, 1, 1, 1), bob, alice, bob2alice, alice2bob)
fulfillHtlc(2, rval(2, 2, 2, 2), bob, alice, bob2alice, alice2bob)
fulfillHtlc(1, "11" * 32, bob, alice, bob2alice, alice2bob)
fulfillHtlc(2, "22" * 32, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
val sender = TestProbe()
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
// actual test starts here
bob2alice.forward(alice)
awaitCond(alice.stateName == NEGOTIATING)
}
}
test("recv update_revocation (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
test("recv RevokeAndAck (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) =>
within(30 seconds) {
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
sender.send(bob, CMD_FULFILL_HTLC(1, rval(1, 1, 1, 1)))
sender.send(bob, CMD_FULFILL_HTLC(1, "11" * 32))
sender.expectMsg("ok")
bob2alice.expectMsgType[update_fulfill_htlc]
bob2alice.expectMsgType[UpdateFulfillHtlc]
bob2alice.forward(alice)
sender.send(bob, CMD_SIGN)
sender.expectMsg("ok")
bob2alice.expectMsgType[update_commit]
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
alice2bob.expectMsgType[update_revocation]
awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirNextCommitInfo.isLeft)
sender.send(bob, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1)))
bob2alice.expectMsgType[error]
alice2bob.expectMsgType[RevokeAndAck]
awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft)
sender.send(bob, RevokeAndAck(0, "11" * 32, "22" * 32, Nil))
bob2alice.expectMsgType[Error]
awaitCond(bob.stateName == CLOSING)
bob2blockchain.expectMsg(Publish(tx))
bob2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv update_revocation (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_SHUTDOWN].commitments.ourCommit.publishableTx
val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
val sender = TestProbe()
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.theirNextCommitInfo.isRight)
sender.send(alice, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1)))
alice2bob.expectMsgType[error]
awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isRight)
sender.send(alice, RevokeAndAck(0, "11" * 32, "22" * 32, Nil))
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
}
}
test("recv BITCOIN_ANCHOR_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv BITCOIN_FUNDING_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
// bob publishes his current commit tx, which contains two pending htlcs alice->bob
val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
assert(bobCommitTx.txOut.size == 3) // one main outputs (bob has zero) and 2 pending htlcs
alice ! (BITCOIN_ANCHOR_SPENT, bobCommitTx)
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
@ -403,20 +402,20 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
}
}
test("recv BITCOIN_ANCHOR_SPENT (revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv BITCOIN_FUNDING_SPENT (revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
// bob fulfills one of the pending htlc (just so that he can have a new sig)
fulfillHtlc(1, rval(1, 1, 1, 1), bob, alice, bob2alice, alice2bob)
fulfillHtlc(1, "11" * 32, bob, alice, bob2alice, alice2bob)
// alice signs
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
// bob now has a new commitment tx
// bob published the revoked tx
alice ! (BITCOIN_ANCHOR_SPENT, revokedTx)
alice2bob.expectMsgType[error]
alice ! (BITCOIN_FUNDING_SPENT, revokedTx)
alice2bob.expectMsgType[Error]
val punishTx = alice2blockchain.expectMsgType[Publish].tx
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
@ -425,14 +424,14 @@ class ShutdownStateSpec extends StateSpecBaseClass with StateTestsHelperMethods
assert(revokedTx.txOut.size == 3)
// the punishment tx consumes all output but ours (which already goes to our final key)
assert(punishTx.txIn.size == 2)
assert(punishTx.txOut == Seq(TxOut(Satoshi(500000), Script.write(Scripts.pay2wpkh(Alice.finalPubKey)))))
assert(punishTx.txOut == Seq(TxOut(Satoshi(500000), Script.write(OldScripts.pay2wpkh(Alice.channelParams.finalPrivKey.point)))))
}
}
test("recv error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv Error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes)
alice2blockchain.expectMsg(Publish(aliceCommitTx))
assert(aliceCommitTx.txOut.size == 1) // only one main output

View file

@ -1,15 +1,14 @@
package fr.acinq.eclair.channel.simulator.states.g
package fr.acinq.eclair.channel.states.g
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, Data, State, _}
import fr.acinq.eclair.{TestBitcoinClient, _}
import lightning._
import lightning.locktime.Locktime.Blocks
import fr.acinq.eclair.channel.states.StateSpecBaseClass
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, Data, State, _}
import fr.acinq.eclair.wire.{AcceptChannel, ClosingSigned, CommitSig, Error, FundingCreated, FundingLocked, FundingSigned, OpenChannel, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -32,16 +31,16 @@ class NegotiatingStateSpec extends StateSpecBaseClass {
val paymentHandler = TestProbe()
// note that alice.initialFeeRate != bob.initialFeeRate
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(initialFeeRate = 20000), "A"))
alice2bob.expectMsgType[open_channel]
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(feeratePerKw = 20000), "A"))
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
@ -51,84 +50,84 @@ class NegotiatingStateSpec extends StateSpecBaseClass {
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob ! BITCOIN_FUNDING_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
// note : alice is funder and bob is fundee, so alice has all the money
val sender = TestProbe()
// alice sends an HTLC to bob
val r: rval = rval(1, 2, 3, 4)
val h: sha256_hash = Crypto.sha256(r)
val r: BinaryData = "12" * 32
val h: BinaryData = Crypto.sha256(r)
val amount = 500000
sender.send(alice, CMD_ADD_HTLC(amount, h, locktime(Blocks(3))))
sender.send(alice, CMD_ADD_HTLC(amount, h, 3))
sender.expectMsg("ok")
val htlc = alice2bob.expectMsgType[update_add_htlc]
val htlc = alice2bob.expectMsgType[UpdateAddHtlc]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc :: Nil)
// alice signs
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == Nil && bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.acked == htlc :: Nil)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == Nil && bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked == htlc :: Nil)
// bob fulfills
sender.send(bob, CMD_FULFILL_HTLC(1, r))
sender.expectMsg("ok")
bob2alice.expectMsgType[update_fulfill_htlc]
bob2alice.expectMsgType[UpdateFulfillHtlc]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed.size == 1)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed.size == 1)
// bob signs
sender.send(bob, CMD_SIGN)
sender.expectMsg("ok")
bob2alice.expectMsgType[update_commit]
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
alice2bob.expectMsgType[update_revocation]
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirCommit.spec.htlcs.isEmpty)
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.spec.htlcs.isEmpty)
// alice signs
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[update_commit]
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
bob2alice.expectMsgType[update_revocation]
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.forward(alice)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirCommit.spec.htlcs.isEmpty)
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.spec.htlcs.isEmpty)
// alice initiates a closing
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
alice2bob.forward(bob)
bob2alice.expectMsgType[close_shutdown]
bob2alice.expectMsgType[Shutdown]
bob2alice.forward(alice)
awaitCond(alice.stateName == NEGOTIATING)
awaitCond(bob.stateName == NEGOTIATING)
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain))
}
test("recv close_signature (theirCloseFee != ourCloseFee") { case (alice, bob, alice2bob, bob2alice, _, _) =>
test("recv ClosingSigned (theirCloseFee != ourCloseFee") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
val aliceCloseSig = alice2bob.expectMsgType[close_signature]
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
alice2bob.forward(bob)
val bob2aliceCloseSig = bob2alice.expectMsgType[close_signature]
assert(2 * aliceCloseSig.closeFee == bob2aliceCloseSig.closeFee)
val bob2aliceCloseSig = bob2alice.expectMsgType[ClosingSigned]
assert(2 * aliceCloseSig.feeSatoshis == bob2aliceCloseSig.feeSatoshis)
}
}
test("recv close_signature (theirCloseFee == ourCloseFee") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
test("recv ClosingSigned (theirCloseFee == ourCloseFee") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
within(30 seconds) {
var aliceCloseFee, bobCloseFee = 0L
do {
aliceCloseFee = alice2bob.expectMsgType[close_signature].closeFee
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
alice2bob.forward(bob)
bobCloseFee = bob2alice.expectMsgType[close_signature].closeFee
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
bob2alice.forward(alice)
} while (aliceCloseFee != bobCloseFee)
alice2blockchain.expectMsgType[Publish]
@ -140,33 +139,33 @@ class NegotiatingStateSpec extends StateSpecBaseClass {
}
}
test("recv BITCOIN_ANCHOR_SPENT (counterparty's mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
test("recv BITCOIN_FUNDING_SPENT (counterparty's mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
within(30 seconds) {
var aliceCloseFee, bobCloseFee = 0L
do {
aliceCloseFee = alice2bob.expectMsgType[close_signature].closeFee
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
alice2bob.forward(bob)
bobCloseFee = bob2alice.expectMsgType[close_signature].closeFee
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
if (aliceCloseFee != bobCloseFee) {
bob2alice.forward(alice)
}
} while (aliceCloseFee != bobCloseFee)
// at this point alice and bob have converged on closing fees, but alice has not yet received the final signature whereas bob has
// bob publishes the mutual close and alice is notified that the anchor has been spent
// bob publishes the mutual close and alice is notified that the funding tx has been spent
// actual test starts here
assert(alice.stateName == NEGOTIATING)
val mutualCloseTx = bob2blockchain.expectMsgType[Publish].tx
bob2blockchain.expectMsgType[WatchConfirmed]
alice ! (BITCOIN_ANCHOR_SPENT, mutualCloseTx)
alice ! (BITCOIN_FUNDING_SPENT, mutualCloseTx)
alice2blockchain.expectNoMsg()
assert(alice.stateName == NEGOTIATING)
}
}
test("recv error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
test("recv Error") { case (alice, _, alice2bob, bob2alice, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes())
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(Publish(tx))
alice2blockchain.expectMsgType[WatchConfirmed]

View file

@ -1,4 +1,4 @@
package fr.acinq.eclair.channel.simulator.states.h
package fr.acinq.eclair.channel.states.h
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
@ -6,9 +6,9 @@ import fr.acinq.bitcoin.Transaction
import fr.acinq.eclair.TestBitcoinClient
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
import fr.acinq.eclair.channel.simulator.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, Data, State, _}
import lightning._
import fr.acinq.eclair.channel.states.{StateSpecBaseClass, StateTestsHelperMethods}
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, Data, State, _}
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
@ -31,16 +31,16 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val paymentHandler = TestProbe()
// note that alice.initialFeeRate != bob.initialFeeRate
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(initialFeeRate = 20000), "A"))
alice2bob.expectMsgType[open_channel]
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(feeratePerKw = 20000), "A"))
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[OpenChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
@ -50,13 +50,13 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob ! BITCOIN_FUNDING_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
@ -64,27 +64,27 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val bobCommitTxes: List[Transaction] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
sign(alice, bob, alice2bob, bob2alice)
val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
fulfillHtlc(htlc.id, r, bob, alice, bob2alice, alice2bob)
sign(bob, alice, bob2alice, alice2bob)
sign(alice, bob, alice2bob, bob2alice)
val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
bobCommitTx1 :: bobCommitTx2 :: Nil
}).flatten
val sender = TestProbe()
// alice initiates a closing
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[close_shutdown]
alice2bob.expectMsgType[Shutdown]
alice2bob.forward(bob)
bob2alice.expectMsgType[close_shutdown]
bob2alice.expectMsgType[Shutdown]
bob2alice.forward(alice)
// agreeing on a closing fee
var aliceCloseFee, bobCloseFee = 0L
do {
aliceCloseFee = alice2bob.expectMsgType[close_signature].closeFee
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
alice2bob.forward(bob)
bobCloseFee = bob2alice.expectMsgType[close_signature].closeFee
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
bob2alice.forward(alice)
} while (aliceCloseFee != bobCloseFee)
alice2blockchain.expectMsgType[Publish]
@ -104,7 +104,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_ANCHOR_SPENT (our commit)") { case (_, _, _, _, _, _, _) =>
test("recv BITCOIN_FUNDING_SPENT (our commit)") { case (_, _, _, _, _, _, _) =>
within(30 seconds) {
// this test needs a specific intialization because we need to have published our own commitment tx (that's why ignored fixture args)
// to do that alice will receive an error packet when in NORMAL state, which will make her publish her commit tx and then reach CLOSING state
@ -117,16 +117,16 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val paymentHandler = TestProbe()
// note that alice.initialFeeRate != bob.initialFeeRate
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(initialFeeRate = 20000), "A"))
alice2bob.expectMsgType[open_channel]
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(feeratePerKw = 20000), "A"))
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[OpenChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
@ -136,20 +136,20 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob ! BITCOIN_FUNDING_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes)
alice2blockchain.expectMsg(Publish(aliceCommitTx))
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
awaitCond(alice.stateName == CLOSING)
@ -157,7 +157,7 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
assert(initialState.ourCommitPublished == Some(aliceCommitTx))
// actual test starts here
alice ! (BITCOIN_ANCHOR_SPENT, aliceCommitTx)
alice ! (BITCOIN_FUNDING_SPENT, aliceCommitTx)
assert(alice.stateData == initialState)
}
}
@ -175,16 +175,16 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
val paymentHandler = TestProbe()
// note that alice.initialFeeRate != bob.initialFeeRate
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, paymentHandler.ref, Alice.channelParams, "B"))
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(initialFeeRate = 20000), "A"))
alice2bob.expectMsgType[open_channel]
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, paymentHandler.ref, Bob.channelParams.copy(feeratePerKw = 20000), "A"))
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_channel]
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[MakeAnchor]
alice2blockchain.expectMsgType[MakeFundingTx]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_anchor]
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
bob2alice.expectMsgType[open_commit_sig]
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchConfirmed]
alice2blockchain.forward(blockchainA)
@ -194,20 +194,20 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
alice2blockchain.forward(blockchainA)
bob2blockchain.expectMsgType[WatchConfirmed]
bob2blockchain.expectMsgType[WatchSpent]
bob ! BITCOIN_ANCHOR_DEPTHOK
bob ! BITCOIN_FUNDING_DEPTHOK
bob2blockchain.expectMsgType[WatchLost]
bob2alice.expectMsgType[open_complete]
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
alice2blockchain.expectMsgType[WatchLost]
alice2blockchain.forward(blockchainA)
alice2bob.expectMsgType[open_complete]
alice2bob.expectMsgType[FundingLocked]
alice2bob.forward(bob)
awaitCond(alice.stateName == NORMAL)
awaitCond(bob.stateName == NORMAL)
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx
alice ! error(Some("oops"))
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTx
alice ! Error(0, "oops".getBytes())
alice2blockchain.expectMsg(Publish(aliceCommitTx))
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
awaitCond(alice.stateName == CLOSING)
@ -219,13 +219,13 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_ANCHOR_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
test("recv BITCOIN_FUNDING_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// 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_ANCHOR_SPENT, bobCommitTx)
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
@ -239,7 +239,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_ANCHOR_SPENT, bobCommitTx)
alice ! (BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING] == initialState.copy(theirCommitPublished = Some(bobCommitTx)))
@ -249,12 +249,12 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_ANCHOR_SPENT (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
test("recv BITCOIN_FUNDING_SPENT (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head
alice ! (BITCOIN_ANCHOR_SPENT, bobRevokedTx)
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
// alice publishes and watches the stealing tx
alice2blockchain.expectMsgType[Publish]
@ -264,12 +264,12 @@ class ClosingStateSpec extends StateSpecBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_ANCHOR_SPENT (multiple revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
test("recv BITCOIN_FUNDING_SPENT (multiple revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
// bob publishes multiple revoked txes (last one isn't revoked)
for (bobRevokedTx <- bobCommitTxes.dropRight(1)) {
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
alice ! (BITCOIN_ANCHOR_SPENT, bobRevokedTx)
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
// alice publishes and watches the stealing tx
alice2blockchain.expectMsgType[Publish]
alice2blockchain.expectMsgType[WatchConfirmed]
@ -284,7 +284,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_ANCHOR_SPENT, bobRevokedTx)
alice ! (BITCOIN_FUNDING_SPENT, bobRevokedTx)
// alice publishes and watches the stealing tx
alice2blockchain.expectMsgType[Publish]
alice2blockchain.expectMsgType[WatchConfirmed]

View file

@ -1,46 +0,0 @@
package fr.acinq.eclair.crypto
import fr.acinq.eclair.io.AuthHandler
import AuthHandler._
import fr.acinq.eclair.crypto.LightningCrypto._
import lightning.{error, pkt}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 27/10/2015.
*/
@RunWith(classOf[JUnitRunner])
class AuthHandlerSpec extends FunSuite {
test("encrypt/decrypt loop") {
val keypairA = randomKeyPair()
val keypairB = randomKeyPair()
val secrets = generate_secrets(ecdh(keypairA.pub, keypairB.priv), keypairA.pub)
val cipher_in = aesDecryptCipher(secrets.aes_key, secrets.aes_iv)
val cipher_out = aesEncryptCipher(secrets.aes_key, secrets.aes_iv)
val msg1 = pkt().withError(error(Some("hello")))
val (data, totlen) = writeMsg(msg1, secrets, cipher_out, 0)
var msg2: pkt = null
val (_, totlen2) = split(data, secrets, cipher_in, 0, d => msg2 = pkt.parseFrom(d))
assert(msg1 === msg2)
}
test("decrypt msgs received in bulk") {
val keypairA = randomKeyPair()
val keypairB = randomKeyPair()
val secrets = generate_secrets(ecdh(keypairA.pub, keypairB.priv), keypairA.pub)
val cipher_in = aesDecryptCipher(secrets.aes_key, secrets.aes_iv)
val cipher_out = aesEncryptCipher(secrets.aes_key, secrets.aes_iv)
val msgs = for (i <- 0 until 3) yield pkt().withError(error(Some(s"hello #$i!")))
val (data0, totlen0) = writeMsg(msgs(0), secrets, cipher_out, 0)
val (data1, totlen1) = writeMsg(msgs(1), secrets, cipher_out, totlen0)
val (data2, totlen2) = writeMsg(msgs(2), secrets, cipher_out, totlen1)
val data = data0 ++ data1 ++ data2
var splitted: Seq[pkt] = Nil
split(data, secrets, cipher_in, 0, d => splitted = splitted :+ pkt.parseFrom(d))
assert(msgs === splitted)
}
}

View file

@ -4,15 +4,14 @@ import java.util.concurrent.{CountDownLatch, TimeUnit}
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import akka.util.ByteString
import fr.acinq.eclair._
import fr.acinq.bitcoin.{BinaryData, Crypto, Hash}
import fr.acinq.eclair.channel.simulator.Pipe
import fr.acinq.eclair.channel.Pipe
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
object EncryptorSpec {
val random = new scala.util.Random()
@ -22,7 +21,7 @@ object EncryptorSpec {
def receive = running(Encryptor(sendingKey, 0), Decryptor(receivinKey, 0))
def running(encryptor: Encryptor, decryptor: Decryptor) : Receive = {
def running(encryptor: Encryptor, decryptor: Decryptor): Receive = {
case chunk: BinaryData =>
val decryptor1 = Decryptor.add(decryptor, ByteString.fromArray(chunk))
decryptor1.bodies.map(_ => latch.countDown())
@ -34,10 +33,12 @@ object EncryptorSpec {
context become running(encryptor1, decryptor)
}
}
}
@RunWith(classOf[JUnitRunner])
class EncryptorSpec extends FunSuite {
import EncryptorSpec._
test("encryption/description tests") {

Some files were not shown because too many files have changed in this diff Show more