mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-15 04:11:33 +01:00
Update Android branch (#746)
* Fixed regression in rebroadcast (#713) Fixed regression caused by2c1811d
: we now don't force sending a channel_update at the same time with channel_announcement. This greatly simplifies the rebroadcast logic, and is what caused the integration test to fail. Added proper test on Peer, testing the actor, not only static methods. * Routing sync fixes (#712) * Router: reset sync state on reconnection When we're reconnected to a peer we will start a new sync process and should reset our sync state with that peer. * Ignore 'origin htlc not found' in CLOSING (#708) If we don't have the origin, it means that we already have forwarded the fulfill so that's not a big deal. This can happen if they send a signature containing the fulfill, then fail the channel before we have time to sign it. * Fix handling of born again channels (#717) * Fix handling of born again channels When we receive a recent update for a channel that we had marked as stale we must send a query to the underlying transport, not the origin of the update (which would send the query back to the router) * Replace `update_fee` in commitments (#709) This is a simple optimisation, we don't have to keep all `update_fee`, just the last one. cf BOLT 2: > An update_fee message is sent by the node which is paying the Bitcoin fee. Like any update, it's first committed to the receiver's commitment transaction and then (once acknowledged) committed to the sender's. Unlike an HTLC, update_fee is never closed but simply replaced. * Tests: use bitcoind 0.16.3 (#715) Bitcoind 0.16.0 is no longer available * Make `publishTransaction` idempotent (#711) Bitcoin core returns an error `missing inputs (code: -25)` if the tx that we want to publish has already been published and its output have been spent. When we receive this error, we try to get the tx, in order to know if it is in the blockchain, or if its inputs were spent by another tx. Note: If the outputs of the tx were still unspent, bitcoin core would return "transaction already in block chain (code: -27)" and this is already handled. * Improved eclair-cli (#718) This fixes #695, and also adds the channel point in the default channel output. ```bash $ ./eclair-cli channel 00fd4d56d94af93765561bb6cb081f519b9627d3f455eba3215a7846a1af0e46 { "nodeId": "0232e20e7b68b9b673fb25f48322b151a93186bffe4550045040673797ceca43cf", "shortChannelId": "845230006070001", "channelId": "00fd4d56d94af93765561bb6cb081f519b9627d3f455eba3215a7846a1af0e46", "state": "NORMAL", "balanceSat": 9858759, "capacitySat": 10000000, "channelPoint": "470eafa146785a21a3eb55f4d327969b511f08cbb61b566537f94ad9564dfd00:1" } ``` * Handle update relay fee in OFFLINE state (#719) Previously it was only possible to update relay fee in NORMAL state, which is not very convenient because most of the time there are always some channels in OFFLINE state. This works like the NORMAL case, except that the new `channel_update` won't be broadcast immediately. It will be sent out next time the channel goes back to NORMAL, in the same `channel_update` that sets the `enable` flag to true. Also added a default handler that properly rejects the CMD_UPDATE_RELAY_FEE command in all other states. * Fixed regression caused by7a4f175
(#722) When updating relay fee in state OFFLINE, the new channel_update must have the disabled flag on. This caused tests to be flaky, added necessary checks to always make them fail in case that kind of regression happens again. * Logging: use a rolling file appender (#721) * Logging: use a rolling file appender Use one file per day, keep 90 days of logs with a total maximum size capped at 5 Gb * Router: log routing broadcast in debug level only * set version to 0.2-beta6 * set version back to 0.2-SNAPSHOT * Simplify bitcoind version check (#731) Bitcoind returns version as MMmmrr (major, minor, revision), use an int representation and compare it to our minimum version target. * Update scalatest and remove junit runner (#728) * updated to scalatest 3.0.5 * use scalatest runner instead of junit Output is far more readable, and makes console (incl. travis) reports actually usable. Turned off test logs as error reporting is enough to figure out what happens. The only downside is that we can't use junit's categories to group tests, like we did for docker related tests. We could use nested suites, but that seems to be overkill so I just removed the categories. Users will only have the possibility to either skip/run all tests. * update scala-maven-plugin to 3.4.2 NB: This requires maven 3.5.4, which means that we currently need to manually install maven on travis. Also updated Docker java version to 8u181 (8u171 for compiling). * Add instructions for Bitcoin Core 0.17.0 [ci skip] (#732) * Add instructions for Bitcoin Core 0.17.0 [ci skip] Bitcoin Core 0.17.0 deprecates the `signrawtransaction` RPC call, which will be removed in version 0.18.0, you need to enable this call if you want your eclair node to use a 0.1.70 node. * README: add an example of how to use the new bitcoin.conf sections [ci skip] * Only persist trimmed htlcs (#724) We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our counterparty, so only htlcs above remote's `dust_limit` matter. Removed the TODO because we need data to be indexed by commit number so it is ok to write the same htlc data for every commitment it is included in. * set version to 0.2-beta7 * set version to 0.2-SNAPSHOT * Add `htlcMaximumMsat` field to `ChannelUpdate` message (#738) * Add `htlcMaximumMsat` field to `ChannelUpdate` message * added compatibility test with c-lightning * Fix encoding of FinalIncorrectHtlcAmount error message (#740) * set version to 0.2-beta8 * set version to 0.2-SNAPSHOT * Always add 1 block to the `finalCltvExpiry` (#742) This fixes #651. * ignore IntegrationSpec (no server on android) * back to SNAPSHOT * use proper [gs]etNullableLong method for Sqlite
This commit is contained in:
parent
2379807f2e
commit
dae5cc718a
108 changed files with 4314 additions and 4203 deletions
|
@ -7,6 +7,11 @@ scala:
|
||||||
- 2.11.11
|
- 2.11.11
|
||||||
env:
|
env:
|
||||||
- export LD_LIBRARY_PATH=/usr/local/lib
|
- export LD_LIBRARY_PATH=/usr/local/lib
|
||||||
|
before_install:
|
||||||
|
- wget http://mirror.ibcp.fr/pub/apache/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.zip
|
||||||
|
- unzip -qq apache-maven-3.5.4-bin.zip
|
||||||
|
- export M2_HOME=$PWD/apache-maven-3.5.4
|
||||||
|
- export PATH=$M2_HOME/bin:$PATH
|
||||||
script:
|
script:
|
||||||
- mvn install
|
- mvn install
|
||||||
cache:
|
cache:
|
||||||
|
|
6
BUILD.md
6
BUILD.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- [Java Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 1.8u161 or newer
|
- [Java Development Kit](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) 1.8u161 or newer
|
||||||
- [Maven](https://maven.apache.org/download.cgi) 3.3.x or newer
|
- [Maven](https://maven.apache.org/download.cgi) 3.5.4 or newer
|
||||||
- [Inno Setup](http://www.jrsoftware.org/isdl.php) 5.5.9 (optional, if you want to generate the windows installer)
|
- [Inno Setup](http://www.jrsoftware.org/isdl.php) 5.5.9 (optional, if you want to generate the windows installer)
|
||||||
- [Docker](https://www.docker.com/) 18.03 or newer (optional) if you want to run all tests
|
- [Docker](https://www.docker.com/) 18.03 or newer (optional) if you want to run all tests
|
||||||
|
|
||||||
|
@ -14,10 +14,6 @@ $ mvn install
|
||||||
|
|
||||||
#### Other build options
|
#### Other build options
|
||||||
|
|
||||||
If you don't have Docker you can skip tests that depend on it:
|
|
||||||
```shell
|
|
||||||
$ mvn install -DexcludedGroups=fr.acinq.eclair.blockchain.electrum.DockerTest
|
|
||||||
```
|
|
||||||
To skip all tests, run:
|
To skip all tests, run:
|
||||||
```shell
|
```shell
|
||||||
$ mvn install -DskipTests
|
$ mvn install -DskipTests
|
||||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -1,4 +1,4 @@
|
||||||
FROM openjdk:8u151-jdk-alpine as BUILD
|
FROM openjdk:8u171-jdk-alpine as BUILD
|
||||||
|
|
||||||
# Setup maven, we don't use https://hub.docker.com/_/maven/ as it declare .m2 as volume, we loose all mvn cache
|
# Setup maven, we don't use https://hub.docker.com/_/maven/ as it declare .m2 as volume, we loose all mvn cache
|
||||||
# We can alternatively do as proposed by https://github.com/carlossg/docker-maven#packaging-a-local-repository-with-the-image
|
# We can alternatively do as proposed by https://github.com/carlossg/docker-maven#packaging-a-local-repository-with-the-image
|
||||||
|
@ -6,9 +6,9 @@ FROM openjdk:8u151-jdk-alpine as BUILD
|
||||||
|
|
||||||
RUN apk add --no-cache curl tar bash
|
RUN apk add --no-cache curl tar bash
|
||||||
|
|
||||||
ARG MAVEN_VERSION=3.3.9
|
ARG MAVEN_VERSION=3.5.4
|
||||||
ARG USER_HOME_DIR="/root"
|
ARG USER_HOME_DIR="/root"
|
||||||
ARG SHA=6e3e9c949ab4695a204f74038717aa7b2689b1be94875899ac1b3fe42800ff82
|
ARG SHA=ce50b1c91364cb77efe3776f756a6d92b76d9038b0a0782f7d53acf1e997a14d
|
||||||
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
|
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
|
||||||
|
|
||||||
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
|
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
|
||||||
|
@ -31,7 +31,8 @@ COPY eclair-node/pom.xml eclair-node/pom.xml
|
||||||
COPY eclair-node-gui/pom.xml eclair-node-gui/pom.xml
|
COPY eclair-node-gui/pom.xml eclair-node-gui/pom.xml
|
||||||
RUN mkdir -p eclair-core/src/main/scala && touch eclair-core/src/main/scala/empty.scala
|
RUN mkdir -p eclair-core/src/main/scala && touch eclair-core/src/main/scala/empty.scala
|
||||||
# Blank build. We only care about eclair-node, and we use install because eclair-node depends on eclair-core
|
# Blank build. We only care about eclair-node, and we use install because eclair-node depends on eclair-core
|
||||||
RUN mvn install -pl eclair-node -am clean
|
RUN mvn install -pl eclair-node -am
|
||||||
|
RUN mvn clean
|
||||||
|
|
||||||
# Only then do we copy the sources
|
# Only then do we copy the sources
|
||||||
COPY . .
|
COPY . .
|
||||||
|
@ -41,7 +42,7 @@ RUN mvn package -pl eclair-node -am -DskipTests -Dgit.commit.id=notag -Dgit.comm
|
||||||
# It might be good idea to run the tests here, so that the docker build fail if the code is bugged
|
# It might be good idea to run the tests here, so that the docker build fail if the code is bugged
|
||||||
|
|
||||||
# We currently use a debian image for runtime because of some jni-related issue with sqlite
|
# We currently use a debian image for runtime because of some jni-related issue with sqlite
|
||||||
FROM openjdk:8u171-jre-slim
|
FROM openjdk:8u181-jre-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# Eclair only needs the eclair-node-*.jar to run
|
# Eclair only needs the eclair-node-*.jar to run
|
||||||
COPY --from=BUILD /usr/src/eclair-node/target/eclair-node-*.jar .
|
COPY --from=BUILD /usr/src/eclair-node/target/eclair-node-*.jar .
|
||||||
|
|
36
README.md
36
README.md
|
@ -30,7 +30,7 @@ Please see the latest [release note](https://github.com/ACINQ/eclair/releases) f
|
||||||
|
|
||||||
### Configuring Bitcoin Core
|
### Configuring Bitcoin Core
|
||||||
|
|
||||||
:warning: Eclair requires Bitcoin Core 0.16.0 or higher. If you are upgrading an existing wallet, you need to create a new address and send all your funds to that address.
|
:warning: Eclair requires Bitcoin Core 0.16.3 or higher. If you are upgrading an existing wallet, you need to create a new address and send all your funds to that address.
|
||||||
|
|
||||||
Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node.
|
Eclair needs a _synchronized_, _segwit-ready_, **_zeromq-enabled_**, _wallet-enabled_, _non-pruning_, _tx-indexing_ [Bitcoin Core](https://github.com/bitcoin/bitcoin) node.
|
||||||
Eclair will use any BTC it finds in the Bitcoin Core wallet to fund any channels you choose to open. Eclair will return BTC from closed channels to this wallet.
|
Eclair will use any BTC it finds in the Bitcoin Core wallet to fund any channels you choose to open. Eclair will return BTC from closed channels to this wallet.
|
||||||
|
@ -47,6 +47,11 @@ zmqpubrawtx=tcp://127.0.0.1:29000
|
||||||
addresstype=p2sh-segwit
|
addresstype=p2sh-segwit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:warning: If you are using Bitcoin Core 0.17.0 you need to add following line to your `bitcoin.conf`:
|
||||||
|
```
|
||||||
|
deprecatedrpc=signrawtransaction
|
||||||
|
```
|
||||||
|
|
||||||
### Installing Eclair
|
### Installing Eclair
|
||||||
|
|
||||||
The released binaries can be downloaded [here](https://github.com/ACINQ/eclair/releases).
|
The released binaries can be downloaded [here](https://github.com/ACINQ/eclair/releases).
|
||||||
|
@ -198,16 +203,39 @@ zmqpubrawtx=tcp://127.0.0.1:29000
|
||||||
addresstype=p2sh-segwit
|
addresstype=p2sh-segwit
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:warning: If you are using Bitcoin Core 0.17.0 you need to add following line to your `bitcoin.conf`:
|
||||||
|
```
|
||||||
|
deprecatedrpc=signrawtransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also want to take advantage of the new configuration sections in `bitcoin.conf` to manage parameters that are network speficic, so you can reasliy run your bitcoin node on both mainnet and testnet. For example you could use:
|
||||||
|
|
||||||
|
```
|
||||||
|
server=1
|
||||||
|
txindex=1
|
||||||
|
addresstype=p2sh-segwit
|
||||||
|
deprecatedrpc=signrawtransaction
|
||||||
|
[main]
|
||||||
|
rpcuser=<your-mainnet-rpc-user-here>
|
||||||
|
rpcpassword=<your-mainnet-rpc-password-here>
|
||||||
|
zmqpubrawblock=tcp://127.0.0.1:29000
|
||||||
|
zmqpubrawtx=tcp://127.0.0.1:29000
|
||||||
|
[test]
|
||||||
|
rpcuser=<your-testnet-rpc-user-here>
|
||||||
|
rpcpassword=<your-testnet-rpc-password-here>
|
||||||
|
zmqpubrawblock=tcp://127.0.0.1:29001
|
||||||
|
zmqpubrawtx=tcp://127.0.0.1:29001
|
||||||
|
```
|
||||||
|
|
||||||
### Eclair configuration
|
### Eclair configuration
|
||||||
|
|
||||||
```
|
```
|
||||||
eclair.chain=mainnet
|
eclair.chain=mainnet
|
||||||
eclair.bitcoind.rpcport=8332
|
eclair.bitcoind.rpcport=8332
|
||||||
eclair.bitcoind.rpcuser=<your-bitcoin-core-rpc-user-here>
|
eclair.bitcoind.rpcuser=<your-mainnet-rpc-user-here>
|
||||||
eclair.bitcoind.rpcpassword=<your-bitcoin-core-rpc-passsword-here>
|
eclair.bitcoind.rpcpassword=<your-mainnet-rpc-password-here>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
- [1] [The Bitcoin Lightning Network: Scalable Off-Chain Instant Payments](https://lightning.network/lightning-network-paper.pdf) by Joseph Poon and Thaddeus Dryja
|
- [1] [The Bitcoin Lightning Network: Scalable Off-Chain Instant Payments](https://lightning.network/lightning-network-paper.pdf) by Joseph Poon and Thaddeus Dryja
|
||||||
- [2] [Reaching The Ground With Lightning](https://github.com/ElementsProject/lightning/raw/master/doc/deployable-lightning.pdf) by Rusty Russell
|
- [2] [Reaching The Ground With Lightning](https://github.com/ElementsProject/lightning/raw/master/doc/deployable-lightning.pdf) by Rusty Russell
|
||||||
|
|
|
@ -80,17 +80,15 @@ case ${METHOD}_${#} in
|
||||||
"receive_2") call ${METHOD} "'$(printf '[%s,"%s"]' "${1}" "${2}")'" ;; # ${1} is numeric (amount to receive)
|
"receive_2") call ${METHOD} "'$(printf '[%s,"%s"]' "${1}" "${2}")'" ;; # ${1} is numeric (amount to receive)
|
||||||
"receive_3") call ${METHOD} "'$(printf '[%s,"%s",%s]' "${1}" "${2}" "${3}")'" ;; # ${1} is numeric (amount to receive) as is ${2} for expiry in seconds
|
"receive_3") call ${METHOD} "'$(printf '[%s,"%s",%s]' "${1}" "${2}" "${3}")'" ;; # ${1} is numeric (amount to receive) as is ${2} for expiry in seconds
|
||||||
|
|
||||||
"channel_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceSat: (.data.commitments.localCommit.spec.toLocalMsat / 1000 | floor), capacitySat: .data.commitments.commitInput.amountSatoshis } end" ;;
|
"channel_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceSat: (try (.data.commitments.localCommit.spec.toLocalMsat / 1000 | floor) catch null), capacitySat: .data.commitments.commitInput.amountSatoshis, channelPoint: .data.commitments.commitInput.outPoint } end" ;;
|
||||||
|
|
||||||
"channels_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | map( { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceSat: (.data.commitments.localCommit.spec.toLocalMsat / 1000 | floor), capacitySat: .data.commitments.commitInput.amountSatoshis } ) end" ;;
|
"channels_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | map( { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceSat: (try (.data.commitments.localCommit.spec.toLocalMsat / 1000 | floor) catch null), capacitySat: .data.commitments.commitInput.amountSatoshis, channelPoint: .data.commitments.commitInput.outPoint } ) end" ;;
|
||||||
|
|
||||||
"channels_"*) call ${METHOD} "'${PARAMS}'" "if .error != null then .error.message else .result | map( { nodeId, shortChannelId: .data.shortChannelId, channelId, state, balanceMsat: .data.commitments.localCommit.spec.toLocalMsat, capacitySat: .data.commitments.commitInput.txOut.amount.amount } ) end" ;;
|
|
||||||
|
|
||||||
"send_3") call ${METHOD} "'$(printf '[%s,"%s","%s"]' "${1}" "${2}" "${3}")'" ;; # ${1} is numeric (amount of the payment)
|
"send_3") call ${METHOD} "'$(printf '[%s,"%s","%s"]' "${1}" "${2}" "${3}")'" ;; # ${1} is numeric (amount of the payment)
|
||||||
"send_2") call ${METHOD} "'$(printf '["%s",%s]' "${1}" "${2}")'" ;; # ${2} is numeric (amount overriding the payment request)
|
"send_2") call ${METHOD} "'$(printf '["%s",%s]' "${1}" "${2}")'" ;; # ${2} is numeric (amount overriding the payment request)
|
||||||
|
|
||||||
"audit_2") call ${METHOD} "'$(printf '[%s,%s]' "${1}" "${2}")'" ;; # ${1} and ${2} are numeric (unix timestamps)
|
"audit_2") call ${METHOD} "'$(printf '[%s,%s]' "${1}" "${2}")'" ;; # ${1} and ${2} are numeric (unix timestamps)
|
||||||
|
|
||||||
"networkfees_2") call ${METHOD} "'$(printf '[%s,%s]' "${1}" "${2}")'" ;; # ${1} and ${2} are numeric (unix timestamps)
|
"networkfees_2") call ${METHOD} "'$(printf '[%s,%s]' "${1}" "${2}")'" ;; # ${1} and ${2} are numeric (unix timestamps)
|
||||||
|
|
||||||
*) # Default case.
|
*) # Default case.
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>fr.acinq.eclair</groupId>
|
<groupId>fr.acinq.eclair</groupId>
|
||||||
<artifactId>eclair_2.11</artifactId>
|
<artifactId>eclair_2.11</artifactId>
|
||||||
<version>0.2-android-beta12</version>
|
<version>0.2-android-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>eclair-core_2.11</artifactId>
|
<artifactId>eclair-core_2.11</artifactId>
|
||||||
|
@ -79,10 +79,10 @@
|
||||||
<activeByDefault>true</activeByDefault>
|
<activeByDefault>true</activeByDefault>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-x86_64-linux-gnu.tar.gz
|
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz
|
||||||
</bitcoind.url>
|
</bitcoind.url>
|
||||||
<bitcoind.md5>1375c9f908b0327d9772d4bff0d9f03f</bitcoind.md5>
|
<bitcoind.md5>c371e383f024c6c45fb255d528a6beec</bitcoind.md5>
|
||||||
<bitcoind.sha1>d0b05b51e1d572c44ef5b2cabbfcb662679cf7cb</bitcoind.sha1>
|
<bitcoind.sha1>e6d8ab1f7661a6654fd81e236b9b5fd35a3d4dcb</bitcoind.sha1>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
|
@ -93,10 +93,10 @@
|
||||||
</os>
|
</os>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-osx64.tar.gz
|
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-osx64.tar.gz
|
||||||
</bitcoind.url>
|
</bitcoind.url>
|
||||||
<bitcoind.md5>fc10d5cb12a4c3905d97df33f249eb1c</bitcoind.md5>
|
<bitcoind.md5>bacd87d0c3f65a5acd666e33d094a59e</bitcoind.md5>
|
||||||
<bitcoind.sha1>3b1ab75439ca7a9b547827ec3e59c7e61c1f6fcd</bitcoind.sha1>
|
<bitcoind.sha1>62cc5bd9ced610bb9e8d4a854396bfe2139e3d0f</bitcoind.sha1>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
|
@ -107,9 +107,9 @@
|
||||||
</os>
|
</os>
|
||||||
</activation>
|
</activation>
|
||||||
<properties>
|
<properties>
|
||||||
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-win64.zip</bitcoind.url>
|
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-win64.zip</bitcoind.url>
|
||||||
<bitcoind.md5>5b9034754752b7e1b3117eaa5434792e</bitcoind.md5>
|
<bitcoind.md5>bbde9b1206956d19298034319e9f405e</bitcoind.md5>
|
||||||
<bitcoind.sha1>90d72e25782a4b454f5f507a26a3cf0f53baecef</bitcoind.sha1>
|
<bitcoind.sha1>85e3dc8a9c6f93b1b20cb79fa5850b5ce81da221</bitcoind.sha1>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
|
@ -190,7 +190,6 @@ class ZmqWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContext =
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
after(3 seconds, context.system.scheduler)(Future.successful({})).map(x => publish(tx, isRetry = true))
|
after(3 seconds, context.system.scheduler)(Future.successful({})).map(x => publish(tx, isRetry = true))
|
||||||
case t: Throwable if t.getMessage.contains("(code: -27)") => () // "transaction already in block chain (code: -27)" ignore error
|
|
||||||
case t: Throwable => log.error(s"cannot publish tx: reason=${t.getMessage} txid=${tx.txid} tx=$tx")
|
case t: Throwable => log.error(s"cannot publish tx: reason=${t.getMessage} txid=${tx.txid} tx=$tx")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,13 +106,27 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
|
||||||
future
|
future
|
||||||
}
|
}
|
||||||
|
|
||||||
def publishTransaction(hex: String)(implicit ec: ExecutionContext): Future[String] =
|
/**
|
||||||
rpcClient.invoke("sendrawtransaction", hex) collect {
|
* Publish a transaction on the bitcoin network.
|
||||||
case JString(txid) => txid
|
*
|
||||||
}
|
* Note that this method is idempotent, meaning that if the tx was already published a long time ago, then this is
|
||||||
|
* considered a success even if bitcoin core rejects this new attempt.
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
* @param ec
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
|
def publishTransaction(tx: Transaction)(implicit ec: ExecutionContext): Future[String] =
|
||||||
publishTransaction(tx.toString())
|
rpcClient.invoke("sendrawtransaction", tx.toString()) collect {
|
||||||
|
case JString(txid) => txid
|
||||||
|
} recoverWith {
|
||||||
|
case JsonRPCError(Error(-27, _)) =>
|
||||||
|
// "transaction already in block chain (code: -27)" ignore error
|
||||||
|
Future.successful(tx.txid.toString())
|
||||||
|
case e@JsonRPCError(Error(-25, _)) =>
|
||||||
|
// "missing inputs (code: -25)" it may be that the tx has already been published and its output spent
|
||||||
|
getRawTransaction(tx.txid.toString()).map { case _ => tx.txid.toString() }.recoverWith { case _ => Future.failed[String](e) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need this to compute absolute timeouts expressed in number of blocks (where getBlockCount would be equivalent
|
* We need this to compute absolute timeouts expressed in number of blocks (where getBlockCount would be equivalent
|
||||||
|
|
|
@ -187,7 +187,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
// we rebuild a channel_update for two reasons:
|
// we rebuild a channel_update for two reasons:
|
||||||
// - we want to reload values from configuration
|
// - we want to reload values from configuration
|
||||||
// - if eclair was previously killed, it might not have had time to publish a channel_update with enable=false
|
// - if eclair was previously killed, it might not have had time to publish a channel_update with enable=false
|
||||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDeltaBlocks, normal.commitments.remoteParams.htlcMinimumMsat, normal.channelUpdate.feeBaseMsat, normal.channelUpdate.feeProportionalMillionths, enable = false)
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, normal.channelUpdate.shortChannelId, nodeParams.expiryDeltaBlocks,
|
||||||
|
normal.commitments.remoteParams.htlcMinimumMsat, normal.channelUpdate.feeBaseMsat, normal.channelUpdate.feeProportionalMillionths, normal.commitments.localCommit.spec.totalFunds, enable = false)
|
||||||
|
|
||||||
goto(OFFLINE) using normal.copy(channelUpdate = channelUpdate)
|
goto(OFFLINE) using normal.copy(channelUpdate = channelUpdate)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
|
@ -476,7 +478,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
// used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly)
|
// used to get the final shortChannelId, used in announcements (if minDepth >= ANNOUNCEMENTS_MINCONF this event will fire instantly)
|
||||||
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
blockchain ! WatchConfirmed(self, commitments.commitInput.outPoint.txid, commitments.commitInput.txOut.publicKeyScript, ANNOUNCEMENTS_MINCONF, BITCOIN_FUNDING_DEEPLYBURIED)
|
||||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId))
|
context.system.eventStream.publish(ShortChannelIdAssigned(self, commitments.channelId, shortChannelId))
|
||||||
val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, enable = true)
|
val initialChannelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, nodeParams.feeBaseMsat, nodeParams.feeProportionalMillionth, commitments.localCommit.spec.totalFunds, enable = true)
|
||||||
goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None))
|
goto(NORMAL) using store(DATA_NORMAL(commitments.copy(remoteNextCommitInfo = Right(nextPerCommitmentPoint)), shortChannelId, buried = false, None, initialChannelUpdate, None, None))
|
||||||
|
|
||||||
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel =>
|
case Event(remoteAnnSigs: AnnouncementSignatures, d: DATA_WAIT_FOR_FUNDING_LOCKED) if d.commitments.announceChannel =>
|
||||||
|
@ -764,7 +766,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
// we need to re-announce this shortChannelId
|
// we need to re-announce this shortChannelId
|
||||||
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId))
|
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId))
|
||||||
// we re-announce the channelUpdate for the same reason
|
// we re-announce the channelUpdate for the same reason
|
||||||
Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = true)
|
Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = true)
|
||||||
} else d.channelUpdate
|
} else d.channelUpdate
|
||||||
val localAnnSigs_opt = if (d.commitments.announceChannel) {
|
val localAnnSigs_opt = if (d.commitments.announceChannel) {
|
||||||
// if channel is public we need to send our announcement_signatures in order to generate the channel_announcement
|
// if channel is public we need to send our announcement_signatures in order to generate the channel_announcement
|
||||||
|
@ -807,13 +809,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
case Event(TickRefreshChannelUpdate, d: DATA_NORMAL) =>
|
case Event(TickRefreshChannelUpdate, d: DATA_NORMAL) =>
|
||||||
// periodic refresh is used as a keep alive
|
// periodic refresh is used as a keep alive
|
||||||
log.info(s"sending channel_update announcement (refresh)")
|
log.info(s"sending channel_update announcement (refresh)")
|
||||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = true)
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = true)
|
||||||
// we use GOTO instead of stay because we want to fire transitions
|
// we use GOTO instead of stay because we want to fire transitions
|
||||||
goto(NORMAL) using store(d.copy(channelUpdate = channelUpdate))
|
goto(NORMAL) using store(d.copy(channelUpdate = channelUpdate))
|
||||||
|
|
||||||
case Event(CMD_UPDATE_RELAY_FEE(feeBaseMsat, feeProportionalMillionths), d: DATA_NORMAL) =>
|
case Event(CMD_UPDATE_RELAY_FEE(feeBaseMsat, feeProportionalMillionths), d: DATA_NORMAL) =>
|
||||||
log.info(s"updating relay fees: prevFeeBaseMsat={} nextFeeBaseMsat={} prevFeeProportionalMillionths={} nextFeeProportionalMillionths={}", d.channelUpdate.feeBaseMsat, feeBaseMsat, d.channelUpdate.feeProportionalMillionths, feeProportionalMillionths)
|
log.info(s"updating relay fees: prevFeeBaseMsat={} nextFeeBaseMsat={} prevFeeProportionalMillionths={} nextFeeProportionalMillionths={}", d.channelUpdate.feeBaseMsat, feeBaseMsat, d.channelUpdate.feeProportionalMillionths, feeProportionalMillionths)
|
||||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, enable = true)
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = true)
|
||||||
// we use GOTO instead of stay because we want to fire transitions
|
// we use GOTO instead of stay because we want to fire transitions
|
||||||
goto(NORMAL) using store(d.copy(channelUpdate = channelUpdate)) replying "ok"
|
goto(NORMAL) using store(d.copy(channelUpdate = channelUpdate)) replying "ok"
|
||||||
|
|
||||||
|
@ -826,7 +828,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
case Event(INPUT_DISCONNECTED, d: DATA_NORMAL) =>
|
case Event(INPUT_DISCONNECTED, d: DATA_NORMAL) =>
|
||||||
// we disable the channel
|
// we disable the channel
|
||||||
log.debug(s"sending channel_update announcement (disable)")
|
log.debug(s"sending channel_update announcement (disable)")
|
||||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = false)
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = false)
|
||||||
d.commitments.localChanges.proposed.collect {
|
d.commitments.localChanges.proposed.collect {
|
||||||
case add: UpdateAddHtlc => relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, ChannelUnavailable(d.channelId), d.commitments.originChannels(add.id), Some(channelUpdate), None))
|
case add: UpdateAddHtlc => relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, ChannelUnavailable(d.channelId), d.commitments.originChannels(add.id), Some(channelUpdate), None))
|
||||||
}
|
}
|
||||||
|
@ -1137,9 +1139,15 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
log.info(s"processing BITCOIN_OUTPUT_SPENT with txid=${tx.txid} tx=$tx")
|
log.info(s"processing BITCOIN_OUTPUT_SPENT with txid=${tx.txid} tx=$tx")
|
||||||
val extracted = Closing.extractPreimages(d.commitments.localCommit, tx)
|
val extracted = Closing.extractPreimages(d.commitments.localCommit, tx)
|
||||||
extracted map { case (htlc, fulfill) =>
|
extracted map { case (htlc, fulfill) =>
|
||||||
val origin = d.commitments.originChannels(fulfill.id)
|
d.commitments.originChannels.get(fulfill.id) match {
|
||||||
log.warning(s"fulfilling htlc #${fulfill.id} paymentHash=${sha256(fulfill.paymentPreimage)} origin=$origin")
|
case Some(origin) =>
|
||||||
relayer ! ForwardFulfill(fulfill, origin, htlc)
|
log.info(s"fulfilling htlc #${fulfill.id} paymentHash=${sha256(fulfill.paymentPreimage)} origin=$origin")
|
||||||
|
relayer ! ForwardFulfill(fulfill, origin, htlc)
|
||||||
|
case None =>
|
||||||
|
// if we don't have the origin, it means that we already have forwarded the fulfill so that's not a big deal.
|
||||||
|
// this can happen if they send a signature containing the fulfill, then fail the channel before we have time to sign it
|
||||||
|
log.info(s"cannot fulfill htlc #${fulfill.id} paymentHash=${sha256(fulfill.paymentPreimage)} (origin not found)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val revokedCommitPublished1 = d.revokedCommitPublished.map { rev =>
|
val revokedCommitPublished1 = d.revokedCommitPublished.map { rev =>
|
||||||
val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx)
|
val (rev1, tx_opt) = Closing.claimRevokedHtlcTxOutputs(keyManager, d.commitments, rev, tx)
|
||||||
|
@ -1167,16 +1175,26 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
Closing.timedoutHtlcs(d.commitments.remoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx) ++
|
Closing.timedoutHtlcs(d.commitments.remoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx) ++
|
||||||
d.commitments.remoteNextCommitInfo.left.toSeq.flatMap(r => Closing.timedoutHtlcs(r.nextRemoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx))
|
d.commitments.remoteNextCommitInfo.left.toSeq.flatMap(r => Closing.timedoutHtlcs(r.nextRemoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx))
|
||||||
timedoutHtlcs.foreach { add =>
|
timedoutHtlcs.foreach { add =>
|
||||||
val origin = d.commitments.originChannels(add.id)
|
d.commitments.originChannels.get(add.id) match {
|
||||||
log.warning(s"failing htlc #${add.id} paymentHash=${add.paymentHash} origin=$origin: htlc timed out")
|
case Some(origin) =>
|
||||||
relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, HtlcTimedout(d.channelId), origin, None, None))
|
log.info(s"failing htlc #${add.id} paymentHash=${add.paymentHash} origin=$origin: htlc timed out")
|
||||||
|
relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, HtlcTimedout(d.channelId), origin, None, None))
|
||||||
|
case None =>
|
||||||
|
// same as for fulfilling the htlc (no big deal)
|
||||||
|
log.info(s"cannot fail timedout htlc #${add.id} paymentHash=${add.paymentHash} (origin not found)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// we also need to fail outgoing htlcs that we know will never reach the blockchain
|
// we also need to fail outgoing htlcs that we know will never reach the blockchain
|
||||||
val overridenHtlcs = Closing.overriddenHtlcs(d.commitments.localCommit, d.commitments.remoteCommit, d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit), tx)
|
val overridenHtlcs = Closing.overriddenHtlcs(d.commitments.localCommit, d.commitments.remoteCommit, d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit), tx)
|
||||||
overridenHtlcs.foreach { add =>
|
overridenHtlcs.foreach { add =>
|
||||||
val origin = d.commitments.originChannels(add.id)
|
d.commitments.originChannels.get(add.id) match {
|
||||||
log.warning(s"failing htlc #${add.id} paymentHash=${add.paymentHash} origin=$origin: overriden by local commit")
|
case Some(origin) =>
|
||||||
relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, HtlcOverridenByLocalCommit(d.channelId), origin, None, None))
|
log.info(s"failing htlc #${add.id} paymentHash=${add.paymentHash} origin=$origin: overriden by local commit")
|
||||||
|
relayer ! Status.Failure(AddHtlcFailed(d.channelId, add.paymentHash, HtlcOverridenByLocalCommit(d.channelId), origin, None, None))
|
||||||
|
case None =>
|
||||||
|
// same as for fulfilling the htlc (no big deal)
|
||||||
|
log.info(s"cannot fail overriden htlc #${add.id} paymentHash=${add.paymentHash} (origin not found)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// then let's see if any of the possible close scenarii can be considered done
|
// then let's see if any of the possible close scenarii can be considered done
|
||||||
val mutualCloseDone = d.mutualClosePublished.exists(_.txid == tx.txid) // this case is trivial, in a mutual close scenario we only need to make sure that one of the closing txes is confirmed
|
val mutualCloseDone = d.mutualClosePublished.exists(_.txid == tx.txid) // this case is trivial, in a mutual close scenario we only need to make sure that one of the closing txes is confirmed
|
||||||
|
@ -1276,6 +1294,12 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
// -> in CLOSING we either have mutual closed (so no more htlcs), or already have unilaterally closed (so no action required), and we can't be in OFFLINE state anyway
|
// -> in CLOSING we either have mutual closed (so no more htlcs), or already have unilaterally closed (so no action required), and we can't be in OFFLINE state anyway
|
||||||
handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
handleLocalError(HtlcTimedout(d.channelId), d, Some(c))
|
||||||
|
|
||||||
|
case Event(CMD_UPDATE_RELAY_FEE(feeBaseMsat, feeProportionalMillionths), d: DATA_NORMAL) =>
|
||||||
|
log.info(s"updating relay fees: prevFeeBaseMsat={} nextFeeBaseMsat={} prevFeeProportionalMillionths={} nextFeeProportionalMillionths={}", d.channelUpdate.feeBaseMsat, feeBaseMsat, d.channelUpdate.feeProportionalMillionths, feeProportionalMillionths)
|
||||||
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = false)
|
||||||
|
// we're in OFFLINE state, we don't broadcast the new update right away, we will do that when next time we go to NORMAL state
|
||||||
|
stay using store(d.copy(channelUpdate = channelUpdate)) replying "ok"
|
||||||
|
|
||||||
// just ignore this, we will put a new watch when we reconnect, and we'll be notified again
|
// just ignore this, we will put a new watch when we reconnect, and we'll be notified again
|
||||||
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK | BITCOIN_FUNDING_DEEPLYBURIED, _, _), _) => stay
|
case Event(WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK | BITCOIN_FUNDING_DEEPLYBURIED, _, _), _) => stay
|
||||||
|
|
||||||
|
@ -1367,7 +1391,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// re-enable the channel
|
// re-enable the channel
|
||||||
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, enable = true)
|
val channelUpdate = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, nodeParams.expiryDeltaBlocks, d.commitments.remoteParams.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.localCommit.spec.totalFunds, enable = true)
|
||||||
|
|
||||||
goto(NORMAL) using d.copy(commitments = commitments1, channelUpdate = channelUpdate)
|
goto(NORMAL) using d.copy(commitments = commitments1, channelUpdate = channelUpdate)
|
||||||
}
|
}
|
||||||
|
@ -1449,14 +1473,16 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
||||||
case _ => handleCommandError(AddHtlcFailed(d.channelId, c.paymentHash, error, origin(c), None, Some(c)), c) // we don't provide a channel_update: this will be a permanent channel failure
|
case _ => handleCommandError(AddHtlcFailed(d.channelId, c.paymentHash, error, origin(c), None, Some(c)), c) // we don't provide a channel_update: this will be a permanent channel failure
|
||||||
}
|
}
|
||||||
|
|
||||||
case Event(c: CMD_CLOSE, d) => handleCommandError(CannotCloseInThisState(Helpers.getChannelId(d), stateName), c)
|
case Event(c: CMD_CLOSE, d) => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "close", stateName), c)
|
||||||
|
|
||||||
case Event(c@CMD_FORCECLOSE, d) =>
|
case Event(c@CMD_FORCECLOSE, d) =>
|
||||||
d match {
|
d match {
|
||||||
case data: HasCommitments => handleLocalError(ForcedLocalCommit(data.channelId), data, Some(c)) replying "ok"
|
case data: HasCommitments => handleLocalError(ForcedLocalCommit(data.channelId), data, Some(c)) replying "ok"
|
||||||
case _ => handleCommandError(CannotCloseInThisState(Helpers.getChannelId(d), stateName), c)
|
case _ => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "forceclose", stateName), c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Event(c: CMD_UPDATE_RELAY_FEE, d) => handleCommandError(CommandUnavailableInThisState(Helpers.getChannelId(d), "updaterelayfee", stateName), c)
|
||||||
|
|
||||||
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
|
// we only care about this event in NORMAL and SHUTDOWN state, and we never unregister to the event stream
|
||||||
case Event(CurrentBlockCount(_), _) => stay
|
case Event(CurrentBlockCount(_), _) => stay
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,6 @@ case class ChannelReserveNotMet (override val channelId: BinaryDa
|
||||||
case class ChannelFundingError (override val channelId: BinaryData) extends ChannelException(channelId, "channel funding error")
|
case class ChannelFundingError (override val channelId: BinaryData) extends ChannelException(channelId, "channel funding error")
|
||||||
case class NoMoreHtlcsClosingInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "cannot send new htlcs, closing in progress")
|
case class NoMoreHtlcsClosingInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "cannot send new htlcs, closing in progress")
|
||||||
case class ClosingAlreadyInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "closing already in progress")
|
case class ClosingAlreadyInProgress (override val channelId: BinaryData) extends ChannelException(channelId, "closing already in progress")
|
||||||
case class CannotCloseInThisState (override val channelId: BinaryData, state: State) extends ChannelException(channelId, s"cannot close in state=$state")
|
|
||||||
case class CannotCloseWithUnsignedOutgoingHtlcs(override val channelId: BinaryData) extends ChannelException(channelId, "cannot close when there are unsigned outgoing htlcs")
|
case class CannotCloseWithUnsignedOutgoingHtlcs(override val channelId: BinaryData) extends ChannelException(channelId, "cannot close when there are unsigned outgoing htlcs")
|
||||||
case class ChannelUnavailable (override val channelId: BinaryData) extends ChannelException(channelId, "channel is unavailable (offline or closing)")
|
case class ChannelUnavailable (override val channelId: BinaryData) extends ChannelException(channelId, "channel is unavailable (offline or closing)")
|
||||||
case class InvalidFinalScript (override val channelId: BinaryData) extends ChannelException(channelId, "invalid final script")
|
case class InvalidFinalScript (override val channelId: BinaryData) extends ChannelException(channelId, "invalid final script")
|
||||||
|
@ -82,4 +81,5 @@ case class RevocationSyncError (override val channelId: BinaryDa
|
||||||
case class InvalidFailureCode (override val channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
|
case class InvalidFailureCode (override val channelId: BinaryData) extends ChannelException(channelId, "UpdateFailMalformedHtlc message doesn't have BADONION bit set")
|
||||||
case class PleasePublishYourCommitment (override val channelId: BinaryData) extends ChannelException(channelId, "please publish your local commitment")
|
case class PleasePublishYourCommitment (override val channelId: BinaryData) extends ChannelException(channelId, "please publish your local commitment")
|
||||||
case class AddHtlcFailed (override val channelId: BinaryData, paymentHash: BinaryData, t: Throwable, origin: Origin, channelUpdate: Option[ChannelUpdate], originalCommand: Option[CMD_ADD_HTLC]) extends ChannelException(channelId, s"cannot add htlc with origin=$origin reason=${t.getMessage}")
|
case class AddHtlcFailed (override val channelId: BinaryData, paymentHash: BinaryData, t: Throwable, origin: Origin, channelUpdate: Option[ChannelUpdate], originalCommand: Option[CMD_ADD_HTLC]) extends ChannelException(channelId, s"cannot add htlc with origin=$origin reason=${t.getMessage}")
|
||||||
|
case class CommandUnavailableInThisState (override val channelId: BinaryData, command: String, state: State) extends ChannelException(channelId, s"cannot execute command=$command in state=$state")
|
||||||
// @formatter:on
|
// @formatter:on
|
|
@ -18,7 +18,7 @@ package fr.acinq.eclair.channel
|
||||||
|
|
||||||
import akka.event.LoggingAdapter
|
import akka.event.LoggingAdapter
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, MilliSatoshi, Satoshi, Transaction}
|
||||||
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
|
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
|
||||||
import fr.acinq.eclair.payment.Origin
|
import fr.acinq.eclair.payment.Origin
|
||||||
import fr.acinq.eclair.transactions.Transactions._
|
import fr.acinq.eclair.transactions.Transactions._
|
||||||
|
@ -290,7 +290,8 @@ object Commitments {
|
||||||
}
|
}
|
||||||
// let's compute the current commitment *as seen by them* with this change taken into account
|
// let's compute the current commitment *as seen by them* with this change taken into account
|
||||||
val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw)
|
val fee = UpdateFee(commitments.channelId, cmd.feeratePerKw)
|
||||||
val commitments1 = addLocalProposal(commitments, fee)
|
// update_fee replace each other, so we can remove previous ones
|
||||||
|
val commitments1 = commitments.copy(localChanges = commitments.localChanges.copy(proposed = commitments.localChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee))
|
||||||
val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed)
|
val reduced = CommitmentSpec.reduce(commitments1.remoteCommit.spec, commitments1.remoteChanges.acked, commitments1.localChanges.proposed)
|
||||||
|
|
||||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||||
|
@ -324,7 +325,8 @@ object Commitments {
|
||||||
// (it also means that we need to check the fee of the initial commitment tx somewhere)
|
// (it also means that we need to check the fee of the initial commitment tx somewhere)
|
||||||
|
|
||||||
// let's compute the current commitment *as seen by us* including this change
|
// let's compute the current commitment *as seen by us* including this change
|
||||||
val commitments1 = addRemoteProposal(commitments, fee)
|
// update_fee replace each other, so we can remove previous ones
|
||||||
|
val commitments1 = commitments.copy(remoteChanges = commitments.remoteChanges.copy(proposed = commitments.remoteChanges.proposed.filterNot(_.isInstanceOf[UpdateFee]) :+ fee))
|
||||||
val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed)
|
val reduced = CommitmentSpec.reduce(commitments1.localCommit.spec, commitments1.localChanges.acked, commitments1.remoteChanges.proposed)
|
||||||
|
|
||||||
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
// a node cannot spend pending incoming htlcs, and need to keep funds above the reserve required by the counterparty, after paying the fee
|
||||||
|
|
|
@ -558,7 +558,7 @@ object Helpers {
|
||||||
})
|
})
|
||||||
|
|
||||||
// we retrieve the informations needed to rebuild htlc scripts
|
// we retrieve the informations needed to rebuild htlc scripts
|
||||||
val htlcInfos = db.listHtlcHtlcInfos(commitments.channelId, txnumber)
|
val htlcInfos = db.listHtlcInfos(commitments.channelId, txnumber)
|
||||||
log.info(s"got htlcs=${htlcInfos.size} for txnumber=$txnumber")
|
log.info(s"got htlcs=${htlcInfos.size} for txnumber=$txnumber")
|
||||||
val htlcsRedeemScripts = (
|
val htlcsRedeemScripts = (
|
||||||
htlcInfos.map { case (paymentHash, cltvExpiry) => Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry) } ++
|
htlcInfos.map { case (paymentHash, cltvExpiry) => Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry) } ++
|
||||||
|
|
|
@ -29,6 +29,6 @@ trait ChannelsDb {
|
||||||
|
|
||||||
def addOrUpdateHtlcInfo(channelId: BinaryData, commitmentNumber: Long, paymentHash: BinaryData, cltvExpiry: Long)
|
def addOrUpdateHtlcInfo(channelId: BinaryData, commitmentNumber: Long, paymentHash: BinaryData, cltvExpiry: Long)
|
||||||
|
|
||||||
def listHtlcHtlcInfos(channelId: BinaryData, commitmentNumber: Long): Seq[(BinaryData, Long)]
|
def listHtlcInfos(channelId: BinaryData, commitmentNumber: Long): Seq[(BinaryData, Long)]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ class SqliteChannelsDb(sqlite: Connection) extends ChannelsDb {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def listHtlcHtlcInfos(channelId: BinaryData, commitmentNumber: Long): Seq[(BinaryData, Long)] = {
|
def listHtlcInfos(channelId: BinaryData, commitmentNumber: Long): Seq[(BinaryData, Long)] = {
|
||||||
using(sqlite.prepareStatement("SELECT payment_hash, cltv_expiry FROM htlc_infos WHERE channel_id=? AND commitment_number=?")) { statement =>
|
using(sqlite.prepareStatement("SELECT payment_hash, cltv_expiry FROM htlc_infos WHERE channel_id=? AND commitment_number=?")) { statement =>
|
||||||
statement.setBytes(1, channelId)
|
statement.setBytes(1, channelId)
|
||||||
statement.setLong(2, commitmentNumber)
|
statement.setLong(2, commitmentNumber)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import java.sql.Connection
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi}
|
||||||
import fr.acinq.eclair.ShortChannelId
|
import fr.acinq.eclair.ShortChannelId
|
||||||
import fr.acinq.eclair.db.{NetworkDb, Payment}
|
import fr.acinq.eclair.db.NetworkDb
|
||||||
import fr.acinq.eclair.router.Announcements
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.wire.LightningMessageCodecs.nodeAnnouncementCodec
|
import fr.acinq.eclair.wire.LightningMessageCodecs.nodeAnnouncementCodec
|
||||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement}
|
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement}
|
||||||
|
@ -40,7 +40,7 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {
|
||||||
statement.execute("PRAGMA foreign_keys = ON")
|
statement.execute("PRAGMA foreign_keys = ON")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS nodes (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS nodes (node_id BLOB NOT NULL PRIMARY KEY, data BLOB NOT NULL)")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channels (short_channel_id INTEGER NOT NULL PRIMARY KEY, node_id_1 BLOB NOT NULL, node_id_2 BLOB NOT NULL)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channels (short_channel_id INTEGER NOT NULL PRIMARY KEY, node_id_1 BLOB NOT NULL, node_id_2 BLOB NOT NULL)")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_updates (short_channel_id INTEGER NOT NULL, node_flag INTEGER NOT NULL, timestamp INTEGER NOT NULL, flags BLOB NOT NULL, cltv_expiry_delta INTEGER NOT NULL, htlc_minimum_msat INTEGER NOT NULL, fee_base_msat INTEGER NOT NULL, fee_proportional_millionths INTEGER NOT NULL, PRIMARY KEY(short_channel_id, node_flag), FOREIGN KEY(short_channel_id) REFERENCES channels(short_channel_id))")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS channel_updates (short_channel_id INTEGER NOT NULL, node_flag INTEGER NOT NULL, timestamp INTEGER NOT NULL, flags BLOB NOT NULL, cltv_expiry_delta INTEGER NOT NULL, htlc_minimum_msat INTEGER NOT NULL, fee_base_msat INTEGER NOT NULL, fee_proportional_millionths INTEGER NOT NULL, htlc_maximum_msat INTEGER, PRIMARY KEY(short_channel_id, node_flag), FOREIGN KEY(short_channel_id) REFERENCES channels(short_channel_id))")
|
||||||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS channel_updates_idx ON channel_updates(short_channel_id)")
|
statement.executeUpdate("CREATE INDEX IF NOT EXISTS channel_updates_idx ON channel_updates(short_channel_id)")
|
||||||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS pruned (short_channel_id INTEGER NOT NULL PRIMARY KEY)")
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS pruned (short_channel_id INTEGER NOT NULL PRIMARY KEY)")
|
||||||
}
|
}
|
||||||
|
@ -118,29 +118,31 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
override def addChannelUpdate(u: ChannelUpdate): Unit = {
|
override def addChannelUpdate(u: ChannelUpdate): Unit = {
|
||||||
using(sqlite.prepareStatement("INSERT OR IGNORE INTO channel_updates VALUES (?, ?, ?, ?, ?, ?, ?, ?)")) { statement =>
|
using(sqlite.prepareStatement("INSERT OR IGNORE INTO channel_updates VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")) { statement =>
|
||||||
statement.setLong(1, u.shortChannelId.toLong)
|
statement.setLong(1, u.shortChannelId.toLong)
|
||||||
statement.setBoolean(2, Announcements.isNode1(u.flags))
|
statement.setBoolean(2, Announcements.isNode1(u.channelFlags))
|
||||||
statement.setLong(3, u.timestamp)
|
statement.setLong(3, u.timestamp)
|
||||||
statement.setBytes(4, u.flags)
|
statement.setBytes(4, Array(u.messageFlags, u.channelFlags))
|
||||||
statement.setInt(5, u.cltvExpiryDelta)
|
statement.setInt(5, u.cltvExpiryDelta)
|
||||||
statement.setLong(6, u.htlcMinimumMsat)
|
statement.setLong(6, u.htlcMinimumMsat)
|
||||||
statement.setLong(7, u.feeBaseMsat)
|
statement.setLong(7, u.feeBaseMsat)
|
||||||
statement.setLong(8, u.feeProportionalMillionths)
|
statement.setLong(8, u.feeProportionalMillionths)
|
||||||
|
setNullableLong(statement, 9, u.htlcMaximumMsat)
|
||||||
statement.executeUpdate()
|
statement.executeUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def updateChannelUpdate(u: ChannelUpdate): Unit = {
|
override def updateChannelUpdate(u: ChannelUpdate): Unit = {
|
||||||
using(sqlite.prepareStatement("UPDATE channel_updates SET timestamp=?, flags=?, cltv_expiry_delta=?, htlc_minimum_msat=?, fee_base_msat=?, fee_proportional_millionths=? WHERE short_channel_id=? AND node_flag=?")) { statement =>
|
using(sqlite.prepareStatement("UPDATE channel_updates SET timestamp=?, flags=?, cltv_expiry_delta=?, htlc_minimum_msat=?, fee_base_msat=?, fee_proportional_millionths=?, htlc_maximum_msat=? WHERE short_channel_id=? AND node_flag=?")) { statement =>
|
||||||
statement.setLong(1, u.timestamp)
|
statement.setLong(1, u.timestamp)
|
||||||
statement.setBytes(2, u.flags)
|
statement.setBytes(2, Array(u.messageFlags, u.channelFlags))
|
||||||
statement.setInt(3, u.cltvExpiryDelta)
|
statement.setInt(3, u.cltvExpiryDelta)
|
||||||
statement.setLong(4, u.htlcMinimumMsat)
|
statement.setLong(4, u.htlcMinimumMsat)
|
||||||
statement.setLong(5, u.feeBaseMsat)
|
statement.setLong(5, u.feeBaseMsat)
|
||||||
statement.setLong(6, u.feeProportionalMillionths)
|
statement.setLong(6, u.feeProportionalMillionths)
|
||||||
statement.setLong(7, u.shortChannelId.toLong)
|
setNullableLong(statement, 7, u.htlcMaximumMsat)
|
||||||
statement.setBoolean(8, Announcements.isNode1(u.flags))
|
statement.setLong(8, u.shortChannelId.toLong)
|
||||||
|
statement.setBoolean(9, Announcements.isNode1(u.channelFlags))
|
||||||
statement.executeUpdate()
|
statement.executeUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,11 +157,13 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {
|
||||||
chainHash = null,
|
chainHash = null,
|
||||||
shortChannelId = ShortChannelId(rs.getLong("short_channel_id")),
|
shortChannelId = ShortChannelId(rs.getLong("short_channel_id")),
|
||||||
timestamp = rs.getLong("timestamp"),
|
timestamp = rs.getLong("timestamp"),
|
||||||
flags = rs.getBytes("flags"),
|
messageFlags = rs.getBytes("flags")(0),
|
||||||
|
channelFlags = rs.getBytes("flags")(1),
|
||||||
cltvExpiryDelta = rs.getInt("cltv_expiry_delta"),
|
cltvExpiryDelta = rs.getInt("cltv_expiry_delta"),
|
||||||
htlcMinimumMsat = rs.getLong("htlc_minimum_msat"),
|
htlcMinimumMsat = rs.getLong("htlc_minimum_msat"),
|
||||||
feeBaseMsat = rs.getLong("fee_base_msat"),
|
feeBaseMsat = rs.getLong("fee_base_msat"),
|
||||||
feeProportionalMillionths = rs.getLong("fee_proportional_millionths"))
|
feeProportionalMillionths = rs.getLong("fee_proportional_millionths"),
|
||||||
|
htlcMaximumMsat = getNullableLong(rs, "htlc_maximum_msat"))
|
||||||
}
|
}
|
||||||
q
|
q
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package fr.acinq.eclair.db.sqlite
|
package fr.acinq.eclair.db.sqlite
|
||||||
|
|
||||||
import java.sql.{ResultSet, Statement}
|
import java.sql.{PreparedStatement, ResultSet, Statement}
|
||||||
|
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
@ -74,4 +74,30 @@ object SqliteUtils {
|
||||||
}
|
}
|
||||||
q
|
q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper uses the proper way to set a nullable value.
|
||||||
|
*
|
||||||
|
* @param statement
|
||||||
|
* @param parameterIndex
|
||||||
|
* @param value_opt
|
||||||
|
*/
|
||||||
|
def setNullableLong(statement: PreparedStatement, parameterIndex: Int, value_opt: Option[Long]) = {
|
||||||
|
value_opt match {
|
||||||
|
case Some(value) => statement.setLong(parameterIndex, value)
|
||||||
|
case None => statement.setNull(parameterIndex, java.sql.Types.INTEGER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper retrieves the value from a nullable integer column and interprets it as an option. This is needed
|
||||||
|
* because `rs.getLong` would return `0` for a null value.
|
||||||
|
*
|
||||||
|
* @param label
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
def getNullableLong(rs: ResultSet, label: String) : Option[Long] = {
|
||||||
|
val result = rs.getLong(label)
|
||||||
|
if (rs.wasNull()) None else Some(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import fr.acinq.eclair.crypto.TransportHandler
|
||||||
import fr.acinq.eclair.router._
|
import fr.acinq.eclair.router._
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{wire, _}
|
import fr.acinq.eclair.{wire, _}
|
||||||
|
import scodec.Attempt
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
@ -264,34 +265,39 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor
|
||||||
stay
|
stay
|
||||||
|
|
||||||
case Event(rebroadcast: Rebroadcast, ConnectedData(_, transport, _, _, maybeGossipTimestampFilter, _, _)) =>
|
case Event(rebroadcast: Rebroadcast, ConnectedData(_, transport, _, _, maybeGossipTimestampFilter, _, _)) =>
|
||||||
val (channels1, updates1, nodes1) = Peer.filterGossipMessages(rebroadcast, self, maybeGossipTimestampFilter)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send and count in a single iteration
|
* Send and count in a single iteration
|
||||||
*/
|
*/
|
||||||
def sendAndCount(msgs: Iterable[RoutingMessage]): Int = msgs.foldLeft(0) {
|
def sendAndCount(msgs: Map[_ <: RoutingMessage, Set[ActorRef]]): Int = msgs.foldLeft(0) {
|
||||||
case (count, msg) =>
|
case (count, (_, origins)) if origins.contains(self) =>
|
||||||
|
// the announcement came from this peer, we don't send it back
|
||||||
|
count
|
||||||
|
case (count, (msg: HasTimestamp, _)) if !timestampInRange(msg, maybeGossipTimestampFilter) =>
|
||||||
|
// the peer has set up a filter on timestamp and this message is out of range
|
||||||
|
count
|
||||||
|
case (count, (msg, _)) =>
|
||||||
transport ! msg
|
transport ! msg
|
||||||
count + 1
|
count + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelsSent = sendAndCount(channels1)
|
val channelsSent = sendAndCount(rebroadcast.channels)
|
||||||
val updatesSent = sendAndCount(updates1)
|
val updatesSent = sendAndCount(rebroadcast.updates)
|
||||||
val nodesSent = sendAndCount(nodes1)
|
val nodesSent = sendAndCount(rebroadcast.nodes)
|
||||||
|
|
||||||
if (channelsSent > 0 || updatesSent > 0 || nodesSent > 0) {
|
if (channelsSent > 0 || updatesSent > 0 || nodesSent > 0) {
|
||||||
log.debug(s"sent announcements to {}: channels={} updates={} nodes={}", remoteNodeId, channelsSent, updatesSent, nodesSent)
|
log.info(s"sent announcements to {}: channels={} updates={} nodes={}", remoteNodeId, channelsSent, updatesSent, nodesSent)
|
||||||
}
|
}
|
||||||
stay
|
stay
|
||||||
|
|
||||||
case Event(msg: GossipTimestampFilter, data: ConnectedData) =>
|
case Event(msg: GossipTimestampFilter, data: ConnectedData) =>
|
||||||
// special case: time range filters are peer specific and must not be sent to
|
// special case: time range filters are peer specific and must not be sent to the router
|
||||||
// the router
|
|
||||||
sender ! TransportHandler.ReadAck(msg)
|
sender ! TransportHandler.ReadAck(msg)
|
||||||
if (msg.chainHash != nodeParams.chainHash) {
|
if (msg.chainHash != nodeParams.chainHash) {
|
||||||
log.warning("received gossip_timestamp_range message for chain {}, we're on {}", msg.chainHash, nodeParams.chainHash)
|
log.warning("received gossip_timestamp_range message for chain {}, we're on {}", msg.chainHash, nodeParams.chainHash)
|
||||||
stay
|
stay
|
||||||
} else {
|
} else {
|
||||||
|
log.info(s"setting up gossipTimestampFilter=$msg")
|
||||||
// update their timestamp filter
|
// update their timestamp filter
|
||||||
stay using data.copy(gossipTimestampFilter = Some(msg))
|
stay using data.copy(gossipTimestampFilter = Some(msg))
|
||||||
}
|
}
|
||||||
|
@ -315,7 +321,10 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor
|
||||||
case Event(badMessage: BadMessage, data@ConnectedData(_, transport, _, _, _, _, behavior)) =>
|
case Event(badMessage: BadMessage, data@ConnectedData(_, transport, _, _, _, _, behavior)) =>
|
||||||
val behavior1 = badMessage match {
|
val behavior1 = badMessage match {
|
||||||
case InvalidSignature(r) =>
|
case InvalidSignature(r) =>
|
||||||
val bin = LightningMessageCodecs.lightningMessageCodec.encode(r)
|
val bin: String = LightningMessageCodecs.lightningMessageCodec.encode(r) match {
|
||||||
|
case Attempt.Successful(b) => b.toHex
|
||||||
|
case _ => "unknown"
|
||||||
|
}
|
||||||
log.error(s"peer sent us a routing message with invalid sig: r=$r bin=$bin")
|
log.error(s"peer sent us a routing message with invalid sig: r=$r bin=$bin")
|
||||||
// for now we just return an error, maybe ban the peer in the future?
|
// for now we just return an error, maybe ban the peer in the future?
|
||||||
transport ! Error(CHANNELID_ZERO, s"bad announcement sig! bin=$bin".getBytes())
|
transport ! Error(CHANNELID_ZERO, s"bad announcement sig! bin=$bin".getBytes())
|
||||||
|
@ -511,42 +520,18 @@ object Peer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* filter out gossip messages using the provided origin and optional timestamp range
|
* Peer may want to filter announcements based on timestamp
|
||||||
*
|
*
|
||||||
* @param rebroadcast rebroadcast message
|
* @param gossipTimestampFilter_opt optional gossip timestamp range
|
||||||
* @param self messages which have been sent by `self` will be filtered out
|
* @return
|
||||||
* @param gossipTimestampFilter optional gossip timestamp range
|
* - true if the msg's timestamp is in the requested range, or if there is no filtering
|
||||||
* @return a filtered (channel announcements, channel updates, node announcements) tuple
|
* - false otherwise
|
||||||
*/
|
*/
|
||||||
def filterGossipMessages(rebroadcast: Rebroadcast, self: ActorRef, gossipTimestampFilter: Option[GossipTimestampFilter]): (Iterable[ChannelAnnouncement], Iterable[ChannelUpdate], Iterable[NodeAnnouncement]) = {
|
def timestampInRange(msg: HasTimestamp, gossipTimestampFilter_opt: Option[GossipTimestampFilter]): Boolean = {
|
||||||
|
|
||||||
// check if this message has a timestamp that matches our timestamp filter
|
// check if this message has a timestamp that matches our timestamp filter
|
||||||
def checkTimestamp(routingMessage: RoutingMessage): Boolean = gossipTimestampFilter match {
|
gossipTimestampFilter_opt match {
|
||||||
case None => true // no filtering
|
case None => true // no filtering
|
||||||
case Some(GossipTimestampFilter(_, firstTimestamp, timestampRange)) => routingMessage match {
|
case Some(GossipTimestampFilter(_, firstTimestamp, timestampRange)) => msg.timestamp >= firstTimestamp && msg.timestamp <= firstTimestamp + timestampRange
|
||||||
case hts: HasTimestamp => hts.timestamp >= firstTimestamp && hts.timestamp <= firstTimestamp + timestampRange
|
|
||||||
case _ => true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we filter out updates against their timestamp filter, and build a list of all channel ids for which we have an update
|
|
||||||
val (updates1, shortChannelIds) = rebroadcast.updates.foldLeft((Seq.empty[ChannelUpdate], Set.empty[ShortChannelId])) {
|
|
||||||
case ((channelUpdates, shortChannelIds), (a, origins)) if !origins.contains(self) && checkTimestamp(a) => (a +: channelUpdates, shortChannelIds + a.shortChannelId)
|
|
||||||
case ((channelUpdates, shortChannelIds), (a, origins)) => (channelUpdates, shortChannelIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we filter out channels for which we don't have an update
|
|
||||||
val channels1 = rebroadcast.channels.foldLeft((Seq.empty[ChannelAnnouncement])) {
|
|
||||||
case (channelAnnouncements, (a, origins)) if !origins.contains(self) && shortChannelIds.contains(a.shortChannelId) => a +: channelAnnouncements
|
|
||||||
case (channelAnnouncements, (a, _)) => channelAnnouncements
|
|
||||||
}
|
|
||||||
|
|
||||||
// we filter out nodes against their timestamp filter
|
|
||||||
// TODO: we do * not * filter out nodes for which matching channel announcements were pruned above.
|
|
||||||
// Our rebroadcast message may sometimes include "orphan" nodes without matching channel announcements, because of
|
|
||||||
// the way announcements are handled in the router
|
|
||||||
val nodes1 = rebroadcast.nodes.collect { case (a, origins) if !origins.contains(self) && checkTimestamp(a) => a }
|
|
||||||
|
|
||||||
(channels1, updates1, nodes1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ class PaymentLifecycle(sourceNodeId: PublicKey, router: ActorRef, register: Acto
|
||||||
case Event(RouteResponse(hops, ignoreNodes, ignoreChannels), WaitingForRoute(s, c, failures)) =>
|
case Event(RouteResponse(hops, ignoreNodes, ignoreChannels), WaitingForRoute(s, c, failures)) =>
|
||||||
log.info(s"route found: attempt=${failures.size + 1}/${c.maxAttempts} route=${hops.map(_.nextNodeId).mkString("->")} channels=${hops.map(_.lastUpdate.shortChannelId).mkString("->")}")
|
log.info(s"route found: attempt=${failures.size + 1}/${c.maxAttempts} route=${hops.map(_.nextNodeId).mkString("->")} channels=${hops.map(_.lastUpdate.shortChannelId).mkString("->")}")
|
||||||
val firstHop = hops.head
|
val firstHop = hops.head
|
||||||
val finalExpiry = Globals.blockCount.get().toInt + c.finalCltvExpiry.toInt
|
// we add one block in order to not have our htlc fail when a new block has just been found
|
||||||
|
val finalExpiry = Globals.blockCount.get().toInt + c.finalCltvExpiry.toInt + 1
|
||||||
|
|
||||||
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, finalExpiry, c.paymentHash, hops)
|
val (cmd, sharedSecrets) = buildCommand(c.amountMsat, finalExpiry, c.paymentHash, hops)
|
||||||
val feePct = (cmd.amountMsat - c.amountMsat) / c.amountMsat.toDouble // c.amountMsat is required to be > 0, have to convert to double, otherwise will be rounded
|
val feePct = (cmd.amountMsat - c.amountMsat) / c.amountMsat.toDouble // c.amountMsat is required to be > 0, have to convert to double, otherwise will be rounded
|
||||||
|
@ -197,10 +198,9 @@ object PaymentLifecycle {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String, expirySeconds_opt: Option[Long] = None, extraHops: Seq[Seq[ExtraHop]] = Nil)
|
case class ReceivePayment(amountMsat_opt: Option[MilliSatoshi], description: String, expirySeconds_opt: Option[Long] = None, extraHops: Seq[Seq[ExtraHop]] = Nil)
|
||||||
/**
|
/**
|
||||||
* @param finalCltvExpiry by default we choose finalCltvExpiry = Channel.MIN_CLTV_EXPIRY + 1 to not have our htlc fail when a new block has just been found
|
|
||||||
* @param maxFeePct set by default to 3% as a safety measure (even if a route is found, if fee is higher than that payment won't be attempted)
|
* @param maxFeePct set by default to 3% as a safety measure (even if a route is found, if fee is higher than that payment won't be attempted)
|
||||||
*/
|
*/
|
||||||
case class SendPayment(amountMsat: Long, paymentHash: BinaryData, targetNodeId: PublicKey, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY + 1, maxAttempts: Int = 5, maxFeePct: Double = 0.03) {
|
case class SendPayment(amountMsat: Long, paymentHash: BinaryData, targetNodeId: PublicKey, assistedRoutes: Seq[Seq[ExtraHop]] = Nil, finalCltvExpiry: Long = Channel.MIN_CLTV_EXPIRY, maxAttempts: Int = 5, maxFeePct: Double = 0.03) {
|
||||||
require(amountMsat > 0, s"amountMsat must be > 0")
|
require(amountMsat > 0, s"amountMsat must be > 0")
|
||||||
}
|
}
|
||||||
case class CheckPayment(paymentHash: BinaryData)
|
case class CheckPayment(paymentHash: BinaryData)
|
||||||
|
|
|
@ -137,7 +137,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
|
||||||
case (_: ExpiryTooBig, _) => ExpiryTooFar
|
case (_: ExpiryTooBig, _) => ExpiryTooFar
|
||||||
case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
case (_: InsufficientFunds, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
||||||
case (_: TooManyAcceptedHtlcs, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
case (_: TooManyAcceptedHtlcs, Some(channelUpdate)) => TemporaryChannelFailure(channelUpdate)
|
||||||
case (_: ChannelUnavailable, Some(channelUpdate)) if !Announcements.isEnabled(channelUpdate.flags) => ChannelDisabled(channelUpdate.flags, channelUpdate)
|
case (_: ChannelUnavailable, Some(channelUpdate)) if !Announcements.isEnabled(channelUpdate.channelFlags) => ChannelDisabled(channelUpdate.messageFlags, channelUpdate.channelFlags, channelUpdate)
|
||||||
case (_: ChannelUnavailable, None) => PermanentChannelFailure
|
case (_: ChannelUnavailable, None) => PermanentChannelFailure
|
||||||
case (_: HtlcTimedout, _) => PermanentChannelFailure
|
case (_: HtlcTimedout, _) => PermanentChannelFailure
|
||||||
case _ => TemporaryNodeFailure
|
case _ => TemporaryNodeFailure
|
||||||
|
@ -260,8 +260,8 @@ object Relayer {
|
||||||
channelUpdate_opt match {
|
channelUpdate_opt match {
|
||||||
case None =>
|
case None =>
|
||||||
Left(CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true))
|
Left(CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true))
|
||||||
case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.flags) =>
|
case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.channelFlags) =>
|
||||||
Left(CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.flags, channelUpdate)), commit = true))
|
Left(CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.messageFlags, channelUpdate.channelFlags, channelUpdate)), commit = true))
|
||||||
case Some(channelUpdate) if payload.amtToForward < channelUpdate.htlcMinimumMsat =>
|
case Some(channelUpdate) if payload.amtToForward < channelUpdate.htlcMinimumMsat =>
|
||||||
Left(CMD_FAIL_HTLC(add.id, Right(AmountBelowMinimum(add.amountMsat, channelUpdate)), commit = true))
|
Left(CMD_FAIL_HTLC(add.id, Right(AmountBelowMinimum(add.amountMsat, channelUpdate)), commit = true))
|
||||||
case Some(channelUpdate) if relayPayload.expiryDelta != channelUpdate.cltvExpiryDelta =>
|
case Some(channelUpdate) if relayPayload.expiryDelta != channelUpdate.cltvExpiryDelta =>
|
||||||
|
|
|
@ -37,10 +37,10 @@ object Announcements {
|
||||||
sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: HNil))))
|
sha256(sha256(serializationResult(LightningMessageCodecs.channelAnnouncementWitnessCodec.encode(features :: chainHash :: shortChannelId :: nodeId1 :: nodeId2 :: bitcoinKey1 :: bitcoinKey2 :: HNil))))
|
||||||
|
|
||||||
def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: BinaryData, addresses: List[NodeAddress]): BinaryData =
|
def nodeAnnouncementWitnessEncode(timestamp: Long, nodeId: PublicKey, rgbColor: Color, alias: String, features: BinaryData, addresses: List[NodeAddress]): BinaryData =
|
||||||
sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: (rgbColor) :: alias :: addresses :: HNil))))
|
sha256(sha256(serializationResult(LightningMessageCodecs.nodeAnnouncementWitnessCodec.encode(features :: timestamp :: nodeId :: rgbColor :: alias :: addresses :: HNil))))
|
||||||
|
|
||||||
def channelUpdateWitnessEncode(chainHash: BinaryData, shortChannelId: ShortChannelId, timestamp: Long, flags: BinaryData, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long): BinaryData =
|
def channelUpdateWitnessEncode(chainHash: BinaryData, shortChannelId: ShortChannelId, timestamp: Long, messageFlags: Byte, channelFlags: Byte, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Option[Long]): BinaryData =
|
||||||
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: flags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: HNil))))
|
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: messageFlags :: channelFlags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: htlcMaximumMsat :: HNil))))
|
||||||
|
|
||||||
def signChannelAnnouncement(chainHash: BinaryData, shortChannelId: ShortChannelId, localNodeSecret: PrivateKey, remoteNodeId: PublicKey, localFundingPrivKey: PrivateKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) = {
|
def signChannelAnnouncement(chainHash: BinaryData, shortChannelId: ShortChannelId, localNodeSecret: PrivateKey, remoteNodeId: PublicKey, localFundingPrivKey: PrivateKey, remoteFundingKey: PublicKey, features: BinaryData): (BinaryData, BinaryData) = {
|
||||||
val witness = if (isNode1(localNodeSecret.publicKey.toBin, remoteNodeId.toBin)) {
|
val witness = if (isNode1(localNodeSecret.publicKey.toBin, remoteNodeId.toBin)) {
|
||||||
|
@ -108,7 +108,7 @@ object Announcements {
|
||||||
*
|
*
|
||||||
* @return true if the node who sent these flags is node1
|
* @return true if the node who sent these flags is node1
|
||||||
*/
|
*/
|
||||||
def isNode1(flags: BinaryData) = !BitVector(flags.data).reverse.get(0)
|
def isNode1(channelFlags: Byte): Boolean = (channelFlags & 1) == 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node MAY create and send a channel_update with the disable bit set to
|
* A node MAY create and send a channel_update with the disable bit set to
|
||||||
|
@ -116,25 +116,31 @@ object Announcements {
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
def isEnabled(flags: BinaryData) = !BitVector(flags.data).reverse.get(1)
|
def isEnabled(channelFlags: Byte): Boolean = (channelFlags & 2) == 0
|
||||||
|
|
||||||
def makeFlags(isNode1: Boolean, enable: Boolean): BinaryData = BitVector.bits(!enable :: !isNode1 :: Nil).padLeft(16).toByteArray
|
def makeMessageFlags(hasOptionChannelHtlcMax: Boolean): Byte = BitVector.bits(hasOptionChannelHtlcMax :: Nil).padLeft(8).toByte()
|
||||||
|
|
||||||
def makeChannelUpdate(chainHash: BinaryData, nodeSecret: PrivateKey, remoteNodeId: PublicKey, shortChannelId: ShortChannelId, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, enable: Boolean = true, timestamp: Long = Platform.currentTime / 1000): ChannelUpdate = {
|
def makeChannelFlags(isNode1: Boolean, enable: Boolean): Byte = BitVector.bits(!enable :: !isNode1 :: Nil).padLeft(8).toByte()
|
||||||
val flags = makeFlags(isNode1 = isNode1(nodeSecret.publicKey.toBin, remoteNodeId.toBin), enable = enable)
|
|
||||||
require(flags.size == 2, "flags must be a 2-bytes field")
|
def makeChannelUpdate(chainHash: BinaryData, nodeSecret: PrivateKey, remoteNodeId: PublicKey, shortChannelId: ShortChannelId, cltvExpiryDelta: Int, htlcMinimumMsat: Long, feeBaseMsat: Long, feeProportionalMillionths: Long, htlcMaximumMsat: Long, enable: Boolean = true, timestamp: Long = Platform.currentTime / 1000): ChannelUpdate = {
|
||||||
val witness = channelUpdateWitnessEncode(chainHash, shortChannelId, timestamp, flags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths)
|
val messageFlags = makeMessageFlags(hasOptionChannelHtlcMax = true) // NB: we always support option_channel_htlc_max
|
||||||
|
val channelFlags = makeChannelFlags(isNode1 = isNode1(nodeSecret.publicKey.toBin, remoteNodeId.toBin), enable = enable)
|
||||||
|
val htlcMaximumMsatOpt = Some(htlcMaximumMsat)
|
||||||
|
|
||||||
|
val witness = channelUpdateWitnessEncode(chainHash, shortChannelId, timestamp, messageFlags, channelFlags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths, htlcMaximumMsatOpt)
|
||||||
val sig = Crypto.encodeSignature(Crypto.sign(witness, nodeSecret)) :+ 1.toByte
|
val sig = Crypto.encodeSignature(Crypto.sign(witness, nodeSecret)) :+ 1.toByte
|
||||||
ChannelUpdate(
|
ChannelUpdate(
|
||||||
signature = sig,
|
signature = sig,
|
||||||
chainHash = chainHash,
|
chainHash = chainHash,
|
||||||
shortChannelId = shortChannelId,
|
shortChannelId = shortChannelId,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
flags = flags,
|
messageFlags = messageFlags,
|
||||||
|
channelFlags = channelFlags,
|
||||||
cltvExpiryDelta = cltvExpiryDelta,
|
cltvExpiryDelta = cltvExpiryDelta,
|
||||||
htlcMinimumMsat = htlcMinimumMsat,
|
htlcMinimumMsat = htlcMinimumMsat,
|
||||||
feeBaseMsat = feeBaseMsat,
|
feeBaseMsat = feeBaseMsat,
|
||||||
feeProportionalMillionths = feeProportionalMillionths
|
feeProportionalMillionths = feeProportionalMillionths,
|
||||||
|
htlcMaximumMsat = htlcMaximumMsatOpt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +157,7 @@ object Announcements {
|
||||||
verifySignature(witness, ann.signature, ann.nodeId)
|
verifySignature(witness, ann.signature, ann.nodeId)
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
def checkSig(ann: ChannelUpdate, nodeId: PublicKey): Boolean = true /*{
|
def checkSig(upd: ChannelUpdate, nodeId: PublicKey): Boolean = true /*{
|
||||||
val witness = channelUpdateWitnessEncode(ann.chainHash, ann.shortChannelId, ann.timestamp, ann.flags, ann.cltvExpiryDelta, ann.htlcMinimumMsat, ann.feeBaseMsat, ann.feeProportionalMillionths)
|
val witness = channelUpdateWitnessEncode(ann.chainHash, ann.shortChannelId, ann.timestamp, ann.flags, ann.cltvExpiryDelta, ann.htlcMinimumMsat, ann.feeBaseMsat, ann.feeProportionalMillionths)
|
||||||
verifySignature(witness, ann.signature, nodeId)
|
verifySignature(witness, ann.signature, nodeId)
|
||||||
}*/
|
}*/
|
||||||
|
|
|
@ -172,6 +172,9 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
stay
|
stay
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Event(GetRoutingState, d: Data) =>
|
||||||
|
stay // ignored on Android
|
||||||
|
|
||||||
case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d) if d.channels.contains(shortChannelId) =>
|
case Event(WatchEventSpentBasic(BITCOIN_FUNDING_EXTERNAL_CHANNEL_SPENT(shortChannelId)), d) if d.channels.contains(shortChannelId) =>
|
||||||
val lostChannel = d.channels(shortChannelId)
|
val lostChannel = d.channels(shortChannelId)
|
||||||
log.info("funding tx of channelId={} has been spent", shortChannelId)
|
log.info("funding tx of channelId={} has been spent", shortChannelId)
|
||||||
|
@ -263,9 +266,6 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
.recover { case t => sender ! Status.Failure(t) }
|
.recover { case t => sender ! Status.Failure(t) }
|
||||||
stay
|
stay
|
||||||
|
|
||||||
case Event(GetRoutingState, d: Data) =>
|
|
||||||
stay // ignored on Android
|
|
||||||
|
|
||||||
case Event(SendChannelQuery(remoteNodeId, remote), d) =>
|
case Event(SendChannelQuery(remoteNodeId, remote), d) =>
|
||||||
// ask for everything
|
// ask for everything
|
||||||
// we currently send only one query_channel_range message per peer, when we just (re)connected to it, so we don't
|
// we currently send only one query_channel_range message per peer, when we just (re)connected to it, so we don't
|
||||||
|
@ -305,10 +305,10 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
log.debug("received channel update from {}", sender)
|
log.debug("received channel update from {}", sender)
|
||||||
stay using handle(u, sender, d)
|
stay using handle(u, sender, d)
|
||||||
|
|
||||||
case Event(PeerRoutingMessage(_, remoteNodeId, u: ChannelUpdate), d) =>
|
case Event(PeerRoutingMessage(transport, remoteNodeId, u: ChannelUpdate), d) =>
|
||||||
sender ! TransportHandler.ReadAck(u)
|
sender ! TransportHandler.ReadAck(u)
|
||||||
log.debug("received channel update for shortChannelId={}", u.shortChannelId)
|
log.debug("received channel update for shortChannelId={}", u.shortChannelId)
|
||||||
stay using handle(u, sender, d, remoteNodeId_opt = Some(remoteNodeId))
|
stay using handle(u, sender, d, remoteNodeId_opt = Some(remoteNodeId), transport_opt = Some(transport))
|
||||||
|
|
||||||
case Event(PeerRoutingMessage(_, _, c: ChannelAnnouncement), d) =>
|
case Event(PeerRoutingMessage(_, _, c: ChannelAnnouncement), d) =>
|
||||||
log.debug("received channel announcement for shortChannelId={} nodeId1={} nodeId2={}", c.shortChannelId, c.nodeId1, c.nodeId2)
|
log.debug("received channel announcement for shortChannelId={} nodeId1={} nodeId2={}", c.shortChannelId, c.nodeId1, c.nodeId2)
|
||||||
|
@ -588,7 +588,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
d
|
d
|
||||||
}
|
}
|
||||||
|
|
||||||
def handle(u: ChannelUpdate, origin: ActorRef, d: Data, remoteNodeId_opt: Option[PublicKey] = None): Data = {
|
def handle(u: ChannelUpdate, origin: ActorRef, d: Data, remoteNodeId_opt: Option[PublicKey] = None, transport_opt: Option[ActorRef] = None): Data = {
|
||||||
// On Android, after checking the sig we remove as much data as possible to reduce RAM consumption
|
// On Android, after checking the sig we remove as much data as possible to reduce RAM consumption
|
||||||
val u1 = u.copy(
|
val u1 = u.copy(
|
||||||
signature = null,
|
signature = null,
|
||||||
|
@ -610,7 +610,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
origin ! InvalidSignature(u)
|
origin ! InvalidSignature(u)
|
||||||
d
|
d
|
||||||
} else if (d.updates.contains(desc)) {
|
} else if (d.updates.contains(desc)) {
|
||||||
log.debug("updated channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.flags, u)
|
log.debug("updated channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
|
||||||
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
||||||
db.updateChannelUpdate(u1)
|
db.updateChannelUpdate(u1)
|
||||||
// we also need to update the graph
|
// we also need to update the graph
|
||||||
|
@ -618,7 +618,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
addEdge(d.graph, desc, u1)
|
addEdge(d.graph, desc, u1)
|
||||||
d.copy(updates = d.updates + (desc -> u1))
|
d.copy(updates = d.updates + (desc -> u1))
|
||||||
} else {
|
} else {
|
||||||
log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.flags, u)
|
log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
|
||||||
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
||||||
db.addChannelUpdate(u1)
|
db.addChannelUpdate(u1)
|
||||||
// we also need to update the graph
|
// we also need to update the graph
|
||||||
|
@ -639,7 +639,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
val publicChannel = false
|
val publicChannel = false
|
||||||
val remoteNodeId = d.privateChannels(u.shortChannelId)
|
val remoteNodeId = d.privateChannels(u.shortChannelId)
|
||||||
val (a, b) = if (Announcements.isNode1(nodeParams.nodeId, remoteNodeId)) (nodeParams.nodeId, remoteNodeId) else (remoteNodeId, nodeParams.nodeId)
|
val (a, b) = if (Announcements.isNode1(nodeParams.nodeId, remoteNodeId)) (nodeParams.nodeId, remoteNodeId) else (remoteNodeId, nodeParams.nodeId)
|
||||||
val desc = if (Announcements.isNode1(u.flags)) ChannelDesc(u.shortChannelId, a, b) else ChannelDesc(u.shortChannelId, b, a)
|
val desc = if (Announcements.isNode1(u.channelFlags)) ChannelDesc(u.shortChannelId, a, b) else ChannelDesc(u.shortChannelId, b, a)
|
||||||
if (isStale(u)) {
|
if (isStale(u)) {
|
||||||
log.debug("ignoring {} (stale)", u)
|
log.debug("ignoring {} (stale)", u)
|
||||||
d
|
d
|
||||||
|
@ -651,14 +651,14 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
|
||||||
origin ! InvalidSignature(u)
|
origin ! InvalidSignature(u)
|
||||||
d
|
d
|
||||||
} else if (d.privateUpdates.contains(desc)) {
|
} else if (d.privateUpdates.contains(desc)) {
|
||||||
log.debug("updated channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.flags, u)
|
log.debug("updated channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
|
||||||
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
||||||
// we also need to update the graph
|
// we also need to update the graph
|
||||||
removeEdge(d.graph, desc)
|
removeEdge(d.graph, desc)
|
||||||
addEdge(d.graph, desc, u1)
|
addEdge(d.graph, desc, u1)
|
||||||
d.copy(privateUpdates = d.privateUpdates + (desc -> u1))
|
d.copy(privateUpdates = d.privateUpdates + (desc -> u1))
|
||||||
} else {
|
} else {
|
||||||
log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.flags, u)
|
log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
|
||||||
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
context.system.eventStream.publish(ChannelUpdateReceived(u))
|
||||||
// we also need to update the graph
|
// we also need to update the graph
|
||||||
addEdge(d.graph, desc, u1)
|
addEdge(d.graph, desc, u1)
|
||||||
|
@ -685,7 +685,7 @@ object Router {
|
||||||
def toFakeUpdate(extraHop: ExtraHop): ChannelUpdate =
|
def toFakeUpdate(extraHop: ExtraHop): ChannelUpdate =
|
||||||
// the `direction` bit in flags will not be accurate but it doesn't matter because it is not used
|
// the `direction` bit in flags will not be accurate but it doesn't matter because it is not used
|
||||||
// what matters is that the `disable` bit is 0 so that this update doesn't get filtered out
|
// what matters is that the `disable` bit is 0 so that this update doesn't get filtered out
|
||||||
ChannelUpdate(signature = "", chainHash = "", extraHop.shortChannelId, Platform.currentTime / 1000, flags = BinaryData("0000"), extraHop.cltvExpiryDelta, htlcMinimumMsat = 0L, extraHop.feeBaseMsat, extraHop.feeProportionalMillionths)
|
ChannelUpdate(signature = "", chainHash = "", extraHop.shortChannelId, Platform.currentTime / 1000, messageFlags = 0, channelFlags = 0, extraHop.cltvExpiryDelta, htlcMinimumMsat = 0L, extraHop.feeBaseMsat, extraHop.feeProportionalMillionths, None)
|
||||||
|
|
||||||
def toFakeUpdates(extraRoute: Seq[ExtraHop], targetNodeId: PublicKey): Map[ChannelDesc, ChannelUpdate] = {
|
def toFakeUpdates(extraRoute: Seq[ExtraHop], targetNodeId: PublicKey): Map[ChannelDesc, ChannelUpdate] = {
|
||||||
// BOLT 11: "For each entry, the pubkey is the node ID of the start of the channel", and the last node is the destination
|
// BOLT 11: "For each entry, the pubkey is the node ID of the start of the channel", and the last node is the destination
|
||||||
|
@ -696,9 +696,8 @@ object Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDesc(u: ChannelUpdate, channel: ChannelAnnouncement): ChannelDesc = {
|
def getDesc(u: ChannelUpdate, channel: ChannelAnnouncement): ChannelDesc = {
|
||||||
require(u.flags.data.size == 2, s"invalid flags length ${u.flags.data.size} != 2")
|
|
||||||
// the least significant bit tells us if it is node1 or node2
|
// the least significant bit tells us if it is node1 or node2
|
||||||
if (Announcements.isNode1(u.flags)) ChannelDesc(u.shortChannelId, channel.nodeId1, channel.nodeId2) else ChannelDesc(u.shortChannelId, channel.nodeId2, channel.nodeId1)
|
if (Announcements.isNode1(u.channelFlags)) ChannelDesc(u.shortChannelId, channel.nodeId1, channel.nodeId2) else ChannelDesc(u.shortChannelId, channel.nodeId2, channel.nodeId1)
|
||||||
}
|
}
|
||||||
|
|
||||||
def isRelatedTo(c: ChannelAnnouncement, nodeId: PublicKey) = nodeId == c.nodeId1 || nodeId == c.nodeId2
|
def isRelatedTo(c: ChannelAnnouncement, nodeId: PublicKey) = nodeId == c.nodeId1 || nodeId == c.nodeId2
|
||||||
|
@ -803,7 +802,7 @@ object Router {
|
||||||
* Note that we only add the edge if the corresponding channel is enabled
|
* Note that we only add the edge if the corresponding channel is enabled
|
||||||
*/
|
*/
|
||||||
def addEdge(g: WeightedGraph[PublicKey, DescEdge], d: ChannelDesc, u: ChannelUpdate) = {
|
def addEdge(g: WeightedGraph[PublicKey, DescEdge], d: ChannelDesc, u: ChannelUpdate) = {
|
||||||
if (Announcements.isEnabled(u.flags)) {
|
if (Announcements.isEnabled(u.channelFlags)) {
|
||||||
g.addVertex(d.a)
|
g.addVertex(d.a)
|
||||||
g.addVertex(d.b)
|
g.addVertex(d.b)
|
||||||
val e = new DescEdge(d, u)
|
val e = new DescEdge(d, u)
|
||||||
|
|
|
@ -46,7 +46,7 @@ case object RequiredChannelFeatureMissing extends Perm { def message = "channel
|
||||||
case object UnknownNextPeer extends Perm { def message = "processing node does not know the next peer in the route" }
|
case object UnknownNextPeer extends Perm { def message = "processing node does not know the next peer in the route" }
|
||||||
case class AmountBelowMinimum(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment amount was below the minimum required by the channel" }
|
case class AmountBelowMinimum(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment amount was below the minimum required by the channel" }
|
||||||
case class FeeInsufficient(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment fee was below the minimum required by the channel" }
|
case class FeeInsufficient(amountMsat: Long, update: ChannelUpdate) extends Update { def message = s"payment fee was below the minimum required by the channel" }
|
||||||
case class ChannelDisabled(flags: BinaryData, update: ChannelUpdate) extends Update { def message = "channel is currently disabled" }
|
case class ChannelDisabled(messageFlags: Byte, channelFlags: Byte, update: ChannelUpdate) extends Update { def message = "channel is currently disabled" }
|
||||||
case class IncorrectCltvExpiry(expiry: Long, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" }
|
case class IncorrectCltvExpiry(expiry: Long, update: ChannelUpdate) extends Update { def message = "payment expiry doesn't match the value in the onion" }
|
||||||
case object UnknownPaymentHash extends Perm { def message = "payment hash is unknown to the final node" }
|
case object UnknownPaymentHash extends Perm { def message = "payment hash is unknown to the final node" }
|
||||||
case object IncorrectPaymentAmount extends Perm { def message = "payment amount is incorrect" }
|
case object IncorrectPaymentAmount extends Perm { def message = "payment amount is incorrect" }
|
||||||
|
@ -87,11 +87,11 @@ object FailureMessageCodecs {
|
||||||
.typecase(UPDATE | 12, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
|
.typecase(UPDATE | 12, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
|
||||||
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
|
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
|
||||||
.typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec)).as[ExpiryTooSoon])
|
.typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec)).as[ExpiryTooSoon])
|
||||||
.typecase(UPDATE | 20, (("flags" | binarydata(2)) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled])
|
.typecase(UPDATE | 20, (("messageFlags" | byte) :: ("channelFlags" | byte) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled])
|
||||||
.typecase(PERM | 15, provide(UnknownPaymentHash))
|
.typecase(PERM | 15, provide(UnknownPaymentHash))
|
||||||
.typecase(PERM | 16, provide(IncorrectPaymentAmount))
|
.typecase(PERM | 16, provide(IncorrectPaymentAmount))
|
||||||
.typecase(17, provide(FinalExpiryTooSoon))
|
.typecase(17, provide(FinalExpiryTooSoon))
|
||||||
.typecase(18, (("expiry" | uint32)).as[FinalIncorrectCltvExpiry])
|
.typecase(18, (("expiry" | uint32)).as[FinalIncorrectCltvExpiry])
|
||||||
.typecase(19, (("amountMsat" | uint32)).as[FinalIncorrectHtlcAmount])
|
.typecase(19, (("amountMsat" | uint64)).as[FinalIncorrectHtlcAmount])
|
||||||
.typecase(21, provide(ExpiryTooFar))
|
.typecase(21, provide(ExpiryTooFar))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
package fr.acinq.eclair.wire
|
package fr.acinq.eclair.wire
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.net.{Inet4Address, Inet6Address, InetAddress, InetSocketAddress}
|
import java.net.{Inet4Address, Inet6Address, InetAddress}
|
||||||
|
|
||||||
import com.google.common.cache.{CacheBuilder, CacheLoader}
|
import com.google.common.cache.{CacheBuilder, CacheLoader}
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||||
|
@ -28,6 +27,7 @@ import fr.acinq.eclair.{ShortChannelId, UInt64, wire}
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import scodec.{Attempt, Codec, DecodeResult, Err, SizeBound}
|
import scodec.{Attempt, Codec, DecodeResult, Err, SizeBound}
|
||||||
|
import shapeless.nat._
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
@ -280,15 +280,18 @@ object LightningMessageCodecs {
|
||||||
("signature" | signature) ::
|
("signature" | signature) ::
|
||||||
nodeAnnouncementWitnessCodec).as[NodeAnnouncement]
|
nodeAnnouncementWitnessCodec).as[NodeAnnouncement]
|
||||||
|
|
||||||
val channelUpdateWitnessCodec = (
|
val channelUpdateWitnessCodec =
|
||||||
("chainHash" | binarydata(32)) ::
|
("chainHash" | binarydata(32)) ::
|
||||||
("shortChannelId" | shortchannelid) ::
|
("shortChannelId" | shortchannelid) ::
|
||||||
("timestamp" | uint32) ::
|
("timestamp" | uint32) ::
|
||||||
("flags" | binarydata(2)) ::
|
(("messageFlags" | byte) >>:~ { messageFlags =>
|
||||||
("cltvExpiryDelta" | uint16) ::
|
("channelFlags" | byte) ::
|
||||||
("htlcMinimumMsat" | uint64) ::
|
("cltvExpiryDelta" | uint16) ::
|
||||||
("feeBaseMsat" | uint32) ::
|
("htlcMinimumMsat" | uint64) ::
|
||||||
("feeProportionalMillionths" | uint32))
|
("feeBaseMsat" | uint32) ::
|
||||||
|
("feeProportionalMillionths" | uint32) ::
|
||||||
|
("htlcMaximumMsat" | conditional((messageFlags & 1) != 0, uint64))
|
||||||
|
})
|
||||||
|
|
||||||
val channelUpdateCodec: Codec[ChannelUpdate] = (
|
val channelUpdateCodec: Codec[ChannelUpdate] = (
|
||||||
("signature" | signature) ::
|
("signature" | signature) ::
|
||||||
|
@ -347,7 +350,7 @@ object LightningMessageCodecs {
|
||||||
("chainHash" | binarydata(32)) ::
|
("chainHash" | binarydata(32)) ::
|
||||||
("firstTimestamp" | uint32) ::
|
("firstTimestamp" | uint32) ::
|
||||||
("timestampRange" | uint32)
|
("timestampRange" | uint32)
|
||||||
).as[GossipTimestampFilter]
|
).as[GossipTimestampFilter]
|
||||||
|
|
||||||
val lightningMessageCodec = discriminated[LightningMessage].by(uint16)
|
val lightningMessageCodec = discriminated[LightningMessage].by(uint16)
|
||||||
.typecase(16, initCodec)
|
.typecase(16, initCodec)
|
||||||
|
@ -415,4 +418,4 @@ object LightningMessageCodecs {
|
||||||
("outgoing_cltv_value" | uint32) ::
|
("outgoing_cltv_value" | uint32) ::
|
||||||
("unused_with_v0_version_on_header" | ignore(8 * 12))).as[PerHopPayload]
|
("unused_with_v0_version_on_header" | ignore(8 * 12))).as[PerHopPayload]
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ import java.net.{Inet4Address, Inet6Address, InetSocketAddress}
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar}
|
import fr.acinq.bitcoin.Crypto.{Point, PublicKey, Scalar}
|
||||||
import fr.acinq.eclair.{ShortChannelId, UInt64}
|
import fr.acinq.eclair.{ShortChannelId, UInt64}
|
||||||
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 15/11/2016.
|
* Created by PM on 15/11/2016.
|
||||||
|
@ -192,11 +193,15 @@ case class ChannelUpdate(signature: BinaryData,
|
||||||
chainHash: BinaryData,
|
chainHash: BinaryData,
|
||||||
shortChannelId: ShortChannelId,
|
shortChannelId: ShortChannelId,
|
||||||
timestamp: Long,
|
timestamp: Long,
|
||||||
flags: BinaryData,
|
messageFlags: Byte,
|
||||||
|
channelFlags: Byte,
|
||||||
cltvExpiryDelta: Int,
|
cltvExpiryDelta: Int,
|
||||||
htlcMinimumMsat: Long,
|
htlcMinimumMsat: Long,
|
||||||
feeBaseMsat: Long,
|
feeBaseMsat: Long,
|
||||||
feeProportionalMillionths: Long) extends RoutingMessage with HasTimestamp with HasChainHash
|
feeProportionalMillionths: Long,
|
||||||
|
htlcMaximumMsat: Option[Long]) extends RoutingMessage with HasTimestamp with HasChainHash {
|
||||||
|
require(((messageFlags & 1) != 0) == htlcMaximumMsat.isDefined, "htlcMaximumMsat is not consistent with messageFlags")
|
||||||
|
}
|
||||||
|
|
||||||
case class PerHopPayload(shortChannelId: ShortChannelId,
|
case class PerHopPayload(shortChannelId: ShortChannelId,
|
||||||
amtToForward: Long,
|
amtToForward: Long,
|
||||||
|
@ -257,7 +262,7 @@ case class ReplyChannelRangeEx(chainHash: BinaryData,
|
||||||
data: BinaryData) extends RoutingMessage with HasChainHash
|
data: BinaryData) extends RoutingMessage with HasChainHash
|
||||||
|
|
||||||
case class ReplyShortChannelIdsEnd(chainHash: BinaryData,
|
case class ReplyShortChannelIdsEnd(chainHash: BinaryData,
|
||||||
complete: Byte) extends RoutingMessage with HasChainHash
|
complete: Byte) extends RoutingMessage with HasChainHash
|
||||||
|
|
||||||
case class ReplyShortChannelIdsEndEx(chainHash: BinaryData,
|
case class ReplyShortChannelIdsEndEx(chainHash: BinaryData,
|
||||||
complete: Byte) extends RoutingMessage with HasChainHash
|
complete: Byte) extends RoutingMessage with HasChainHash
|
||||||
|
|
|
@ -48,8 +48,8 @@
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<!--appender-ref ref="FILE"/>
|
<!--appender-ref ref="FILE"/>
|
||||||
<appender-ref ref="CONSOLEWARN"/-->
|
<appender-ref ref="CONSOLEWARN"/>
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/-->
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -17,11 +17,9 @@
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{Btc, MilliBtc, MilliSatoshi, Satoshi}
|
import fr.acinq.bitcoin.{Btc, MilliBtc, MilliSatoshi, Satoshi}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class CoinUtilsSpec extends FunSuite {
|
class CoinUtilsSpec extends FunSuite {
|
||||||
|
|
||||||
test("Convert string amount to the correct BtcAmount") {
|
test("Convert string amount to the correct BtcAmount") {
|
||||||
|
|
|
@ -20,14 +20,12 @@ import java.nio.ByteOrder
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Protocol
|
import fr.acinq.bitcoin.Protocol
|
||||||
import fr.acinq.eclair.Features._
|
import fr.acinq.eclair.Features._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 27/01/2017.
|
* Created by PM on 27/01/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class FeaturesSpec extends FunSuite {
|
class FeaturesSpec extends FunSuite {
|
||||||
|
|
||||||
test("'initial_routing_sync' feature") {
|
test("'initial_routing_sync' feature") {
|
||||||
|
|
|
@ -8,15 +8,14 @@ import fr.acinq.eclair.crypto.ShaChain
|
||||||
import fr.acinq.eclair.db.ChannelStateSpec
|
import fr.acinq.eclair.db.ChannelStateSpec
|
||||||
import fr.acinq.eclair.transactions._
|
import fr.acinq.eclair.transactions._
|
||||||
import fr.acinq.eclair.wire.{NodeAddress, UpdateAddHtlc, UpdateFailHtlc}
|
import fr.acinq.eclair.wire.{NodeAddress, UpdateAddHtlc, UpdateFailHtlc}
|
||||||
import org.junit.runner.RunWith
|
import grizzled.slf4j.Logging
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
import org.scalatest.junit.JUnitRunner
|
||||||
import upickle.default.write
|
import upickle.default.write
|
||||||
|
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
class JsonSerializersSpec extends FunSuite with Logging {
|
||||||
class JsonSerializersSpec extends FunSuite {
|
|
||||||
import JsonSerializers._
|
import JsonSerializers._
|
||||||
|
|
||||||
test("deserialize Map[OutPoint, BinaryData]") {
|
test("deserialize Map[OutPoint, BinaryData]") {
|
||||||
|
@ -60,7 +59,7 @@ class JsonSerializersSpec extends FunSuite {
|
||||||
globalFeatures = randomBytes(256),
|
globalFeatures = randomBytes(256),
|
||||||
localFeatures = randomBytes(256))
|
localFeatures = randomBytes(256))
|
||||||
|
|
||||||
println(write(localParams))
|
logger.info(write(localParams))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,12 +80,12 @@ class JsonSerializersSpec extends FunSuite {
|
||||||
globalFeatures = randomBytes(256),
|
globalFeatures = randomBytes(256),
|
||||||
localFeatures = randomBytes(256))
|
localFeatures = randomBytes(256))
|
||||||
|
|
||||||
println(write(remoteParams))
|
logger.info(write(remoteParams))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("serialize CommitmentSpec") {
|
test("serialize CommitmentSpec") {
|
||||||
val spec = CommitmentSpec(Set(DirectedHtlc(IN, UpdateAddHtlc(randomKey.publicKey.value.toBin(true), 421, 1245, randomBytes(32), 1000, BinaryData("010101")))), feeratePerKw = 1233, toLocalMsat = 100, toRemoteMsat = 200)
|
val spec = CommitmentSpec(Set(DirectedHtlc(IN, UpdateAddHtlc(randomKey.publicKey.value.toBin(true), 421, 1245, randomBytes(32), 1000, BinaryData("010101")))), feeratePerKw = 1233, toLocalMsat = 100, toRemoteMsat = 200)
|
||||||
println(write(spec))
|
logger.info(write(spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("serialize LocalChanges") {
|
test("serialize LocalChanges") {
|
||||||
|
@ -94,7 +93,7 @@ class JsonSerializersSpec extends FunSuite {
|
||||||
val add = UpdateAddHtlc(channelId, 421, 1245, randomBytes(32), 1000, BinaryData("010101"))
|
val add = UpdateAddHtlc(channelId, 421, 1245, randomBytes(32), 1000, BinaryData("010101"))
|
||||||
val fail = UpdateFailHtlc(channelId, 42, BinaryData("0101"))
|
val fail = UpdateFailHtlc(channelId, 42, BinaryData("0101"))
|
||||||
val localChanges = LocalChanges(proposed = add :: add :: fail :: Nil, signed = add :: Nil, acked = fail :: fail :: Nil)
|
val localChanges = LocalChanges(proposed = add :: add :: fail :: Nil, signed = add :: Nil, acked = fail :: fail :: Nil)
|
||||||
println(write(localChanges))
|
logger.info(write(localChanges))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("serialize shaChain") {
|
test("serialize shaChain") {
|
||||||
|
@ -103,16 +102,16 @@ class JsonSerializersSpec extends FunSuite {
|
||||||
for (i <- 0 until 7) {
|
for (i <- 0 until 7) {
|
||||||
receiver = receiver.addHash(ShaChain.shaChainFromSeed(seed, 0xFFFFFFFFFFFFL - i), 0xFFFFFFFFFFFFL - i)
|
receiver = receiver.addHash(ShaChain.shaChainFromSeed(seed, 0xFFFFFFFFFFFFL - i), 0xFFFFFFFFFFFFL - i)
|
||||||
}
|
}
|
||||||
println(write(receiver))
|
logger.info(write(receiver))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("serialize Commitments") {
|
test("serialize Commitments") {
|
||||||
val commitments = ChannelStateSpec.commitments
|
val commitments = ChannelStateSpec.commitments
|
||||||
println(write(commitments))
|
logger.info(write(commitments))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("serialize DATA_NORMAL") {
|
test("serialize DATA_NORMAL") {
|
||||||
val data = ChannelStateSpec.normal
|
val data = ChannelStateSpec.normal
|
||||||
println(write(data))
|
logger.info(write(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,14 @@ package fr.acinq.eclair
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, BinaryData, Block, Crypto, Script}
|
import fr.acinq.bitcoin.{Base58, Base58Check, Bech32, BinaryData, Block, Crypto, Script}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 27/01/2017.
|
* Created by PM on 27/01/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PackageSpec extends FunSuite {
|
class PackageSpec extends FunSuite {
|
||||||
|
|
||||||
test("compute long channel id") {
|
test("compute long channel id") {
|
||||||
|
|
|
@ -16,12 +16,10 @@
|
||||||
|
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ShortChannelIdSpec extends FunSuite {
|
class ShortChannelIdSpec extends FunSuite {
|
||||||
|
|
||||||
test("handle values from 0 to 0xffffffffffff") {
|
test("handle values from 0 to 0xffffffffffff") {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.sql.DriverManager
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block, Script}
|
import fr.acinq.bitcoin.{BinaryData, Block, Script}
|
||||||
import fr.acinq.eclair.NodeParams.BITCOIND
|
import fr.acinq.eclair.NodeParams.BITCOIND
|
||||||
import fr.acinq.eclair.TestConstants.Alice.sqlite
|
|
||||||
import fr.acinq.eclair.crypto.LocalKeyManager
|
import fr.acinq.eclair.crypto.LocalKeyManager
|
||||||
import fr.acinq.eclair.db.sqlite._
|
import fr.acinq.eclair.db.sqlite._
|
||||||
import fr.acinq.eclair.io.Peer
|
import fr.acinq.eclair.io.Peer
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
package fr.acinq.eclair
|
package fr.acinq.eclair
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class UInt64Spec extends FunSuite {
|
class UInt64Spec extends FunSuite {
|
||||||
|
|
||||||
test("handle values from 0 to 2^63-1") {
|
test("handle values from 0 to 2^63-1") {
|
||||||
|
|
|
@ -17,14 +17,12 @@
|
||||||
package fr.acinq.eclair.blockchain
|
package fr.acinq.eclair.blockchain
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Transaction
|
import fr.acinq.bitcoin.Transaction
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 27/01/2017.
|
* Created by PM on 27/01/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WatcherSpec extends FunSuite {
|
class WatcherSpec extends FunSuite {
|
||||||
|
|
||||||
test("extract pay2wpkh pubkey script") {
|
test("extract pay2wpkh pubkey script") {
|
||||||
|
|
|
@ -30,8 +30,6 @@ import fr.acinq.eclair.{addressToPublicKeyScript, randomKey}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.JsonAST._
|
import org.json4s.JsonAST._
|
||||||
import org.json4s.{DefaultFormats, JString}
|
import org.json4s.{DefaultFormats, JString}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.collection.JavaConversions._
|
import scala.collection.JavaConversions._
|
||||||
|
@ -40,7 +38,7 @@ import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.{Random, Try}
|
import scala.util.{Random, Try}
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
class BitcoinCoreWalletSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
||||||
|
|
|
@ -41,7 +41,7 @@ trait BitcoindService extends Logging {
|
||||||
val INTEGRATION_TMP_DIR = s"${System.getProperty("buildDirectory")}/integration-${UUID.randomUUID().toString}"
|
val INTEGRATION_TMP_DIR = s"${System.getProperty("buildDirectory")}/integration-${UUID.randomUUID().toString}"
|
||||||
logger.info(s"using tmp dir: $INTEGRATION_TMP_DIR")
|
logger.info(s"using tmp dir: $INTEGRATION_TMP_DIR")
|
||||||
|
|
||||||
val PATH_BITCOIND = new File(System.getProperty("buildDirectory"), "bitcoin-0.16.0/bin/bitcoind")
|
val PATH_BITCOIND = new File(System.getProperty("buildDirectory"), "bitcoin-0.16.3/bin/bitcoind")
|
||||||
val PATH_BITCOIND_DATADIR = new File(INTEGRATION_TMP_DIR, "datadir-bitcoin")
|
val PATH_BITCOIND_DATADIR = new File(INTEGRATION_TMP_DIR, "datadir-bitcoin")
|
||||||
|
|
||||||
var bitcoind: Process = null
|
var bitcoind: Process = null
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 ACINQ SAS
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fr.acinq.eclair.blockchain.bitcoind
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.actor.Status.Failure
|
||||||
|
import akka.pattern.pipe
|
||||||
|
import akka.testkit.{TestKit, TestProbe}
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import fr.acinq.bitcoin.Transaction
|
||||||
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, ExtendedBitcoinClient}
|
||||||
|
import grizzled.slf4j.Logging
|
||||||
|
import org.json4s.JsonAST._
|
||||||
|
import org.json4s.{DefaultFormats, JString}
|
||||||
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
|
import scala.collection.JavaConversions._
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
|
|
||||||
|
class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
|
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
||||||
|
val config = ConfigFactory.load(commonConfig).getConfig("eclair")
|
||||||
|
|
||||||
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
|
override def beforeAll(): Unit = {
|
||||||
|
startBitcoind()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def afterAll(): Unit = {
|
||||||
|
stopBitcoind()
|
||||||
|
}
|
||||||
|
|
||||||
|
test("wait bitcoind ready") {
|
||||||
|
waitForBitcoindReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
test("send transaction idempotent") {
|
||||||
|
val bitcoinClient = new BasicBitcoinJsonRPCClient(
|
||||||
|
user = config.getString("bitcoind.rpcuser"),
|
||||||
|
password = config.getString("bitcoind.rpcpassword"),
|
||||||
|
host = config.getString("bitcoind.host"),
|
||||||
|
port = config.getInt("bitcoind.rpcport"))
|
||||||
|
|
||||||
|
val sender = TestProbe()
|
||||||
|
bitcoinClient.invoke("getnewaddress").pipeTo(sender.ref)
|
||||||
|
val JString(address) = sender.expectMsgType[JString]
|
||||||
|
bitcoinClient.invoke("createrawtransaction", Array.empty, Map(address -> 6)).pipeTo(sender.ref)
|
||||||
|
val JString(noinputTx) = sender.expectMsgType[JString]
|
||||||
|
bitcoinClient.invoke("fundrawtransaction", noinputTx).pipeTo(sender.ref)
|
||||||
|
val json = sender.expectMsgType[JValue]
|
||||||
|
val JString(unsignedtx) = json \ "hex"
|
||||||
|
val JInt(changePos) = json \ "changepos"
|
||||||
|
bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref)
|
||||||
|
val JString(signedTx) = sender.expectMsgType[JValue] \ "hex"
|
||||||
|
val tx = Transaction.read(signedTx)
|
||||||
|
val txid = tx.txid.toString()
|
||||||
|
|
||||||
|
// test starts here
|
||||||
|
val client = new ExtendedBitcoinClient(bitcoinClient)
|
||||||
|
// we publish it a first time
|
||||||
|
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||||
|
sender.expectMsg(txid)
|
||||||
|
// we publish the tx a second time to test idempotence
|
||||||
|
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||||
|
sender.expectMsg(txid)
|
||||||
|
// let's confirm the tx
|
||||||
|
bitcoinClient.invoke("generate", 1).pipeTo(sender.ref)
|
||||||
|
sender.expectMsgType[JValue]
|
||||||
|
// and publish the tx a third time to test idempotence
|
||||||
|
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||||
|
sender.expectMsg(txid)
|
||||||
|
|
||||||
|
// now let's spent the output of the tx
|
||||||
|
val spendingTx = {
|
||||||
|
val pos = if (changePos == 0) 1 else 0
|
||||||
|
bitcoinClient.invoke("createrawtransaction", Array(Map("txid" -> txid, "vout" -> pos)), Map(address -> 5.99999)).pipeTo(sender.ref)
|
||||||
|
val JString(unsignedtx) = sender.expectMsgType[JValue]
|
||||||
|
bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref)
|
||||||
|
val JString(signedTx) = sender.expectMsgType[JValue] \ "hex"
|
||||||
|
signedTx
|
||||||
|
}
|
||||||
|
bitcoinClient.invoke("sendrawtransaction", spendingTx).pipeTo(sender.ref)
|
||||||
|
val JString(spendingTxid) = sender.expectMsgType[JValue]
|
||||||
|
|
||||||
|
// and publish the tx a fourth time to test idempotence
|
||||||
|
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||||
|
sender.expectMsg(txid)
|
||||||
|
// let's confirm the tx
|
||||||
|
bitcoinClient.invoke("generate", 1).pipeTo(sender.ref)
|
||||||
|
sender.expectMsgType[JValue]
|
||||||
|
// and publish the tx a fifth time to test idempotence
|
||||||
|
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||||
|
sender.expectMsg(txid)
|
||||||
|
|
||||||
|
// this one should be rejected
|
||||||
|
client.publishTransaction(Transaction.read("02000000000101b9e2a3f518fd74e696d258fed3c78c43f84504e76c99212e01cf225083619acf00000000000d0199800136b34b00000000001600145464ce1e5967773922506e285780339d72423244040047304402206795df1fd93c285d9028c384aacf28b43679f1c3f40215fd7bd1abbfb816ee5a022047a25b8c128e692d4717b6dd7b805aa24ecbbd20cfd664ab37a5096577d4a15d014730440220770f44121ed0e71ec4b482dded976f2febd7500dfd084108e07f3ce1e85ec7f5022025b32dc0d551c47136ce41bfb80f5a10de95c0babb22a3ae2d38e6688b32fcb20147522102c2662ab3e4fa18a141d3be3317c6ee134aff10e6cd0a91282a25bf75c0481ebc2102e952dd98d79aa796289fa438e4fdeb06ed8589ff2a0f032b0cfcb4d7b564bc3252aea58d1120")).pipeTo(sender.ref)
|
||||||
|
sender.expectMsgType[Failure]
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,14 +21,12 @@ import akka.testkit.{TestKit, TestProbe}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
||||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient._
|
import fr.acinq.eclair.blockchain.electrum.ElectrumClient._
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ElectrumClientPoolSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
|
class ElectrumClientPoolSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
|
||||||
var router: ActorRef = _
|
var router: ActorRef = _
|
||||||
val probe = TestProbe()
|
val probe = TestProbe()
|
||||||
|
|
|
@ -22,14 +22,12 @@ import akka.actor.{ActorRef, ActorSystem, Props}
|
||||||
import akka.testkit.{TestKit, TestProbe}
|
import akka.testkit.{TestKit, TestProbe}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
|
class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
|
||||||
|
|
||||||
import ElectrumClient._
|
import ElectrumClient._
|
||||||
|
|
|
@ -20,14 +20,13 @@ import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, derivePrivateKey}
|
import fr.acinq.bitcoin.DeterministicWallet.{ExtendedPrivateKey, derivePrivateKey}
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair.transactions.Transactions
|
import fr.acinq.eclair.transactions.Transactions
|
||||||
import org.junit.runner.RunWith
|
import grizzled.slf4j.Logging
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.util.{Failure, Random, Success, Try}
|
import scala.util.{Failure, Random, Success, Try}
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ElectrumWalletBasicSpec extends FunSuite {
|
class ElectrumWalletBasicSpec extends FunSuite with Logging {
|
||||||
|
|
||||||
import ElectrumWallet._
|
import ElectrumWallet._
|
||||||
import ElectrumWalletBasicSpec._
|
import ElectrumWalletBasicSpec._
|
||||||
|
@ -197,7 +196,7 @@ class ElectrumWalletBasicSpec extends FunSuite {
|
||||||
Try(state1.completeTransaction(tx, feeRatePerKw, minimumFee, dustLimit, true)) match {
|
Try(state1.completeTransaction(tx, feeRatePerKw, minimumFee, dustLimit, true)) match {
|
||||||
case Success((state2, tx1, fee1)) => ()
|
case Success((state2, tx1, fee1)) => ()
|
||||||
case Failure(cause) if cause.getMessage != null && cause.getMessage.contains("insufficient funds") => ()
|
case Failure(cause) if cause.getMessage != null && cause.getMessage.contains("insufficient funds") => ()
|
||||||
case Failure(cause) => println(s"unexpected $cause")
|
case Failure(cause) => logger.error(s"unexpected $cause")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,11 @@ import akka.testkit.{TestFSMRef, TestKit, TestProbe}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block, MnemonicCode, Satoshi}
|
import fr.acinq.bitcoin.{BinaryData, Block, MnemonicCode, Satoshi}
|
||||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ScriptHashSubscription, ScriptHashSubscriptionResponse}
|
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ScriptHashSubscription, ScriptHashSubscriptionResponse}
|
||||||
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._
|
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuiteLike
|
import org.scalatest.FunSuiteLike
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
|
@ -93,7 +91,7 @@ class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) wit
|
||||||
listener.expectMsgType[NewWalletReceiveAddress]
|
listener.expectMsgType[NewWalletReceiveAddress]
|
||||||
}
|
}
|
||||||
|
|
||||||
test("don't send the same ready mnessage more then once") {
|
test("don't send the same ready message more then once") {
|
||||||
// listener should be notified
|
// listener should be notified
|
||||||
sender.send(wallet, ElectrumClient.HeaderSubscriptionResponse(header4))
|
sender.send(wallet, ElectrumClient.HeaderSubscriptionResponse(header4))
|
||||||
assert(listener.expectMsgType[WalletReady].timestamp == header4.timestamp)
|
assert(listener.expectMsgType[WalletReady].timestamp == header4.timestamp)
|
||||||
|
|
|
@ -27,20 +27,14 @@ import fr.acinq.eclair.blockchain.bitcoind.BitcoinCoreWallet.{FundTransactionRes
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, BitcoindService}
|
import fr.acinq.eclair.blockchain.bitcoind.{BitcoinCoreWallet, BitcoindService}
|
||||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse}
|
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.JsonAST.{JDecimal, JDouble, JString, JValue}
|
import org.json4s.JsonAST.{JDecimal, JString, JValue}
|
||||||
import org.junit.experimental.categories.Category
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
trait DockerTest {}
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
@Category(Array(classOf[DockerTest]))
|
|
||||||
class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging {
|
class ElectrumWalletSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
import ElectrumWallet._
|
import ElectrumWallet._
|
||||||
|
|
|
@ -27,15 +27,11 @@ import fr.acinq.eclair.blockchain.{WatchConfirmed, WatchEventConfirmed, WatchEve
|
||||||
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, BITCOIN_FUNDING_SPENT}
|
import fr.acinq.eclair.channel.{BITCOIN_FUNDING_DEPTHOK, BITCOIN_FUNDING_SPENT}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.JsonAST.{JArray, JString, JValue}
|
import org.json4s.JsonAST.{JArray, JString, JValue}
|
||||||
import org.junit.experimental.categories.Category
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
@Category(Array(classOf[DockerTest]))
|
|
||||||
class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging {
|
class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BitcoindService with ElectrumxService with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
override def beforeAll(): Unit = {
|
override def beforeAll(): Unit = {
|
||||||
|
@ -59,7 +55,6 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
|
||||||
|
|
||||||
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||||
val JString(address) = probe.expectMsgType[JValue]
|
val JString(address) = probe.expectMsgType[JValue]
|
||||||
println(address)
|
|
||||||
|
|
||||||
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
|
||||||
val JString(txid) = probe.expectMsgType[JValue](3000 seconds)
|
val JString(txid) = probe.expectMsgType[JValue](3000 seconds)
|
||||||
|
@ -84,7 +79,6 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
|
||||||
|
|
||||||
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
|
||||||
val JString(address) = probe.expectMsgType[JValue]
|
val JString(address) = probe.expectMsgType[JValue]
|
||||||
println(address)
|
|
||||||
|
|
||||||
probe.send(bitcoincli, BitcoinReq("dumpprivkey", address))
|
probe.send(bitcoincli, BitcoinReq("dumpprivkey", address))
|
||||||
val JString(wif) = probe.expectMsgType[JValue]
|
val JString(wif) = probe.expectMsgType[JValue]
|
||||||
|
|
|
@ -19,7 +19,7 @@ package fr.acinq.eclair.blockchain.electrum
|
||||||
import com.spotify.docker.client.{DefaultDockerClient, DockerClient}
|
import com.spotify.docker.client.{DefaultDockerClient, DockerClient}
|
||||||
import com.whisk.docker.impl.spotify.SpotifyDockerFactory
|
import com.whisk.docker.impl.spotify.SpotifyDockerFactory
|
||||||
import com.whisk.docker.scalatest.DockerTestKit
|
import com.whisk.docker.scalatest.DockerTestKit
|
||||||
import com.whisk.docker.{DockerContainer, DockerFactory, LogLineReceiver}
|
import com.whisk.docker.{DockerContainer, DockerFactory}
|
||||||
import org.scalatest.Suite
|
import org.scalatest.Suite
|
||||||
|
|
||||||
trait ElectrumxService extends DockerTestKit {
|
trait ElectrumxService extends DockerTestKit {
|
||||||
|
@ -30,14 +30,14 @@ trait ElectrumxService extends DockerTestKit {
|
||||||
DockerContainer("lukechilds/electrumx")
|
DockerContainer("lukechilds/electrumx")
|
||||||
.withNetworkMode("host")
|
.withNetworkMode("host")
|
||||||
.withEnv("DAEMON_URL=http://foo:bar@localhost:28332", "COIN=BitcoinSegwit", "NET=regtest")
|
.withEnv("DAEMON_URL=http://foo:bar@localhost:28332", "COIN=BitcoinSegwit", "NET=regtest")
|
||||||
.withLogLineReceiver(LogLineReceiver(true, println))
|
//.withLogLineReceiver(LogLineReceiver(true, println))
|
||||||
} else {
|
} else {
|
||||||
// on windows or oxs, host mode is not available, but from docker 18.03 on host.docker.internal can be used instead
|
// on windows or oxs, host mode is not available, but from docker 18.03 on host.docker.internal can be used instead
|
||||||
// host.docker.internal is not (yet ?) available on linux though
|
// host.docker.internal is not (yet ?) available on linux though
|
||||||
DockerContainer("lukechilds/electrumx")
|
DockerContainer("lukechilds/electrumx")
|
||||||
.withPorts(50001 -> Some(50001))
|
.withPorts(50001 -> Some(50001))
|
||||||
.withEnv("DAEMON_URL=http://foo:bar@host.docker.internal:28332", "COIN=BitcoinSegwit", "NET=regtest", "TCP_PORT=50001")
|
.withEnv("DAEMON_URL=http://foo:bar@host.docker.internal:28332", "COIN=BitcoinSegwit", "NET=regtest", "TCP_PORT=50001")
|
||||||
.withLogLineReceiver(LogLineReceiver(true, println))
|
//.withLogLineReceiver(LogLineReceiver(true, println))
|
||||||
}
|
}
|
||||||
|
|
||||||
override def dockerContainers: List[DockerContainer] = electrumxContainer :: super.dockerContainers
|
override def dockerContainers: List[DockerContainer] = electrumxContainer :: super.dockerContainers
|
||||||
|
|
|
@ -7,13 +7,11 @@ import akka.testkit.{TestKit, TestProbe}
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
import fr.acinq.eclair.blockchain.bitcoind.BitcoindService
|
||||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, JsonRPCError}
|
import fr.acinq.eclair.blockchain.bitcoind.rpc.BasicBitcoinJsonRPCClient
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.DefaultFormats
|
import org.json4s.DefaultFormats
|
||||||
import org.json4s.JsonAST._
|
import org.json4s.JsonAST._
|
||||||
import org.json4s.jackson.JsonMethods
|
import org.json4s.jackson.JsonMethods
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
|
|
||||||
import scala.collection.JavaConversions._
|
import scala.collection.JavaConversions._
|
||||||
|
@ -21,7 +19,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
class BitcoinCoreFeeProviderSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
||||||
|
|
|
@ -19,14 +19,12 @@ package fr.acinq.eclair.blockchain.fee
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import org.json4s.DefaultFormats
|
import org.json4s.DefaultFormats
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 27/01/2017.
|
* Created by PM on 27/01/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class BitgoFeeProviderSpec extends FunSuite {
|
class BitgoFeeProviderSpec extends FunSuite {
|
||||||
|
|
||||||
import BitgoFeeProvider._
|
import BitgoFeeProvider._
|
||||||
|
|
|
@ -18,18 +18,17 @@ package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.DefaultFormats
|
import org.json4s.DefaultFormats
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 27/01/2017.
|
* Created by PM on 27/01/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class EarnDotComFeeProviderSpec extends FunSuite {
|
class EarnDotComFeeProviderSpec extends FunSuite with Logging {
|
||||||
|
|
||||||
import EarnDotComFeeProvider._
|
import EarnDotComFeeProvider._
|
||||||
import org.json4s.jackson.JsonMethods.parse
|
import org.json4s.jackson.JsonMethods.parse
|
||||||
|
@ -74,7 +73,7 @@ class EarnDotComFeeProviderSpec extends FunSuite {
|
||||||
implicit val system = ActorSystem()
|
implicit val system = ActorSystem()
|
||||||
implicit val timeout = Timeout(30 seconds)
|
implicit val timeout = Timeout(30 seconds)
|
||||||
val provider = new EarnDotComFeeProvider()
|
val provider = new EarnDotComFeeProvider()
|
||||||
println("earn.com livenet fees: " + Await.result(provider.getFeerates, 10 seconds))
|
logger.info("earn.com livenet fees: " + Await.result(provider.getFeerates, 10 seconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,13 @@
|
||||||
|
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{Await, Future}
|
import scala.concurrent.{Await, Future}
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class FallbackFeeProviderSpec extends FunSuite {
|
class FallbackFeeProviderSpec extends FunSuite {
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package fr.acinq.eclair.blockchain.fee
|
package fr.acinq.eclair.blockchain.fee
|
||||||
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.{Await, Future}
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{Await, Future}
|
||||||
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SmoothFeeProviderSpec extends FunSuite {
|
class SmoothFeeProviderSpec extends FunSuite {
|
||||||
test("smooth fee rates") {
|
test("smooth fee rates") {
|
||||||
val rates = Array(
|
val rates = Array(
|
||||||
|
|
|
@ -31,9 +31,7 @@ import fr.acinq.eclair.payment._
|
||||||
import fr.acinq.eclair.router.Hop
|
import fr.acinq.eclair.router.Hop
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.Tag
|
import org.scalatest.Tag
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.collection.immutable.Nil
|
import scala.collection.immutable.Nil
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
@ -42,10 +40,10 @@ import scala.util.Random
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Logging {
|
class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Logging {
|
||||||
|
|
||||||
type FixtureParam = Tuple7[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], ActorRef, ActorRef, ActorRef, ActorRef, ActorRef]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], pipe: ActorRef, relayerA: ActorRef, relayerB: ActorRef, paymentHandlerA: ActorRef, paymentHandlerB: ActorRef)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest) = {
|
||||||
val fuzzy = test.tags.contains("fuzzy")
|
val fuzzy = test.tags.contains("fuzzy")
|
||||||
|
@ -82,7 +80,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi
|
||||||
awaitCond(alice.stateName == NORMAL)
|
awaitCond(alice.stateName == NORMAL)
|
||||||
awaitCond(bob.stateName == NORMAL)
|
awaitCond(bob.stateName == NORMAL)
|
||||||
}
|
}
|
||||||
test((alice, bob, pipe, relayerA, relayerB, paymentHandlerA, paymentHandlerB))
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, pipe, relayerA, relayerB, paymentHandlerA, paymentHandlerB)))
|
||||||
}
|
}
|
||||||
|
|
||||||
class SenderActor(channel: TestFSMRef[State, Data, Channel], paymentHandler: ActorRef, latch: CountDownLatch) extends Actor with ActorLogging {
|
class SenderActor(channel: TestFSMRef[State, Data, Channel], paymentHandler: ActorRef, latch: CountDownLatch) extends Actor with ActorLogging {
|
||||||
|
@ -133,65 +131,65 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fuzzy test with only one party sending HTLCs", Tag("fuzzy")) {
|
test("fuzzy test with only one party sending HTLCs", Tag("fuzzy")) { f =>
|
||||||
case (alice, bob, _, _, _, _, paymentHandlerB) =>
|
import f._
|
||||||
val latch = new CountDownLatch(100)
|
val latch = new CountDownLatch(100)
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
||||||
awaitCond(latch.getCount == 0, max = 2 minutes)
|
awaitCond(latch.getCount == 0, max = 2 minutes)
|
||||||
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
|
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
|
||||||
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
|
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fuzzy test with both parties sending HTLCs", Tag("fuzzy")) {
|
test("fuzzy test with both parties sending HTLCs", Tag("fuzzy")) { f =>
|
||||||
case (alice, bob, _, _, _, paymentHandlerA, paymentHandlerB) =>
|
import f._
|
||||||
val latch = new CountDownLatch(100)
|
val latch = new CountDownLatch(100)
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
|
||||||
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
|
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
|
||||||
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
|
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
|
||||||
awaitCond(latch.getCount == 0, max = 2 minutes)
|
awaitCond(latch.getCount == 0, max = 2 minutes)
|
||||||
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
|
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
|
||||||
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
|
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("one party sends lots of htlcs send shutdown") {
|
test("one party sends lots of htlcs send shutdown") { f =>
|
||||||
case (alice, _, _, _, _, _, paymentHandlerB) =>
|
import f._
|
||||||
val latch = new CountDownLatch(20)
|
val latch = new CountDownLatch(20)
|
||||||
val senders = system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
val senders = system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) :: Nil
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) :: Nil
|
||||||
awaitCond(latch.getCount == 0, max = 2 minutes)
|
awaitCond(latch.getCount == 0, max = 2 minutes)
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
awaitCond({
|
awaitCond({
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure]) == "ok"
|
sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure]) == "ok"
|
||||||
}, max = 30 seconds)
|
}, max = 30 seconds)
|
||||||
senders.foreach(_ ! 'stop)
|
senders.foreach(_ ! 'stop)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("both parties send lots of htlcs send shutdown") {
|
test("both parties send lots of htlcs send shutdown") { f =>
|
||||||
case (alice, bob, _, _, _, paymentHandlerA, paymentHandlerB) =>
|
import f._
|
||||||
val latch = new CountDownLatch(30)
|
val latch = new CountDownLatch(30)
|
||||||
val senders = system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
val senders = system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
||||||
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch))) ::
|
||||||
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch))) ::
|
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch))) ::
|
||||||
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch))) :: Nil
|
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch))) :: Nil
|
||||||
awaitCond(latch.getCount == 0, max = 2 minutes)
|
awaitCond(latch.getCount == 0, max = 2 minutes)
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
awaitCond({
|
awaitCond({
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
val resa = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
|
val resa = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
|
||||||
sender.send(bob, CMD_CLOSE(None))
|
sender.send(bob, CMD_CLOSE(None))
|
||||||
val resb = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
|
val resb = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
|
||||||
// we only need that one of them succeeds
|
// we only need that one of them succeeds
|
||||||
resa == "ok" || resb == "ok"
|
resa == "ok" || resb == "ok"
|
||||||
}, max = 30 seconds)
|
}, max = 30 seconds)
|
||||||
senders.foreach(_ ! 'stop)
|
senders.foreach(_ ! 'stop)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,21 +23,19 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _}
|
import fr.acinq.eclair.channel.{WAIT_FOR_FUNDING_INTERNAL, _}
|
||||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel}
|
import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel}
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.{Outcome, Tag}
|
||||||
import org.scalatest.Tag
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = if (test.tags.contains("mainnet")) {
|
val setup = if (test.tags.contains("mainnet")) {
|
||||||
init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash))
|
init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash))
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,112 +50,102 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp
|
||||||
alice2bob.expectMsgType[OpenChannel]
|
alice2bob.expectMsgType[OpenChannel]
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL)
|
awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
|
||||||
test((alice, alice2bob, bob2alice, alice2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv AcceptChannel") { case (alice, _, bob2alice, _) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
bob2alice.expectMsgType[AcceptChannel]
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (invalid max accepted htlcs)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
bob2alice.expectMsgType[AcceptChannel]
|
||||||
// spec says max = 483
|
bob2alice.forward(alice)
|
||||||
val invalidMaxAcceptedHtlcs = 484
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
|
||||||
alice ! accept.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
|
|
||||||
val error = alice2bob.expectMsgType[Error]
|
|
||||||
assert(error === Error(accept.temporaryChannelId, InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
|
|
||||||
awaitCond(alice.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (invalid max accepted htlcs)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
// we don't want their dust limit to be below 546
|
// spec says max = 483
|
||||||
val lowDustLimitSatoshis = 545
|
val invalidMaxAcceptedHtlcs = 484
|
||||||
alice ! accept.copy(dustLimitSatoshis = lowDustLimitSatoshis)
|
alice ! accept.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
|
||||||
val error = alice2bob.expectMsgType[Error]
|
val error = alice2bob.expectMsgType[Error]
|
||||||
assert(error === Error(accept.temporaryChannelId, DustLimitTooSmall(accept.temporaryChannelId, lowDustLimitSatoshis, Channel.MIN_DUSTLIMIT).getMessage.getBytes("UTF-8")))
|
assert(error === Error(accept.temporaryChannelId, InvalidMaxAcceptedHtlcs(accept.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (to_self_delay too high)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
val delayTooHigh = 10000
|
// we don't want their dust limit to be below 546
|
||||||
alice ! accept.copy(toSelfDelay = delayTooHigh)
|
val lowDustLimitSatoshis = 545
|
||||||
val error = alice2bob.expectMsgType[Error]
|
alice ! accept.copy(dustLimitSatoshis = lowDustLimitSatoshis)
|
||||||
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
|
val error = alice2bob.expectMsgType[Error]
|
||||||
awaitCond(alice.stateName == CLOSED)
|
assert(error === Error(accept.temporaryChannelId, DustLimitTooSmall(accept.temporaryChannelId, lowDustLimitSatoshis, Channel.MIN_DUSTLIMIT).getMessage.getBytes("UTF-8")))
|
||||||
}
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (reserve too high)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (to_self_delay too high)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
// 30% is huge, recommended ratio is 1%
|
val delayTooHigh = 10000
|
||||||
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
|
alice ! accept.copy(toSelfDelay = delayTooHigh)
|
||||||
alice ! accept.copy(channelReserveSatoshis = reserveTooHigh)
|
val error = alice2bob.expectMsgType[Error]
|
||||||
val error = alice2bob.expectMsgType[Error]
|
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
|
||||||
assert(error === Error(accept.temporaryChannelId, ChannelReserveTooHigh(accept.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
|
awaitCond(alice.stateName == CLOSED)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (reserve below dust limit)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (reserve too high)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
val reserveTooSmall = accept.dustLimitSatoshis - 1
|
// 30% is huge, recommended ratio is 1%
|
||||||
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
|
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
|
||||||
val error = alice2bob.expectMsgType[Error]
|
alice ! accept.copy(channelReserveSatoshis = reserveTooHigh)
|
||||||
assert(error === Error(accept.temporaryChannelId, DustLimitTooLarge(accept.temporaryChannelId, accept.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
|
val error = alice2bob.expectMsgType[Error]
|
||||||
awaitCond(alice.stateName == CLOSED)
|
assert(error === Error(accept.temporaryChannelId, ChannelReserveTooHigh(accept.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
|
||||||
}
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (reserve below our dust limit)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (reserve below dust limit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
|
val reserveTooSmall = accept.dustLimitSatoshis - 1
|
||||||
val reserveTooSmall = open.dustLimitSatoshis - 1
|
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
|
||||||
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
|
val error = alice2bob.expectMsgType[Error]
|
||||||
val error = alice2bob.expectMsgType[Error]
|
assert(error === Error(accept.temporaryChannelId, DustLimitTooLarge(accept.temporaryChannelId, accept.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
|
||||||
assert(error === Error(accept.temporaryChannelId, ChannelReserveBelowOurDustLimit(accept.temporaryChannelId, reserveTooSmall, open.dustLimitSatoshis).getMessage.getBytes("UTF-8")))
|
awaitCond(alice.stateName == CLOSED)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv AcceptChannel (dust limit above our reserve)") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (reserve below our dust limit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val accept = bob2alice.expectMsgType[AcceptChannel]
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
|
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
|
||||||
val dustTooBig = open.channelReserveSatoshis + 1
|
val reserveTooSmall = open.dustLimitSatoshis - 1
|
||||||
alice ! accept.copy(dustLimitSatoshis = dustTooBig)
|
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
|
||||||
val error = alice2bob.expectMsgType[Error]
|
val error = alice2bob.expectMsgType[Error]
|
||||||
assert(error === Error(accept.temporaryChannelId, DustLimitAboveOurChannelReserve(accept.temporaryChannelId, dustTooBig, open.channelReserveSatoshis).getMessage.getBytes("UTF-8")))
|
assert(error === Error(accept.temporaryChannelId, ChannelReserveBelowOurDustLimit(accept.temporaryChannelId, reserveTooSmall, open.dustLimitSatoshis).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv AcceptChannel (dust limit above our reserve)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
bob ! Error("00" * 32, "oops".getBytes)
|
val accept = bob2alice.expectMsgType[AcceptChannel]
|
||||||
awaitCond(bob.stateName == CLOSED)
|
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
|
||||||
}
|
val dustTooBig = open.channelReserveSatoshis + 1
|
||||||
|
alice ! accept.copy(dustLimitSatoshis = dustTooBig)
|
||||||
|
val error = alice2bob.expectMsgType[Error]
|
||||||
|
assert(error === Error(accept.temporaryChannelId, DustLimitAboveOurChannelReserve(accept.temporaryChannelId, dustTooBig, open.channelReserveSatoshis).getMessage.getBytes("UTF-8")))
|
||||||
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! CMD_CLOSE(None)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("recv CMD_CLOSE") { f =>
|
||||||
|
import f._
|
||||||
|
alice ! CMD_CLOSE(None)
|
||||||
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,22 +21,21 @@ import fr.acinq.bitcoin.Block
|
||||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, Init, OpenChannel}
|
import fr.acinq.eclair.wire.{Error, Init, OpenChannel}
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
||||||
|
@ -45,161 +44,147 @@ class WaitForOpenChannelStateSpec extends TestkitBaseClass with StateTestsHelper
|
||||||
alice ! INPUT_INIT_FUNDER("00" * 32, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty)
|
alice ! INPUT_INIT_FUNDER("00" * 32, TestConstants.fundingSatoshis, TestConstants.pushMsat, TestConstants.feeratePerKw, TestConstants.feeratePerKw, Alice.channelParams, alice2bob.ref, bobInit, ChannelFlags.Empty)
|
||||||
bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams, bob2alice.ref, aliceInit)
|
bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams, bob2alice.ref, aliceInit)
|
||||||
awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL)
|
awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain)))
|
||||||
test((bob, alice2bob, bob2alice, bob2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv OpenChannel") { case (bob, alice2bob, _, _) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
alice2bob.expectMsgType[OpenChannel]
|
|
||||||
alice2bob.forward(bob)
|
|
||||||
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (invalid chain)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
alice2bob.expectMsgType[OpenChannel]
|
||||||
// using livenet genesis block
|
alice2bob.forward(bob)
|
||||||
val livenetChainHash = Block.LivenetGenesisBlock.hash
|
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
|
||||||
bob ! open.copy(chainHash = livenetChainHash)
|
|
||||||
val error = bob2alice.expectMsgType[Error]
|
|
||||||
assert(error === Error(open.temporaryChannelId, new InvalidChainHash(open.temporaryChannelId, Block.RegtestGenesisBlock.hash, livenetChainHash).getMessage.getBytes("UTF-8")))
|
|
||||||
awaitCond(bob.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (funding too low)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (invalid chain)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val lowFundingMsat = 100
|
// using livenet genesis block
|
||||||
bob ! open.copy(fundingSatoshis = lowFundingMsat)
|
val livenetChainHash = Block.LivenetGenesisBlock.hash
|
||||||
val error = bob2alice.expectMsgType[Error]
|
bob ! open.copy(chainHash = livenetChainHash)
|
||||||
assert(error === Error(open.temporaryChannelId, new InvalidFundingAmount(open.temporaryChannelId, lowFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
|
val error = bob2alice.expectMsgType[Error]
|
||||||
awaitCond(bob.stateName == CLOSED)
|
assert(error === Error(open.temporaryChannelId, InvalidChainHash(open.temporaryChannelId, Block.RegtestGenesisBlock.hash, livenetChainHash).getMessage.getBytes("UTF-8")))
|
||||||
}
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (funding too high)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (funding too low)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val highFundingMsat = 100000000
|
val lowFundingMsat = 100
|
||||||
bob ! open.copy(fundingSatoshis = highFundingMsat)
|
bob ! open.copy(fundingSatoshis = lowFundingMsat)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
assert(error === Error(open.temporaryChannelId, new InvalidFundingAmount(open.temporaryChannelId, highFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, InvalidFundingAmount(open.temporaryChannelId, lowFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (invalid max accepted htlcs)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (funding too high)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1
|
val highFundingMsat = 100000000
|
||||||
bob ! open.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
|
bob ! open.copy(fundingSatoshis = highFundingMsat)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
assert(error === Error(open.temporaryChannelId, new InvalidMaxAcceptedHtlcs(open.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, InvalidFundingAmount(open.temporaryChannelId, highFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (invalid push_msat)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (invalid max accepted htlcs)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val invalidPushMsat = 100000000000L
|
val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1
|
||||||
bob ! open.copy(pushMsat = invalidPushMsat)
|
bob ! open.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
assert(error === Error(open.temporaryChannelId, new InvalidPushAmount(open.temporaryChannelId, invalidPushMsat, 1000 * open.fundingSatoshis).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, InvalidMaxAcceptedHtlcs(open.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (to_self_delay too high)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (invalid push_msat)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val delayTooHigh = 10000
|
val invalidPushMsat = 100000000000L
|
||||||
bob ! open.copy(toSelfDelay = delayTooHigh)
|
bob ! open.copy(pushMsat = invalidPushMsat)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, InvalidPushAmount(open.temporaryChannelId, invalidPushMsat, 1000 * open.fundingSatoshis).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (reserve too high)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (to_self_delay too high)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
// 30% is huge, recommended ratio is 1%
|
val delayTooHigh = 10000
|
||||||
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
|
bob ! open.copy(toSelfDelay = delayTooHigh)
|
||||||
bob ! open.copy(channelReserveSatoshis = reserveTooHigh)
|
val error = bob2alice.expectMsgType[Error]
|
||||||
val error = bob2alice.expectMsgType[Error]
|
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
|
||||||
assert(error === Error(open.temporaryChannelId, new ChannelReserveTooHigh(open.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
|
awaitCond(bob.stateName == CLOSED)
|
||||||
awaitCond(bob.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (fee too low, but still valid)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (reserve too high)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
// set a very small fee
|
// 30% is huge, recommended ratio is 1%
|
||||||
val tinyFee = 253
|
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
|
||||||
bob ! open.copy(feeratePerKw = tinyFee)
|
bob ! open.copy(channelReserveSatoshis = reserveTooHigh)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
// we check that the error uses the temporary channel id
|
assert(error === Error(open.temporaryChannelId, ChannelReserveTooHigh(open.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
|
||||||
assert(error === Error(open.temporaryChannelId, "local/remote feerates are too different: remoteFeeratePerKw=253 localFeeratePerKw=10000".getBytes("UTF-8")))
|
awaitCond(bob.stateName == CLOSED)
|
||||||
awaitCond(bob.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (fee below absolute valid minimum)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (fee too low, but still valid)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
// set a very small fee
|
// set a very small fee
|
||||||
val tinyFee = 252
|
val tinyFee = 253
|
||||||
bob ! open.copy(feeratePerKw = tinyFee)
|
bob ! open.copy(feeratePerKw = tinyFee)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
// we check that the error uses the temporary channel id
|
// we check that the error uses the temporary channel id
|
||||||
assert(error === Error(open.temporaryChannelId, "remote fee rate is too small: remoteFeeratePerKw=252".getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, "local/remote feerates are too different: remoteFeeratePerKw=253 localFeeratePerKw=10000".getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("recv OpenChannel (fee below absolute valid minimum)") { f =>
|
||||||
|
import f._
|
||||||
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
|
// set a very small fee
|
||||||
|
val tinyFee = 252
|
||||||
|
bob ! open.copy(feeratePerKw = tinyFee)
|
||||||
|
val error = bob2alice.expectMsgType[Error]
|
||||||
|
// we check that the error uses the temporary channel id
|
||||||
|
assert(error === Error(open.temporaryChannelId, "remote fee rate is too small: remoteFeeratePerKw=252".getBytes("UTF-8")))
|
||||||
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
test("recv OpenChannel (reserve below dust)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (reserve below dust)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val reserveTooSmall = open.dustLimitSatoshis - 1
|
val reserveTooSmall = open.dustLimitSatoshis - 1
|
||||||
bob ! open.copy(channelReserveSatoshis = reserveTooSmall)
|
bob ! open.copy(channelReserveSatoshis = reserveTooSmall)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
// we check that the error uses the temporary channel id
|
// we check that the error uses the temporary channel id
|
||||||
assert(error === Error(open.temporaryChannelId, DustLimitTooLarge(open.temporaryChannelId, open.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, DustLimitTooLarge(open.temporaryChannelId, open.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv OpenChannel (toLocal + toRemote below reserve)") { case (bob, alice2bob, bob2alice, _) =>
|
test("recv OpenChannel (toLocal + toRemote below reserve)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val open = alice2bob.expectMsgType[OpenChannel]
|
val open = alice2bob.expectMsgType[OpenChannel]
|
||||||
val fundingSatoshis = open.channelReserveSatoshis + 499
|
val fundingSatoshis = open.channelReserveSatoshis + 499
|
||||||
val pushMsat = 500 * 1000
|
val pushMsat = 500 * 1000
|
||||||
bob ! open.copy(fundingSatoshis = fundingSatoshis, pushMsat = pushMsat)
|
bob ! open.copy(fundingSatoshis = fundingSatoshis, pushMsat = pushMsat)
|
||||||
val error = bob2alice.expectMsgType[Error]
|
val error = bob2alice.expectMsgType[Error]
|
||||||
// we check that the error uses the temporary channel id
|
// we check that the error uses the temporary channel id
|
||||||
assert(error === Error(open.temporaryChannelId, ChannelReserveNotMet(open.temporaryChannelId, 500 * 1000, (open.channelReserveSatoshis - 1) * 1000, open.channelReserveSatoshis).getMessage.getBytes("UTF-8")))
|
assert(error === Error(open.temporaryChannelId, ChannelReserveNotMet(open.temporaryChannelId, 500 * 1000, (open.channelReserveSatoshis - 1) * 1000, open.channelReserveSatoshis).getMessage.getBytes("UTF-8")))
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (bob, _, _, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
bob ! Error("00" * 32, "oops".getBytes())
|
bob ! Error("00" * 32, "oops".getBytes())
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (bob, _, _, _) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
bob ! CMD_CLOSE(None)
|
bob ! CMD_CLOSE(None)
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,20 +22,19 @@ import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
||||||
|
@ -48,22 +47,20 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State
|
||||||
bob2alice.expectMsgType[AcceptChannel]
|
bob2alice.expectMsgType[AcceptChannel]
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
|
||||||
test((alice, alice2bob, bob2alice, alice2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv Error") { case (bob, alice2bob, bob2alice, _) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
bob ! Error("00" * 32, "oops".getBytes)
|
|
||||||
awaitCond(bob.stateName == CLOSED)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! CMD_CLOSE(None)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("recv CMD_CLOSE") { f =>
|
||||||
|
import f._
|
||||||
|
alice ! CMD_CLOSE(None)
|
||||||
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,21 +24,19 @@ import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.transactions.Transactions
|
import fr.acinq.eclair.transactions.Transactions
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.{Outcome, Tag}
|
||||||
import org.scalatest.Tag
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, bob2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val (fundingSatoshis, pushMsat) = if (test.tags.contains("funder_below_reserve")) {
|
val (fundingSatoshis, pushMsat) = if (test.tags.contains("funder_below_reserve")) {
|
||||||
|
@ -56,46 +54,42 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel
|
||||||
bob2alice.expectMsgType[AcceptChannel]
|
bob2alice.expectMsgType[AcceptChannel]
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
|
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain)))
|
||||||
test((bob, alice2bob, bob2alice, bob2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv FundingCreated") { case (bob, alice2bob, bob2alice, bob2blockchain) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
alice2bob.expectMsgType[FundingCreated]
|
|
||||||
alice2bob.forward(bob)
|
|
||||||
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
|
||||||
bob2alice.expectMsgType[FundingSigned]
|
|
||||||
bob2blockchain.expectMsgType[WatchSpent]
|
|
||||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { case (bob, alice2bob, bob2alice, _) =>
|
test("recv FundingCreated") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val fees = Transactions.commitWeight * TestConstants.feeratePerKw / 1000
|
alice2bob.expectMsgType[FundingCreated]
|
||||||
val reserve = Bob.channelParams.channelReserveSatoshis
|
alice2bob.forward(bob)
|
||||||
val missing = 100 - fees - reserve
|
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
||||||
val fundingCreated = alice2bob.expectMsgType[FundingCreated]
|
bob2alice.expectMsgType[FundingSigned]
|
||||||
alice2bob.forward(bob)
|
bob2blockchain.expectMsgType[WatchSpent]
|
||||||
val error = bob2alice.expectMsgType[Error]
|
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||||
assert(error === Error(fundingCreated.temporaryChannelId, s"can't pay the fee: missingSatoshis=${-1 * missing} reserveSatoshis=$reserve feesSatoshis=$fees".getBytes("UTF-8")))
|
|
||||||
awaitCond(bob.stateName == CLOSED)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (bob, _, _, _) =>
|
test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
bob ! Error("00" * 32, "oops".getBytes)
|
val fees = Transactions.commitWeight * TestConstants.feeratePerKw / 1000
|
||||||
awaitCond(bob.stateName == CLOSED)
|
val reserve = Bob.channelParams.channelReserveSatoshis
|
||||||
}
|
val missing = 100 - fees - reserve
|
||||||
|
val fundingCreated = alice2bob.expectMsgType[FundingCreated]
|
||||||
|
alice2bob.forward(bob)
|
||||||
|
val error = bob2alice.expectMsgType[Error]
|
||||||
|
assert(error === Error(fundingCreated.temporaryChannelId, s"can't pay the fee: missingSatoshis=${-1 * missing} reserveSatoshis=$reserve feesSatoshis=$fees".getBytes("UTF-8")))
|
||||||
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (bob, _, _, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
bob ! CMD_CLOSE(None)
|
bob ! Error("00" * 32, "oops".getBytes)
|
||||||
awaitCond(bob.stateName == CLOSED)
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("recv CMD_CLOSE") { f =>
|
||||||
|
import f._
|
||||||
|
bob ! CMD_CLOSE(None)
|
||||||
|
awaitCond(bob.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,20 +24,19 @@ import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
|
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple4[TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
||||||
|
@ -52,41 +51,37 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp
|
||||||
alice2bob.expectMsgType[FundingCreated]
|
alice2bob.expectMsgType[FundingCreated]
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
|
||||||
test((alice, alice2bob, bob2alice, alice2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv FundingSigned with valid signature") { case (alice, _, bob2alice, alice2blockchain) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
bob2alice.expectMsgType[FundingSigned]
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
|
||||||
alice2blockchain.expectMsgType[WatchSpent]
|
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv FundingSigned with invalid signature") { case (alice, alice2bob, _, _) =>
|
test("recv FundingSigned with valid signature") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// sending an invalid sig
|
bob2alice.expectMsgType[FundingSigned]
|
||||||
alice ! FundingSigned("00" * 32, BinaryData("00" * 64))
|
bob2alice.forward(alice)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
||||||
alice2bob.expectMsgType[Error]
|
alice2blockchain.expectMsgType[WatchSpent]
|
||||||
}
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, _, _, _) =>
|
test("recv FundingSigned with invalid signature") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! CMD_CLOSE(None)
|
// sending an invalid sig
|
||||||
awaitCond(alice.stateName == CLOSED)
|
alice ! FundingSigned("00" * 32, BinaryData("00" * 64))
|
||||||
}
|
awaitCond(alice.stateName == CLOSED)
|
||||||
|
alice2bob.expectMsgType[Error]
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_FORCECLOSE") { case (alice, _, _, _) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! CMD_FORCECLOSE
|
alice ! CMD_CLOSE(None)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("recv CMD_FORCECLOSE") { f =>
|
||||||
|
import f._
|
||||||
|
alice ! CMD_FORCECLOSE
|
||||||
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,20 +25,19 @@ import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel}
|
import fr.acinq.eclair.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel}
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple5[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
||||||
|
@ -57,95 +56,86 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH
|
||||||
alice2blockchain.expectMsgType[WatchSpent]
|
alice2blockchain.expectMsgType[WatchSpent]
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain)))
|
||||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv FundingLocked") { case (alice, bob, _, bob2alice, _) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
// make bob send a FundingLocked msg
|
|
||||||
bob ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
|
||||||
val msg = bob2alice.expectMsgType[FundingLocked]
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred == Some(msg))
|
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_DEPTHOK") { case (alice, _, alice2bob, _, alice2blockchain) =>
|
test("recv FundingLocked") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
// make bob send a FundingLocked msg
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
|
bob ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||||
alice2blockchain.expectMsgType[WatchLost]
|
val msg = bob2alice.expectMsgType[FundingLocked]
|
||||||
alice2bob.expectMsgType[FundingLocked]
|
bob2alice.forward(alice)
|
||||||
}
|
awaitCond(alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].deferred.contains(msg))
|
||||||
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_PUBLISH_FAILED") { case (alice, _, alice2bob, _, _) =>
|
test("recv BITCOIN_FUNDING_DEPTHOK") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! BITCOIN_FUNDING_PUBLISH_FAILED
|
alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
|
||||||
alice2bob.expectMsgType[Error]
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
alice2blockchain.expectMsgType[WatchLost]
|
||||||
}
|
alice2bob.expectMsgType[FundingLocked]
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_TIMEOUT") { case (alice, _, alice2bob, _, _) =>
|
test("recv BITCOIN_FUNDING_PUBLISH_FAILED") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice ! BITCOIN_FUNDING_TIMEOUT
|
alice ! BITCOIN_FUNDING_PUBLISH_FAILED
|
||||||
alice2bob.expectMsgType[Error]
|
alice2bob.expectMsgType[Error]
|
||||||
awaitCond(alice.stateName == ERR_FUNDING_TIMEOUT)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, _, _, alice2blockchain) =>
|
test("recv BITCOIN_FUNDING_TIMEOUT") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// bob publishes his commitment tx
|
alice ! BITCOIN_FUNDING_TIMEOUT
|
||||||
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
alice2bob.expectMsgType[Error]
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
awaitCond(alice.stateName == ERR_FUNDING_TIMEOUT)
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
awaitCond(alice.stateName == CLOSING)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain) =>
|
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
// bob publishes his commitment tx
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice2bob.expectMsgType[Error]
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
}
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (alice, _, _, _, alice2blockchain) =>
|
test("recv BITCOIN_FUNDING_SPENT (other commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
||||||
awaitCond(alice.stateName == CLOSING)
|
alice2bob.expectMsgType[Error]
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main-delayed
|
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, _, _, _, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
sender.expectMsg(Failure(CannotCloseInThisState(channelId(alice), WAIT_FOR_FUNDING_CONFIRMED)))
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main-delayed
|
||||||
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_FORCECLOSE") { case (alice, _, _, _, alice2blockchain) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
val sender = TestProbe()
|
||||||
alice ! CMD_FORCECLOSE
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
awaitCond(alice.stateName == CLOSING)
|
sender.expectMsg(Failure(CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_FUNDING_CONFIRMED)))
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
}
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main-delayed
|
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
test("recv CMD_FORCECLOSE") { f =>
|
||||||
}
|
import f._
|
||||||
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
|
alice ! CMD_FORCECLOSE
|
||||||
|
awaitCond(alice.stateName == CLOSING)
|
||||||
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main-delayed
|
||||||
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package fr.acinq.eclair.channel.states.c
|
package fr.acinq.eclair.channel.states.c
|
||||||
|
|
||||||
import akka.actor.Status
|
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.Status.Failure
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import fr.acinq.bitcoin.Transaction
|
import fr.acinq.bitcoin.Transaction
|
||||||
|
@ -26,20 +25,19 @@ import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, router: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
|
||||||
|
@ -66,68 +64,62 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp
|
||||||
alice2bob.expectMsgType[FundingLocked]
|
alice2bob.expectMsgType[FundingLocked]
|
||||||
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
|
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
|
||||||
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED)
|
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED)
|
||||||
}
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, router)))
|
||||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, router))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv FundingLocked") { case (alice, _, alice2bob, bob2alice, alice2blockchain, router) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
bob2alice.expectMsgType[FundingLocked]
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
awaitCond(alice.stateName == NORMAL)
|
|
||||||
bob2alice.expectNoMsg(200 millis)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, router) =>
|
test("recv FundingLocked") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// bob publishes his commitment tx
|
bob2alice.expectMsgType[FundingLocked]
|
||||||
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
bob2alice.forward(alice)
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
awaitCond(alice.stateName == NORMAL)
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
bob2alice.expectNoMsg(200 millis)
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
awaitCond(alice.stateName == CLOSING)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
|
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
// bob publishes his commitment tx
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice2bob.expectMsgType[Error]
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (alice, _, _, _, alice2blockchain, _) =>
|
test("recv BITCOIN_FUNDING_SPENT (other commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
|
||||||
awaitCond(alice.stateName == CLOSING)
|
alice2bob.expectMsgType[Error]
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, _, _, _, _, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
sender.expectMsg(Failure(CannotCloseInThisState(channelId(alice), WAIT_FOR_FUNDING_LOCKED)))
|
awaitCond(alice.stateName == CLOSING)
|
||||||
}
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_FORCECLOSE") { case (alice, _, _, _, alice2blockchain, _) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
val sender = TestProbe()
|
||||||
alice ! CMD_FORCECLOSE
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
awaitCond(alice.stateName == CLOSING)
|
sender.expectMsg(Failure(CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_FUNDING_LOCKED)))
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
}
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
test("recv CMD_FORCECLOSE") { f =>
|
||||||
}
|
import f._
|
||||||
|
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
|
alice ! CMD_FORCECLOSE
|
||||||
|
awaitCond(alice.stateName == CLOSING)
|
||||||
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,36 +23,37 @@ import fr.acinq.eclair.blockchain.{PublishAsap, WatchEventSpent}
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.channel.{Data, State, _}
|
import fr.acinq.eclair.channel.{Data, State, _}
|
||||||
import fr.acinq.eclair.crypto.Sphinx
|
import fr.acinq.eclair.crypto.Sphinx
|
||||||
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple7[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayer: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
within(30 seconds) {
|
within(30 seconds) {
|
||||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||||
awaitCond(alice.stateName == NORMAL)
|
awaitCond(alice.stateName == NORMAL)
|
||||||
awaitCond(bob.stateName == NORMAL)
|
awaitCond(bob.stateName == NORMAL)
|
||||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer))
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test checks the case where a disconnection occurs *right before* the counterparty receives a new sig
|
* This test checks the case where a disconnection occurs *right before* the counterparty receives a new sig
|
||||||
*/
|
*/
|
||||||
test("re-send update+sig after first commitment") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
test("re-send update+sig after first commitment") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
sender.send(alice, CMD_ADD_HTLC(1000000, BinaryData("42" * 32), 400144))
|
sender.send(alice, CMD_ADD_HTLC(1000000, BinaryData("42" * 32), 400144))
|
||||||
|
@ -74,7 +75,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
|
|
||||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||||
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +129,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
/**
|
/**
|
||||||
* This test checks the case where a disconnection occurs *right after* the counterparty receives a new sig
|
* This test checks the case where a disconnection occurs *right after* the counterparty receives a new sig
|
||||||
*/
|
*/
|
||||||
test("re-send lost revocation") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
test("re-send lost revocation") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
sender.send(alice, CMD_ADD_HTLC(1000000, BinaryData("42" * 32), 400144))
|
sender.send(alice, CMD_ADD_HTLC(1000000, BinaryData("42" * 32), 400144))
|
||||||
|
@ -157,7 +159,7 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
val bobCommitments = bob.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
val aliceCommitments = alice.stateData.asInstanceOf[HasCommitments].commitments
|
||||||
|
|
||||||
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
val bobCurrentPerCommitmentPoint = TestConstants.Bob.keyManager.commitmentPoint(bobCommitments.localParams.channelKeyPath, bobCommitments.localCommit.index)
|
||||||
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
val aliceCurrentPerCommitmentPoint = TestConstants.Alice.keyManager.commitmentPoint(aliceCommitments.localParams.channelKeyPath, aliceCommitments.localCommit.index)
|
||||||
|
|
||||||
// a didn't receive the sig
|
// a didn't receive the sig
|
||||||
|
@ -188,7 +190,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("discover that we have a revoked commitment") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
test("discover that we have a revoked commitment") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice)
|
val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice)
|
||||||
|
@ -241,7 +244,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("discover that they have a more recent commit than the one we know") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) =>
|
test("discover that they have a more recent commit than the one we know") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we start by storing the current state
|
// we start by storing the current state
|
||||||
|
@ -292,7 +296,8 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("counterparty lies about having a more recent commitment") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
|
test("counterparty lies about having a more recent commitment") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we simulate a disconnection
|
// we simulate a disconnection
|
||||||
|
@ -318,4 +323,45 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
assert(new String(error.data) === InvalidRevokedCommitProof(channelId(alice), 0, 42, ba_reestablish_forged.yourLastPerCommitmentSecret.get).getMessage)
|
assert(new String(error.data) === InvalidRevokedCommitProof(channelId(alice), 0, 42, ba_reestablish_forged.yourLastPerCommitmentSecret.get).getMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("change relay fee while offline") { f =>
|
||||||
|
import f._
|
||||||
|
val sender = TestProbe()
|
||||||
|
|
||||||
|
// we simulate a disconnection
|
||||||
|
sender.send(alice, INPUT_DISCONNECTED)
|
||||||
|
sender.send(bob, INPUT_DISCONNECTED)
|
||||||
|
awaitCond(alice.stateName == OFFLINE)
|
||||||
|
awaitCond(bob.stateName == OFFLINE)
|
||||||
|
|
||||||
|
// alice and bob announce that their channel is OFFLINE
|
||||||
|
assert(Announcements.isEnabled(relayer.expectMsgType[LocalChannelUpdate].channelUpdate.channelFlags) == false)
|
||||||
|
assert(Announcements.isEnabled(relayer.expectMsgType[LocalChannelUpdate].channelUpdate.channelFlags) == false)
|
||||||
|
|
||||||
|
// we make alice update here relay fee
|
||||||
|
sender.send(alice, CMD_UPDATE_RELAY_FEE(4200, 123456))
|
||||||
|
sender.expectMsg("ok")
|
||||||
|
|
||||||
|
// alice doesn't broadcast the new channel_update yet
|
||||||
|
relayer.expectNoMsg(300 millis)
|
||||||
|
|
||||||
|
// then we reconnect them
|
||||||
|
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref))
|
||||||
|
sender.send(bob, INPUT_RECONNECTED(bob2alice.ref))
|
||||||
|
|
||||||
|
// peers exchange channel_reestablish messages
|
||||||
|
alice2bob.expectMsgType[ChannelReestablish]
|
||||||
|
bob2alice.expectMsgType[ChannelReestablish]
|
||||||
|
// note that we don't forward the channel_reestablish so that only alice reaches NORMAL state, it facilitates the test below
|
||||||
|
bob2alice.forward(alice)
|
||||||
|
|
||||||
|
// then alice reaches NORMAL state, and during the transition she broadcasts the channel_update
|
||||||
|
val channelUpdate = relayer.expectMsgType[LocalChannelUpdate](10 seconds).channelUpdate
|
||||||
|
assert(channelUpdate.feeBaseMsat === 4200)
|
||||||
|
assert(channelUpdate.feeProportionalMillionths === 123456)
|
||||||
|
assert(Announcements.isEnabled(channelUpdate.channelFlags) == true)
|
||||||
|
|
||||||
|
// no more messages
|
||||||
|
relayer.expectNoMsg(300 millis)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@
|
||||||
package fr.acinq.eclair.channel.states.g
|
package fr.acinq.eclair.channel.states.g
|
||||||
|
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.Status.Failure
|
||||||
|
import akka.event.LoggingAdapter
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import fr.acinq.bitcoin.Satoshi
|
import fr.acinq.bitcoin.Satoshi
|
||||||
import fr.acinq.eclair.TestConstants.Bob
|
import fr.acinq.eclair.TestConstants.Bob
|
||||||
|
@ -26,11 +27,9 @@ import fr.acinq.eclair.channel.Helpers.Closing
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
import fr.acinq.eclair.channel.{Data, State, _}
|
import fr.acinq.eclair.channel.{Data, State, _}
|
||||||
import fr.acinq.eclair.payment.Local
|
import fr.acinq.eclair.payment.Local
|
||||||
import fr.acinq.eclair.wire.{ClosingSigned, CommitSig, Error, Shutdown}
|
import fr.acinq.eclair.wire.{ClosingSigned, Error, Shutdown}
|
||||||
import fr.acinq.eclair.{Globals, TestkitBaseClass}
|
import fr.acinq.eclair.{Globals, TestkitBaseClass}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.{Outcome, Tag}
|
||||||
import org.scalatest.Tag
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
@ -38,12 +37,12 @@ import scala.util.Success
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
within(30 seconds) {
|
within(30 seconds) {
|
||||||
|
@ -62,161 +61,147 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
|
||||||
if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000))
|
if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4316)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(5000))
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
awaitCond(bob.stateName == NEGOTIATING)
|
awaitCond(bob.stateName == NEGOTIATING)
|
||||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain))
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _) =>
|
test("recv CMD_ADD_HTLC") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
alice2bob.expectMsgType[ClosingSigned]
|
alice2bob.expectMsgType[ClosingSigned]
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
||||||
sender.send(alice, add)
|
sender.send(alice, add)
|
||||||
val error = ChannelUnavailable(channelId(alice))
|
val error = ChannelUnavailable(channelId(alice))
|
||||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
|
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
|
||||||
alice2bob.expectNoMsg(200 millis)
|
alice2bob.expectNoMsg(200 millis)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
|
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// alice initiates the negotiation
|
// alice initiates the negotiation
|
||||||
val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned]
|
val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned]
|
||||||
|
alice2bob.forward(bob)
|
||||||
|
// bob answers with a counter proposition
|
||||||
|
val bobCloseSig1 = bob2alice.expectMsgType[ClosingSigned]
|
||||||
|
assert(aliceCloseSig1.feeSatoshis > bobCloseSig1.feeSatoshis)
|
||||||
|
// actual test starts here
|
||||||
|
val initialState = alice.stateData.asInstanceOf[DATA_NEGOTIATING]
|
||||||
|
bob2alice.forward(alice)
|
||||||
|
val aliceCloseSig2 = alice2bob.expectMsgType[ClosingSigned]
|
||||||
|
// BOLT 2: If the receiver [doesn't agree with the fee] it SHOULD propose a value strictly between the received fee-satoshis and its previously-sent fee-satoshis
|
||||||
|
assert(aliceCloseSig2.feeSatoshis < aliceCloseSig1.feeSatoshis && aliceCloseSig2.feeSatoshis > bobCloseSig1.feeSatoshis)
|
||||||
|
awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.map(_.localClosingSigned) == initialState.closingTxProposed.last.map(_.localClosingSigned) :+ aliceCloseSig2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def testFeeConverge(f: FixtureParam) = {
|
||||||
|
import f._
|
||||||
|
var aliceCloseFee, bobCloseFee = 0L
|
||||||
|
do {
|
||||||
|
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
// bob answers with a counter proposition
|
if (!bob2blockchain.msgAvailable) {
|
||||||
val bobCloseSig1 = bob2alice.expectMsgType[ClosingSigned]
|
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
assert(aliceCloseSig1.feeSatoshis > bobCloseSig1.feeSatoshis)
|
bob2alice.forward(alice)
|
||||||
// actual test starts here
|
}
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_NEGOTIATING]
|
} while (!alice2blockchain.msgAvailable && !bob2blockchain.msgAvailable)
|
||||||
bob2alice.forward(alice)
|
|
||||||
val aliceCloseSig2 = alice2bob.expectMsgType[ClosingSigned]
|
|
||||||
// BOLT 2: If the receiver [doesn't agree with the fee] it SHOULD propose a value strictly between the received fee-satoshis and its previously-sent fee-satoshis
|
|
||||||
assert(aliceCloseSig2.feeSatoshis < aliceCloseSig1.feeSatoshis && aliceCloseSig2.feeSatoshis > bobCloseSig1.feeSatoshis)
|
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_NEGOTIATING].closingTxProposed.last.map(_.localClosingSigned) == initialState.closingTxProposed.last.map(_.localClosingSigned) :+ aliceCloseSig2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def testFeeConverge(alice: TestFSMRef[State, Data, Channel],
|
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 1)") { f =>
|
||||||
bob: TestFSMRef[State, Data, Channel],
|
testFeeConverge(f)
|
||||||
alice2bob: TestProbe,
|
}
|
||||||
bob2alice: TestProbe,
|
|
||||||
alice2blockchain: TestProbe,
|
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 2)", Tag("fee2")) { f =>
|
||||||
bob2blockchain: TestProbe) = {
|
testFeeConverge(f)
|
||||||
within(30 seconds) {
|
}
|
||||||
var aliceCloseFee, bobCloseFee = 0L
|
|
||||||
do {
|
test("recv ClosingSigned (fee too high)") { f =>
|
||||||
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
|
import f._
|
||||||
alice2bob.forward(bob)
|
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
|
||||||
if (!bob2blockchain.msgAvailable) {
|
val sender = TestProbe()
|
||||||
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
|
sender.send(bob, aliceCloseSig.copy(feeSatoshis = 99000)) // sig doesn't matter, it is checked later
|
||||||
|
val error = bob2alice.expectMsgType[Error]
|
||||||
|
assert(new String(error.data).startsWith("invalid close fee: fee_satoshis=99000"))
|
||||||
|
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
bob2blockchain.expectMsgType[PublishAsap]
|
||||||
|
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||||
|
}
|
||||||
|
|
||||||
|
test("recv ClosingSigned (invalid sig)") { f =>
|
||||||
|
import f._
|
||||||
|
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
|
||||||
|
val sender = TestProbe()
|
||||||
|
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
|
sender.send(bob, aliceCloseSig.copy(signature = "00" * 64))
|
||||||
|
val error = bob2alice.expectMsgType[Error]
|
||||||
|
assert(new String(error.data).startsWith("invalid close signature"))
|
||||||
|
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||||
|
bob2blockchain.expectMsgType[PublishAsap]
|
||||||
|
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||||
|
}
|
||||||
|
|
||||||
|
test("recv BITCOIN_FUNDING_SPENT (counterparty's mutual close)") { f =>
|
||||||
|
import f._
|
||||||
|
var aliceCloseFee, bobCloseFee = 0L
|
||||||
|
do {
|
||||||
|
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
|
alice2bob.forward(bob)
|
||||||
|
if (!bob2blockchain.msgAvailable) {
|
||||||
|
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
|
if (aliceCloseFee != bobCloseFee) {
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
}
|
}
|
||||||
} while (!alice2blockchain.msgAvailable && !bob2blockchain.msgAvailable)
|
}
|
||||||
}
|
} while (!alice2blockchain.msgAvailable && !bob2blockchain.msgAvailable)
|
||||||
|
// 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 funding tx has been spent
|
||||||
|
// actual test starts here
|
||||||
|
assert(alice.stateName == NEGOTIATING)
|
||||||
|
val mutualCloseTx = bob2blockchain.expectMsgType[PublishAsap].tx
|
||||||
|
assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(mutualCloseTx))
|
||||||
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
||||||
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
|
alice2blockchain.expectNoMsg(100 millis)
|
||||||
|
assert(alice.stateName == CLOSING)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 1)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
test("recv BITCOIN_FUNDING_SPENT (an older mutual close)") { f =>
|
||||||
testFeeConverge(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
import f._
|
||||||
}
|
val aliceClose1 = alice2bob.expectMsgType[ClosingSigned]
|
||||||
|
alice2bob.forward(bob)
|
||||||
|
val bobClose1 = bob2alice.expectMsgType[ClosingSigned]
|
||||||
|
bob2alice.forward(alice)
|
||||||
|
val aliceClose2 = alice2bob.expectMsgType[ClosingSigned]
|
||||||
|
assert(aliceClose2.feeSatoshis != bobClose1.feeSatoshis)
|
||||||
|
// at this point alice and bob have not yet converged on closing fees, but bob decides to publish a mutual close with one of the previous sigs
|
||||||
|
val d = bob.stateData.asInstanceOf[DATA_NEGOTIATING]
|
||||||
|
implicit val log: LoggingAdapter = bob.underlyingActor.implicitLog
|
||||||
|
val Success(bobClosingTx) = Closing.checkClosingSignature(Bob.keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(aliceClose1.feeSatoshis), aliceClose1.signature)
|
||||||
|
|
||||||
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 2)", Tag("fee2")) { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobClosingTx)
|
||||||
testFeeConverge(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
}
|
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||||
|
alice2blockchain.expectNoMsg(100 millis)
|
||||||
test("recv ClosingSigned (fee too high)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
assert(alice.stateName == CLOSING)
|
||||||
within(30 seconds) {
|
|
||||||
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
|
|
||||||
val sender = TestProbe()
|
|
||||||
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
|
||||||
sender.send(bob, aliceCloseSig.copy(feeSatoshis = 99000)) // sig doesn't matter, it is checked later
|
|
||||||
val error = bob2alice.expectMsgType[Error]
|
|
||||||
assert(new String(error.data).startsWith("invalid close fee: fee_satoshis=99000"))
|
|
||||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
|
||||||
bob2blockchain.expectMsgType[PublishAsap]
|
|
||||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv ClosingSigned (invalid sig)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
val aliceCloseSig = alice2bob.expectMsgType[ClosingSigned]
|
|
||||||
val sender = TestProbe()
|
|
||||||
val tx = bob.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
|
||||||
sender.send(bob, aliceCloseSig.copy(signature = "00" * 64))
|
|
||||||
val error = bob2alice.expectMsgType[Error]
|
|
||||||
assert(new String(error.data).startsWith("invalid close signature"))
|
|
||||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
|
||||||
bob2blockchain.expectMsgType[PublishAsap]
|
|
||||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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[ClosingSigned].feeSatoshis
|
|
||||||
alice2bob.forward(bob)
|
|
||||||
if (!bob2blockchain.msgAvailable) {
|
|
||||||
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
|
||||||
if (aliceCloseFee != bobCloseFee) {
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (!alice2blockchain.msgAvailable && !bob2blockchain.msgAvailable)
|
|
||||||
// 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 funding tx has been spent
|
|
||||||
// actual test starts here
|
|
||||||
assert(alice.stateName == NEGOTIATING)
|
|
||||||
val mutualCloseTx = bob2blockchain.expectMsgType[PublishAsap].tx
|
|
||||||
assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(mutualCloseTx))
|
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
alice2blockchain.expectNoMsg(100 millis)
|
|
||||||
assert(alice.stateName == CLOSING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (an older mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain) =>
|
|
||||||
within(30 seconds) {
|
|
||||||
val aliceClose1 = alice2bob.expectMsgType[ClosingSigned]
|
|
||||||
alice2bob.forward(bob)
|
|
||||||
val bobClose1 = bob2alice.expectMsgType[ClosingSigned]
|
|
||||||
bob2alice.forward(alice)
|
|
||||||
val aliceClose2 = alice2bob.expectMsgType[ClosingSigned]
|
|
||||||
assert(aliceClose2.feeSatoshis != bobClose1.feeSatoshis)
|
|
||||||
// at this point alice and bob have not yet converged on closing fees, but bob decides to publish a mutual close with one of the previous sigs
|
|
||||||
val d = bob.stateData.asInstanceOf[DATA_NEGOTIATING]
|
|
||||||
implicit val log = bob.underlyingActor.implicitLog
|
|
||||||
val Success(bobClosingTx) = Closing.checkClosingSignature(Bob.keyManager, d.commitments, d.localShutdown.scriptPubKey, d.remoteShutdown.scriptPubKey, Satoshi(aliceClose1.feeSatoshis), aliceClose1.signature)
|
|
||||||
|
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobClosingTx)
|
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
|
||||||
alice2blockchain.expectNoMsg(100 millis)
|
|
||||||
assert(alice.stateName == CLOSING)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, _, _, _, _, _) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
|
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv Error") { case (alice, _, _, _, alice2blockchain, _) =>
|
test("recv Error") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes())
|
alice ! Error("00" * 32, "oops".getBytes())
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import akka.actor.Status
|
||||||
import akka.actor.Status.Failure
|
import akka.actor.Status.Failure
|
||||||
import akka.testkit.{TestFSMRef, TestProbe}
|
import akka.testkit.{TestFSMRef, TestProbe}
|
||||||
import fr.acinq.bitcoin.{OutPoint, ScriptFlags, Transaction, TxIn}
|
import fr.acinq.bitcoin.{OutPoint, ScriptFlags, Transaction, TxIn}
|
||||||
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
|
|
||||||
import fr.acinq.eclair.blockchain._
|
import fr.acinq.eclair.blockchain._
|
||||||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||||
|
@ -28,24 +27,24 @@ import fr.acinq.eclair.channel.{Data, State, _}
|
||||||
import fr.acinq.eclair.payment.{CommandBuffer, ForwardAdd, ForwardFulfill, Local}
|
import fr.acinq.eclair.payment.{CommandBuffer, ForwardAdd, ForwardFulfill, Local}
|
||||||
import fr.acinq.eclair.transactions.Scripts
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import org.junit.runner.RunWith
|
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
|
||||||
import org.scalatest.junit.JUnitRunner
|
import org.scalatest.Outcome
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 05/07/2016.
|
* Created by PM on 05/07/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
|
|
||||||
type FixtureParam = Tuple8[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe, TestProbe, List[PublishableTxs]]
|
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayer: TestProbe, bobCommitTxes: List[PublishableTxs])
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
val setup = init()
|
val setup = init()
|
||||||
import setup._
|
import setup._
|
||||||
|
|
||||||
val bobCommitTxes = within(30 seconds) {
|
within(30 seconds) {
|
||||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||||
val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
|
val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
|
||||||
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
|
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
|
||||||
|
@ -71,9 +70,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
// - revoked commit
|
// - revoked commit
|
||||||
// and we want to be able to test the different scenarii.
|
// and we want to be able to test the different scenarii.
|
||||||
// Hence the NORMAL->CLOSING transition will occur in the individual tests.
|
// Hence the NORMAL->CLOSING transition will occur in the individual tests.
|
||||||
bobCommitTxes
|
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer, bobCommitTxes)))
|
||||||
}
|
}
|
||||||
test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer, bobCommitTxes))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def mutualClose(alice: TestFSMRef[State, Data, Channel],
|
def mutualClose(alice: TestFSMRef[State, Data, Channel],
|
||||||
|
@ -106,438 +104,420 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||||
// both nodes are now in CLOSING state with a mutual close tx pending for confirmation
|
// both nodes are now in CLOSING state with a mutual close tx pending for confirmation
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_ADD_HTLC") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv CMD_ADD_HTLC") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
|
||||||
sender.send(alice, add)
|
sender.send(alice, add)
|
||||||
val error = ChannelUnavailable(channelId(alice))
|
val error = ChannelUnavailable(channelId(alice))
|
||||||
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
|
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
|
||||||
alice2bob.expectNoMsg(200 millis)
|
alice2bob.expectNoMsg(200 millis)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_FULFILL_HTLC (unexisting htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv CMD_FULFILL_HTLC (unexisting htlc)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(alice, CMD_FULFILL_HTLC(42, "42" * 32))
|
sender.send(alice, CMD_FULFILL_HTLC(42, "42" * 32))
|
||||||
sender.expectMsg(Failure(UnknownHtlcId(channelId(alice), 42)))
|
sender.expectMsg(Failure(UnknownHtlcId(channelId(alice), 42)))
|
||||||
|
|
||||||
// NB: nominal case is tested in IntegrationSpec
|
// NB: nominal case is tested in IntegrationSpec
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (mutual close before converging)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv BITCOIN_FUNDING_SPENT (mutual close before converging)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
// alice initiates a closing
|
// alice initiates a closing
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
alice2bob.expectMsgType[Shutdown]
|
alice2bob.expectMsgType[Shutdown]
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
bob2alice.expectMsgType[Shutdown]
|
bob2alice.expectMsgType[Shutdown]
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
// agreeing on a closing fee
|
// agreeing on a closing fee
|
||||||
val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
|
val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
Globals.feeratesPerKw.set(FeeratesPerKw.single(100))
|
Globals.feeratesPerKw.set(FeeratesPerKw.single(100))
|
||||||
alice2bob.forward(bob)
|
alice2bob.forward(bob)
|
||||||
val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
// they don't converge yet, but alice has a publishable commit tx now
|
// they don't converge yet, but alice has a publishable commit tx now
|
||||||
assert(aliceCloseFee != bobCloseFee)
|
assert(aliceCloseFee != bobCloseFee)
|
||||||
val Some(mutualCloseTx) = alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt
|
val Some(mutualCloseTx) = alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt
|
||||||
// let's make alice publish this closing tx
|
// let's make alice publish this closing tx
|
||||||
alice ! Error("00" * 32, "")
|
alice ! Error("00" * 32, "")
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
assert(mutualCloseTx === alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last)
|
assert(mutualCloseTx === alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv BITCOIN_TX_CONFIRMED (mutual close)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last
|
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (local commit)") { case (alice, _, _, _, alice2blockchain, _, _, _) =>
|
test("recv BITCOIN_FUNDING_SPENT (local commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// an error occurs and alice publishes her commit tx
|
// an error occurs and alice publishes her commit tx
|
||||||
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
|
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
|
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
assert(initialState.localCommitPublished.isDefined)
|
assert(initialState.localCommitPublished.isDefined)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
// we are notified afterwards from our watcher about the tx that we just published
|
// we are notified afterwards from our watcher about the tx that we just published
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, aliceCommitTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, aliceCommitTx)
|
||||||
assert(alice.stateData == initialState) // this was a no-op
|
assert(alice.stateData == initialState) // this was a no-op
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_OUTPUT_SPENT") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer, _) =>
|
test("recv BITCOIN_OUTPUT_SPENT") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
// alice sends an htlc to bob
|
// alice sends an htlc to bob
|
||||||
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(alice, bob, alice2bob, bob2alice)
|
crossSign(alice, bob, alice2bob, bob2alice)
|
||||||
relayer.expectMsgType[ForwardAdd]
|
relayer.expectMsgType[ForwardAdd]
|
||||||
// an error occurs and alice publishes her commit tx
|
// an error occurs and alice publishes her commit tx
|
||||||
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
|
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // main-delayed-output
|
alice2blockchain.expectMsgType[PublishAsap] // main-delayed-output
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // htlc-timeout
|
alice2blockchain.expectMsgType[PublishAsap] // htlc-timeout
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-delayed-output
|
alice2blockchain.expectMsgType[PublishAsap] // claim-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx))
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx))
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // main-delayed-output
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // main-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // claim-delayed-output
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // claim-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
|
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
assert(initialState.localCommitPublished.isDefined)
|
assert(initialState.localCommitPublished.isDefined)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
relayer.expectMsgType[LocalChannelDown]
|
relayer.expectMsgType[LocalChannelDown]
|
||||||
|
|
||||||
// scenario 1: bob claims the htlc output from the commit tx using its preimage
|
// scenario 1: bob claims the htlc output from the commit tx using its preimage
|
||||||
val claimHtlcSuccessFromCommitTx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessClaimHtlcSuccessFromCommitTx("11" * 70, ra1, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
val claimHtlcSuccessFromCommitTx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessClaimHtlcSuccessFromCommitTx("11" * 70, ra1, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
||||||
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessFromCommitTx)
|
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessFromCommitTx)
|
||||||
assert(relayer.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
|
assert(relayer.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
|
||||||
|
|
||||||
// scenario 2: bob claims the htlc output from his own commit tx using its preimage (let's assume both parties had published their commitment tx)
|
// scenario 2: bob claims the htlc output from his own commit tx using its preimage (let's assume both parties had published their commitment tx)
|
||||||
val claimHtlcSuccessTx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessHtlcSuccess("11" * 70, "22" * 70, ra1, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
val claimHtlcSuccessTx = Transaction(version = 0, txIn = TxIn(outPoint = OutPoint("22" * 32, 0), signatureScript = "", sequence = 0, witness = Scripts.witnessHtlcSuccess("11" * 70, "22" * 70, ra1, "33" * 130)) :: Nil, txOut = Nil, lockTime = 0)
|
||||||
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessTx)
|
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessTx)
|
||||||
assert(relayer.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
|
assert(relayer.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
|
||||||
|
|
||||||
assert(alice.stateData == initialState) // this was a no-op
|
assert(alice.stateData == initialState) // this was a no-op
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (local commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _, _) =>
|
test("recv BITCOIN_TX_CONFIRMED (local commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val listener = TestProbe()
|
val listener = TestProbe()
|
||||||
system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed])
|
system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed])
|
||||||
// alice sends an htlc to bob
|
// alice sends an htlc to bob
|
||||||
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(alice, bob, alice2bob, bob2alice)
|
crossSign(alice, bob, alice2bob, bob2alice)
|
||||||
// an error occurs and alice publishes her commit tx
|
// an error occurs and alice publishes her commit tx
|
||||||
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
|
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
|
||||||
val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output
|
val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output
|
||||||
val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout
|
val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout
|
||||||
val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output
|
val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx))
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx))
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // main-delayed-output
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // main-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // claim-delayed-output
|
assert(alice2blockchain.expectMsgType[WatchConfirmed].event.isInstanceOf[BITCOIN_TX_CONFIRMED]) // claim-delayed-output
|
||||||
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
|
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
assert(initialState.localCommitPublished.isDefined)
|
assert(initialState.localCommitPublished.isDefined)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0)
|
||||||
assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay)
|
assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimDelayedTx), 202, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimDelayedTx), 202, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (local commit with htlcs only signed by local)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer, _) =>
|
test("recv BITCOIN_TX_CONFIRMED (local commit with htlcs only signed by local)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
// alice sends an htlc
|
// alice sends an htlc
|
||||||
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
|
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
|
||||||
// and signs it (but bob doesn't sign it)
|
// and signs it (but bob doesn't sign it)
|
||||||
sender.send(alice, CMD_SIGN)
|
sender.send(alice, CMD_SIGN)
|
||||||
sender.expectMsg("ok")
|
sender.expectMsg("ok")
|
||||||
alice2bob.expectMsgType[CommitSig]
|
alice2bob.expectMsgType[CommitSig]
|
||||||
// note that bob doesn't receive the new sig!
|
// note that bob doesn't receive the new sig!
|
||||||
// then we make alice unilaterally close the channel
|
// then we make alice unilaterally close the channel
|
||||||
alice ! Error("00" * 32, "oops".getBytes)
|
alice ! Error("00" * 32, "oops".getBytes)
|
||||||
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
|
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
assert(aliceData.localCommitPublished.isDefined)
|
assert(aliceData.localCommitPublished.isDefined)
|
||||||
relayer.expectMsgType[LocalChannelDown]
|
relayer.expectMsgType[LocalChannelDown]
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
// when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain
|
// when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 0, 0)
|
||||||
// so she fails it
|
// so she fails it
|
||||||
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
|
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
|
||||||
relayer.expectMsg(Status.Failure(AddHtlcFailed(aliceData.channelId, htlc.paymentHash, HtlcOverridenByLocalCommit(aliceData.channelId), origin, None, None)))
|
relayer.expectMsg(Status.Failure(AddHtlcFailed(aliceData.channelId, htlc.paymentHash, HtlcOverridenByLocalCommit(aliceData.channelId), origin, None, None)))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (remote commit with htlcs only signed by local in next remote commit)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, relayer, _) =>
|
test("recv BITCOIN_TX_CONFIRMED (remote commit with htlcs only signed by local in next remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
// alice sends an htlc
|
// alice sends an htlc
|
||||||
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
|
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
|
||||||
// and signs it (but bob doesn't sign it)
|
// and signs it (but bob doesn't sign it)
|
||||||
sender.send(alice, CMD_SIGN)
|
sender.send(alice, CMD_SIGN)
|
||||||
sender.expectMsg("ok")
|
sender.expectMsg("ok")
|
||||||
alice2bob.expectMsgType[CommitSig]
|
alice2bob.expectMsgType[CommitSig]
|
||||||
// then we make alice believe bob unilaterally close the channel
|
// then we make alice believe bob unilaterally close the channel
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||||
awaitCond(alice.stateName == CLOSING)
|
awaitCond(alice.stateName == CLOSING)
|
||||||
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
assert(aliceData.remoteCommitPublished.isDefined)
|
assert(aliceData.remoteCommitPublished.isDefined)
|
||||||
relayer.expectMsgType[LocalChannelDown]
|
relayer.expectMsgType[LocalChannelDown]
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
// when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain
|
// when the commit tx is signed, alice knows that the htlc she sent right before the unilateral close will never reach the chain
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
||||||
// so she fails it
|
// so she fails it
|
||||||
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
|
val origin = alice.stateData.asInstanceOf[DATA_CLOSING].commitments.originChannels(htlc.id)
|
||||||
relayer.expectMsg(Status.Failure(AddHtlcFailed(aliceData.channelId, htlc.paymentHash, HtlcOverridenByLocalCommit(aliceData.channelId), origin, None, None)))
|
relayer.expectMsg(Status.Failure(AddHtlcFailed(aliceData.channelId, htlc.paymentHash, HtlcOverridenByLocalCommit(aliceData.channelId), origin, None, None)))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
||||||
val bobCommitTx = bobCommitTxes.last.commitTx.tx
|
val bobCommitTx = bobCommitTxes.last.commitTx.tx
|
||||||
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||||
|
|
||||||
alice2blockchain.expectMsgType[PublishAsap]
|
alice2blockchain.expectMsgType[PublishAsap]
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||||
|
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
|
||||||
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
|
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_TX_CONFIRMED (remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
|
||||||
val bobCommitTx = bobCommitTxes.last.commitTx.tx
|
val bobCommitTx = bobCommitTxes.last.commitTx.tx
|
||||||
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
assert(bobCommitTx.txOut.size == 2) // two main outputs
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||||
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
|
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
|
||||||
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
|
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (future remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_TX_CONFIRMED (future remote commit)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val oldStateData = alice.stateData
|
val oldStateData = alice.stateData
|
||||||
val (ra1, htlca1) = addHtlc(25000000, alice, bob, alice2bob, bob2alice)
|
val (ra1, htlca1) = addHtlc(25000000, alice, bob, alice2bob, bob2alice)
|
||||||
crossSign(alice, bob, alice2bob, bob2alice)
|
crossSign(alice, bob, alice2bob, bob2alice)
|
||||||
fulfillHtlc(htlca1.id, ra1, bob, alice, bob2alice, alice2bob)
|
fulfillHtlc(htlca1.id, ra1, bob, alice, bob2alice, alice2bob)
|
||||||
crossSign(bob, alice, bob2alice, alice2bob)
|
crossSign(bob, alice, bob2alice, alice2bob)
|
||||||
// we simulate a disconnection
|
// we simulate a disconnection
|
||||||
sender.send(alice, INPUT_DISCONNECTED)
|
sender.send(alice, INPUT_DISCONNECTED)
|
||||||
sender.send(bob, INPUT_DISCONNECTED)
|
sender.send(bob, INPUT_DISCONNECTED)
|
||||||
awaitCond(alice.stateName == OFFLINE)
|
awaitCond(alice.stateName == OFFLINE)
|
||||||
awaitCond(bob.stateName == OFFLINE)
|
awaitCond(bob.stateName == OFFLINE)
|
||||||
// then we manually replace alice's state with an older one
|
// then we manually replace alice's state with an older one
|
||||||
alice.setState(OFFLINE, oldStateData)
|
alice.setState(OFFLINE, oldStateData)
|
||||||
// then we reconnect them
|
// then we reconnect them
|
||||||
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref))
|
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref))
|
||||||
sender.send(bob, INPUT_RECONNECTED(bob2alice.ref))
|
sender.send(bob, INPUT_RECONNECTED(bob2alice.ref))
|
||||||
// peers exchange channel_reestablish messages
|
// peers exchange channel_reestablish messages
|
||||||
alice2bob.expectMsgType[ChannelReestablish]
|
alice2bob.expectMsgType[ChannelReestablish]
|
||||||
bob2alice.expectMsgType[ChannelReestablish]
|
bob2alice.expectMsgType[ChannelReestablish]
|
||||||
// alice then realizes it has an old state...
|
// alice then realizes it has an old state...
|
||||||
bob2alice.forward(alice)
|
bob2alice.forward(alice)
|
||||||
// ... and ask bob to publish its current commitment
|
// ... and ask bob to publish its current commitment
|
||||||
val error = alice2bob.expectMsgType[Error]
|
val error = alice2bob.expectMsgType[Error]
|
||||||
assert(new String(error.data) === PleasePublishYourCommitment(channelId(alice)).getMessage)
|
assert(new String(error.data) === PleasePublishYourCommitment(channelId(alice)).getMessage)
|
||||||
// alice now waits for bob to publish its commitment
|
// alice now waits for bob to publish its commitment
|
||||||
awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT)
|
awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT)
|
||||||
// bob is nice and publishes its commitment
|
// bob is nice and publishes its commitment
|
||||||
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
|
||||||
// alice is able to claim its main output
|
// alice is able to claim its main output
|
||||||
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
|
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
|
||||||
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_FUNDING_SPENT (one revoked tx)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
// bob publishes one of his revoked txes
|
// bob publishes one of his revoked txes
|
||||||
val bobRevokedTx = bobCommitTxes.head.commitTx.tx
|
val bobRevokedTx = bobCommitTxes.head.commitTx.tx
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
|
||||||
|
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
|
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1)
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].copy(revokedCommitPublished = Nil) == initialState)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].copy(revokedCommitPublished = Nil) == initialState)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_FUNDING_SPENT (multiple revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_FUNDING_SPENT (multiple revoked tx)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
// bob publishes multiple revoked txes (last one isn't revoked)
|
// bob publishes multiple revoked txes (last one isn't revoked)
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(0).commitTx.tx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(0).commitTx.tx)
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
|
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(1).commitTx.tx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(1).commitTx.tx)
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
|
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(2).commitTx.tx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(2).commitTx.tx)
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
alice2blockchain.expectMsgType[PublishAsap] // claim-main
|
||||||
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
|
|
||||||
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 3)
|
assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 3)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_OUTPUT_SPENT (one revoked tx, counterparty published HtlcSuccess tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_OUTPUT_SPENT (one revoked tx, counterparty published HtlcSuccess tx)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
// bob publishes one of his revoked txes
|
// bob publishes one of his revoked txes
|
||||||
val bobRevokedTx = bobCommitTxes.head
|
val bobRevokedTx = bobCommitTxes.head
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
|
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
|
||||||
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
|
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
|
||||||
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
|
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
|
||||||
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx) // we published this
|
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx) // we published this
|
||||||
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-penalty
|
||||||
// val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx
|
// val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx
|
||||||
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, bobHtlcSuccessTx) // bob published his HtlcSuccess tx
|
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, bobHtlcSuccessTx) // bob published his HtlcSuccess tx
|
||||||
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-success
|
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-success
|
||||||
// val claimHtlcDelayedPenaltyTxs = alice2blockchain.expectMsgType[PublishAsap].tx // we publish a tx spending the output of bob's HtlcSuccess tx
|
// val claimHtlcDelayedPenaltyTxs = alice2blockchain.expectMsgType[PublishAsap].tx // we publish a tx spending the output of bob's HtlcSuccess tx
|
||||||
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobHtlcSuccessTx), 0, 0) // bob won
|
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobHtlcSuccessTx), 0, 0) // bob won
|
||||||
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimHtlcDelayedPenaltyTxs), 0, 0) // bob won
|
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimHtlcDelayedPenaltyTxs), 0, 0) // bob won
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv BITCOIN_TX_CONFIRMED (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
|
test("recv BITCOIN_TX_CONFIRMED (one revoked tx)") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
// bob publishes one of his revoked txes
|
// bob publishes one of his revoked txes
|
||||||
val bobRevokedTx = bobCommitTxes.head
|
val bobRevokedTx = bobCommitTxes.head
|
||||||
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
|
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
|
||||||
// alice publishes and watches the penalty tx
|
// alice publishes and watches the penalty tx
|
||||||
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
|
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
|
||||||
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
|
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
|
||||||
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
|
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
|
||||||
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
|
||||||
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
|
||||||
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
|
||||||
alice2blockchain.expectNoMsg(1 second)
|
alice2blockchain.expectNoMsg(1 second)
|
||||||
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
|
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
|
||||||
|
|
||||||
// actual test starts here
|
// actual test starts here
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
|
||||||
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
|
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
|
||||||
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx)
|
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx)
|
||||||
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcPenaltyTx), 0, 0)
|
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcPenaltyTx), 0, 0)
|
||||||
awaitCond(alice.stateName == CLOSED)
|
awaitCond(alice.stateName == CLOSED)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv ChannelReestablish") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv ChannelReestablish") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(alice, ChannelReestablish(channelId(bob), 42, 42))
|
sender.send(alice, ChannelReestablish(channelId(bob), 42, 42))
|
||||||
val error = alice2bob.expectMsgType[Error]
|
val error = alice2bob.expectMsgType[Error]
|
||||||
assert(new String(error.data) === FundingTxSpent(channelId(alice), initialState.spendingTxes.head).getMessage)
|
assert(new String(error.data) === FundingTxSpent(channelId(alice), initialState.spendingTxes.head).getMessage)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("recv CMD_CLOSE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
|
test("recv CMD_CLOSE") { f =>
|
||||||
within(30 seconds) {
|
import f._
|
||||||
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(alice, CMD_CLOSE(None))
|
sender.send(alice, CMD_CLOSE(None))
|
||||||
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
|
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,13 @@
|
||||||
|
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.spongycastle.util.encoders.Hex
|
import org.spongycastle.util.encoders.Hex
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fabrice on 22/06/17.
|
* Created by fabrice on 22/06/17.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class BitStreamSpec extends FunSuite {
|
class BitStreamSpec extends FunSuite {
|
||||||
|
|
||||||
import BitStream._
|
import BitStream._
|
||||||
|
|
|
@ -17,12 +17,10 @@
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ChaCha20Poly1305Spec extends FunSuite {
|
class ChaCha20Poly1305Spec extends FunSuite {
|
||||||
test("Poly1305") {
|
test("Poly1305") {
|
||||||
// from https://tools.ietf.org/html/rfc7539 ch 2.5.2
|
// from https://tools.ietf.org/html/rfc7539 ch 2.5.2
|
||||||
|
|
|
@ -18,11 +18,9 @@ package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.bitcoin.Crypto.{Point, Scalar}
|
import fr.acinq.bitcoin.Crypto.{Point, Scalar}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class GeneratorsSpec extends FunSuite {
|
class GeneratorsSpec extends FunSuite {
|
||||||
val base_secret: Scalar = BinaryData("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
val base_secret: Scalar = BinaryData("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
|
||||||
val per_commitment_secret: Scalar = BinaryData("0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
|
val per_commitment_secret: Scalar = BinaryData("0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")
|
||||||
|
|
|
@ -19,11 +19,9 @@ package fr.acinq.eclair.crypto
|
||||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.bitcoin.DeterministicWallet.KeyPath
|
import fr.acinq.bitcoin.DeterministicWallet.KeyPath
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block}
|
import fr.acinq.bitcoin.{BinaryData, Block}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class LocalKeyManagerSpec extends FunSuite {
|
class LocalKeyManagerSpec extends FunSuite {
|
||||||
test("generate the same node id from the same seed") {
|
test("generate the same node id from the same seed") {
|
||||||
// if this test breaks it means that we will generate a different node id from
|
// if this test breaks it means that we will generate a different node id from
|
||||||
|
|
|
@ -100,7 +100,6 @@ object NoiseDemo extends App {
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case message: BinaryData =>
|
case message: BinaryData =>
|
||||||
println(s"received ${new String(message)}")
|
|
||||||
sender ! BinaryData("response to ".getBytes() ++ message)
|
sender ! BinaryData("response to ".getBytes() ++ message)
|
||||||
count = count + 1
|
count = count + 1
|
||||||
if (count == 5) context stop self
|
if (count == 5) context stop self
|
||||||
|
|
|
@ -18,12 +18,10 @@ package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.crypto.Noise._
|
import fr.acinq.eclair.crypto.Noise._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.spongycastle.crypto.ec.CustomNamedCurves
|
import org.spongycastle.crypto.ec.CustomNamedCurves
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class NoiseSpec extends FunSuite {
|
class NoiseSpec extends FunSuite {
|
||||||
|
|
||||||
import Noise._
|
import Noise._
|
||||||
|
|
|
@ -17,11 +17,9 @@
|
||||||
package fr.acinq.eclair.crypto
|
package fr.acinq.eclair.crypto
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Hash}
|
import fr.acinq.bitcoin.{BinaryData, Hash}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ShaChainSpec extends FunSuite {
|
class ShaChainSpec extends FunSuite {
|
||||||
val expected: List[BinaryData] = List(
|
val expected: List[BinaryData] = List(
|
||||||
"f85a0f7f237ed2139855703db4264de380ec731f64a3d832c22a5f2ef615f1d5",
|
"f85a0f7f237ed2139855703db4264de380ec731f64a3d832c22a5f2ef615f1d5",
|
||||||
|
|
|
@ -19,17 +19,14 @@ package fr.acinq.eclair.crypto
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import scodec.bits.BitVector
|
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fabrice on 10/01/17.
|
* Created by fabrice on 10/01/17.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SphinxSpec extends FunSuite {
|
class SphinxSpec extends FunSuite {
|
||||||
|
|
||||||
import Sphinx._
|
import Sphinx._
|
||||||
|
|
|
@ -25,8 +25,6 @@ import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.crypto.Noise.{Chacha20Poly1305CipherFunctions, CipherState}
|
import fr.acinq.eclair.crypto.Noise.{Chacha20Poly1305CipherFunctions, CipherState}
|
||||||
import fr.acinq.eclair.crypto.TransportHandler.{Encryptor, ExtendedCipherState, Listener}
|
import fr.acinq.eclair.crypto.TransportHandler.{Encryptor, ExtendedCipherState, Listener}
|
||||||
import fr.acinq.eclair.wire.LightningMessageCodecs
|
import fr.acinq.eclair.wire.LightningMessageCodecs
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
@ -34,7 +32,7 @@ import scodec.codecs._
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BeforeAndAfterAll {
|
class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BeforeAndAfterAll {
|
||||||
|
|
||||||
import TransportHandlerSpec._
|
import TransportHandlerSpec._
|
||||||
|
|
|
@ -27,14 +27,12 @@ import fr.acinq.eclair.transactions.Transactions.CommitTx
|
||||||
import fr.acinq.eclair.transactions._
|
import fr.acinq.eclair.transactions._
|
||||||
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
|
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
|
||||||
import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey}
|
import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fabrice on 07/02/17.
|
* Created by fabrice on 07/02/17.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ChannelStateSpec extends FunSuite {
|
class ChannelStateSpec extends FunSuite {
|
||||||
|
|
||||||
import ChannelStateSpec._
|
import ChannelStateSpec._
|
||||||
|
@ -109,7 +107,7 @@ object ChannelStateSpec {
|
||||||
remoteNextCommitInfo = Right(randomKey.publicKey),
|
remoteNextCommitInfo = Right(randomKey.publicKey),
|
||||||
commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32)
|
commitInput = commitmentInput, remotePerCommitmentSecrets = ShaChain.init, channelId = "00" * 32)
|
||||||
|
|
||||||
val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53)
|
val channelUpdate = Announcements.makeChannelUpdate("11" * 32, randomKey, randomKey.publicKey, ShortChannelId(142553), 42, 15, 575, 53, Channel.MAX_FUNDING_SATOSHIS * 1000L)
|
||||||
|
|
||||||
val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None)
|
val normal = DATA_NORMAL(commitments, ShortChannelId(42), true, None, channelUpdate, None, None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,11 @@ import fr.acinq.eclair.channel.NetworkFeePaid
|
||||||
import fr.acinq.eclair.db.sqlite.SqliteAuditDb
|
import fr.acinq.eclair.db.sqlite.SqliteAuditDb
|
||||||
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
|
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
|
||||||
import fr.acinq.eclair.{randomBytes, randomKey}
|
import fr.acinq.eclair.{randomBytes, randomKey}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.compat.Platform
|
import scala.compat.Platform
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqliteAuditDbSpec extends FunSuite {
|
class SqliteAuditDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
@ -59,7 +57,7 @@ class SqliteAuditDbSpec extends FunSuite {
|
||||||
db.add(e6)
|
db.add(e6)
|
||||||
|
|
||||||
assert(db.listSent(from = 0L, to = Long.MaxValue).toSet === Set(e1, e5, e6))
|
assert(db.listSent(from = 0L, to = Long.MaxValue).toSet === Set(e1, e5, e6))
|
||||||
assert(db.listSent(from = 100000L, to = Platform.currentTime).toList === List(e1))
|
assert(db.listSent(from = 100000L, to = Platform.currentTime + 1).toList === List(e1))
|
||||||
assert(db.listReceived(from = 0L, to = Long.MaxValue).toList === List(e2))
|
assert(db.listReceived(from = 0L, to = Long.MaxValue).toList === List(e2))
|
||||||
assert(db.listRelayed(from = 0L, to = Long.MaxValue).toList === List(e3))
|
assert(db.listRelayed(from = 0L, to = Long.MaxValue).toList === List(e3))
|
||||||
assert(db.listNetworkFees(from = 0L, to = Long.MaxValue).size === 1)
|
assert(db.listNetworkFees(from = 0L, to = Long.MaxValue).size === 1)
|
||||||
|
|
|
@ -20,12 +20,10 @@ import java.sql.DriverManager
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.db.sqlite.{SqliteChannelsDb, SqlitePendingRelayDb}
|
import fr.acinq.eclair.db.sqlite.{SqliteChannelsDb, SqlitePendingRelayDb}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.sqlite.SQLiteException
|
import org.sqlite.SQLiteException
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqliteChannelsDbSpec extends FunSuite {
|
class SqliteChannelsDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
@ -56,15 +54,15 @@ class SqliteChannelsDbSpec extends FunSuite {
|
||||||
db.addOrUpdateChannel(channel)
|
db.addOrUpdateChannel(channel)
|
||||||
assert(db.listChannels() === List(channel))
|
assert(db.listChannels() === List(channel))
|
||||||
|
|
||||||
assert(db.listHtlcHtlcInfos(channel.channelId, commitNumber).toList == Nil)
|
assert(db.listHtlcInfos(channel.channelId, commitNumber).toList == Nil)
|
||||||
db.addOrUpdateHtlcInfo(channel.channelId, commitNumber, paymentHash1, cltvExpiry1)
|
db.addOrUpdateHtlcInfo(channel.channelId, commitNumber, paymentHash1, cltvExpiry1)
|
||||||
db.addOrUpdateHtlcInfo(channel.channelId, commitNumber, paymentHash2, cltvExpiry2)
|
db.addOrUpdateHtlcInfo(channel.channelId, commitNumber, paymentHash2, cltvExpiry2)
|
||||||
assert(db.listHtlcHtlcInfos(channel.channelId, commitNumber).toList == List((paymentHash1, cltvExpiry1), (paymentHash2, cltvExpiry2)))
|
assert(db.listHtlcInfos(channel.channelId, commitNumber).toList == List((paymentHash1, cltvExpiry1), (paymentHash2, cltvExpiry2)))
|
||||||
assert(db.listHtlcHtlcInfos(channel.channelId, 43).toList == Nil)
|
assert(db.listHtlcInfos(channel.channelId, 43).toList == Nil)
|
||||||
|
|
||||||
db.removeChannel(channel.channelId)
|
db.removeChannel(channel.channelId)
|
||||||
assert(db.listChannels() === Nil)
|
assert(db.listChannels() === Nil)
|
||||||
assert(db.listHtlcHtlcInfos(channel.channelId, commitNumber).toList == Nil)
|
assert(db.listHtlcInfos(channel.channelId, commitNumber).toList == Nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,17 +19,15 @@ package fr.acinq.eclair.db
|
||||||
import java.net.{InetAddress, InetSocketAddress}
|
import java.net.{InetAddress, InetSocketAddress}
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{Block, Crypto, Satoshi}
|
import fr.acinq.bitcoin.{BinaryData, Block, Crypto, Satoshi}
|
||||||
import fr.acinq.eclair.db.sqlite.SqliteNetworkDb
|
import fr.acinq.eclair.db.sqlite.SqliteNetworkDb
|
||||||
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
|
||||||
import fr.acinq.eclair.router.Announcements
|
import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.wire.Color
|
import fr.acinq.eclair.wire.{ChannelUpdate, Color}
|
||||||
import org.junit.runner.RunWith
|
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.sqlite.SQLiteException
|
import org.sqlite.SQLiteException
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqliteNetworkDbSpec extends FunSuite {
|
class SqliteNetworkDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
@ -85,9 +83,9 @@ class SqliteNetworkDbSpec extends FunSuite {
|
||||||
db.removeChannel(channel_2.shortChannelId)
|
db.removeChannel(channel_2.shortChannelId)
|
||||||
assert(db.listChannels().size === 2)
|
assert(db.listChannels().size === 2)
|
||||||
|
|
||||||
val channel_update_1 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(42), 5, 7000000, 50000, 100, true)
|
val channel_update_1 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(42), 5, 7000000, 50000, 100, 500000000L, true)
|
||||||
val channel_update_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(43), 5, 7000000, 50000, 100, true)
|
val channel_update_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(43), 5, 7000000, 50000, 100, 500000000L, true)
|
||||||
val channel_update_3 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(44), 5, 7000000, 50000, 100, true)
|
val channel_update_3 = ChannelUpdate(BinaryData.empty, Block.RegtestGenesisBlock.hash, ShortChannelId(44), 123456789, Announcements.makeMessageFlags(hasOptionChannelHtlcMax = false), Announcements.makeChannelFlags(isNode1 = true, enable = true), 5, 7000000, 50000, 100, None)
|
||||||
|
|
||||||
assert(db.listChannelUpdates().toSet === Set.empty)
|
assert(db.listChannelUpdates().toSet === Set.empty)
|
||||||
db.addChannelUpdate(channel_update_1)
|
db.addChannelUpdate(channel_update_1)
|
||||||
|
@ -95,6 +93,10 @@ class SqliteNetworkDbSpec extends FunSuite {
|
||||||
assert(db.listChannelUpdates().size === 1)
|
assert(db.listChannelUpdates().size === 1)
|
||||||
intercept[SQLiteException](db.addChannelUpdate(channel_update_2))
|
intercept[SQLiteException](db.addChannelUpdate(channel_update_2))
|
||||||
db.addChannelUpdate(channel_update_3)
|
db.addChannelUpdate(channel_update_3)
|
||||||
|
assert(db.listChannelUpdates().size === 2)
|
||||||
|
db.addChannelUpdate(channel_update_1) // testing update
|
||||||
|
db.addChannelUpdate(channel_update_3) // testing update
|
||||||
|
assert(db.listChannelUpdates().size === 2)
|
||||||
db.removeChannel(channel_3.shortChannelId)
|
db.removeChannel(channel_3.shortChannelId)
|
||||||
assert(db.listChannels().size === 1)
|
assert(db.listChannels().size === 1)
|
||||||
assert(db.listChannelUpdates().size === 1)
|
assert(db.listChannelUpdates().size === 1)
|
||||||
|
|
|
@ -20,11 +20,9 @@ import java.sql.DriverManager
|
||||||
|
|
||||||
import fr.acinq.bitcoin.BinaryData
|
import fr.acinq.bitcoin.BinaryData
|
||||||
import fr.acinq.eclair.db.sqlite.SqlitePaymentsDb
|
import fr.acinq.eclair.db.sqlite.SqlitePaymentsDb
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqlitePaymentsDbSpec extends FunSuite {
|
class SqlitePaymentsDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
|
|
@ -21,11 +21,9 @@ import java.sql.DriverManager
|
||||||
|
|
||||||
import fr.acinq.eclair.db.sqlite.SqlitePeersDb
|
import fr.acinq.eclair.db.sqlite.SqlitePeersDb
|
||||||
import fr.acinq.eclair.randomKey
|
import fr.acinq.eclair.randomKey
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqlitePeersDbSpec extends FunSuite {
|
class SqlitePeersDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
|
|
@ -22,11 +22,9 @@ import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FAIL_MALFORMED_HTLC, CMD_FULF
|
||||||
import fr.acinq.eclair.db.sqlite.SqlitePendingRelayDb
|
import fr.acinq.eclair.db.sqlite.SqlitePendingRelayDb
|
||||||
import fr.acinq.eclair.randomBytes
|
import fr.acinq.eclair.randomBytes
|
||||||
import fr.acinq.eclair.wire.FailureMessageCodecs
|
import fr.acinq.eclair.wire.FailureMessageCodecs
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class SqlitePendingRelayDbSpec extends FunSuite {
|
class SqlitePendingRelayDbSpec extends FunSuite {
|
||||||
|
|
||||||
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
|
||||||
|
|
|
@ -44,10 +44,7 @@ import fr.acinq.eclair.{Globals, Kit, Setup}
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.json4s.JsonAST.JValue
|
import org.json4s.JsonAST.JValue
|
||||||
import org.json4s.{DefaultFormats, JString}
|
import org.json4s.{DefaultFormats, JString}
|
||||||
import org.junit.Ignore
|
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike, Ignore}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
@ -56,7 +53,6 @@ import scala.concurrent.duration._
|
||||||
/**
|
/**
|
||||||
* Created by PM on 15/03/2017.
|
* Created by PM on 15/03/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
@Ignore
|
@Ignore
|
||||||
class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
||||||
|
|
||||||
|
@ -246,7 +242,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||||
sender.send(nodes("B").register, ForwardShortId(shortIdBC, CMD_GETINFO))
|
sender.send(nodes("B").register, ForwardShortId(shortIdBC, CMD_GETINFO))
|
||||||
val commitmentBC = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].commitments
|
val commitmentBC = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].commitments
|
||||||
// we then forge a new channel_update for B-C...
|
// we then forge a new channel_update for B-C...
|
||||||
val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.expiryDeltaBlocks + 1, nodes("C").nodeParams.htlcMinimumMsat, nodes("B").nodeParams.feeBaseMsat, nodes("B").nodeParams.feeProportionalMillionth)
|
val channelUpdateBC = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, nodes("B").nodeParams.privateKey, nodes("C").nodeParams.nodeId, shortIdBC, nodes("B").nodeParams.expiryDeltaBlocks + 1, nodes("C").nodeParams.htlcMinimumMsat, nodes("B").nodeParams.feeBaseMsat, nodes("B").nodeParams.feeProportionalMillionth, 500000000L)
|
||||||
// ...and notify B's relayer
|
// ...and notify B's relayer
|
||||||
sender.send(nodes("B").relayer, LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, shortIdBC, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC))
|
sender.send(nodes("B").relayer, LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, shortIdBC, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC))
|
||||||
// we retrieve a payment hash from D
|
// we retrieve a payment hash from D
|
||||||
|
@ -262,6 +258,8 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||||
sender.send(nodes("A").router, 'updatesMap)
|
sender.send(nodes("A").router, 'updatesMap)
|
||||||
assert(sender.expectMsgType[Map[ChannelDesc, ChannelUpdate]].apply(ChannelDesc(channelUpdateBC.shortChannelId, nodes("B").nodeParams.nodeId, nodes("C").nodeParams.nodeId)) === channelUpdateBC)
|
assert(sender.expectMsgType[Map[ChannelDesc, ChannelUpdate]].apply(ChannelDesc(channelUpdateBC.shortChannelId, nodes("B").nodeParams.nodeId, nodes("C").nodeParams.nodeId)) === channelUpdateBC)
|
||||||
// we then put everything back like before by asking B to refresh its channel update (this will override the one we created)
|
// we then put everything back like before by asking B to refresh its channel update (this will override the one we created)
|
||||||
|
// first let's wait 3 seconds to make sure the timestamp of the new channel_update will be strictly greater than the former
|
||||||
|
sender.expectNoMsg(3 seconds)
|
||||||
sender.send(nodes("B").register, ForwardShortId(shortIdBC, TickRefreshChannelUpdate))
|
sender.send(nodes("B").register, ForwardShortId(shortIdBC, TickRefreshChannelUpdate))
|
||||||
sender.send(nodes("B").register, ForwardShortId(shortIdBC, CMD_GETINFO))
|
sender.send(nodes("B").register, ForwardShortId(shortIdBC, CMD_GETINFO))
|
||||||
val channelUpdateBC_new = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].channelUpdate
|
val channelUpdateBC_new = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].channelUpdate
|
||||||
|
@ -687,6 +685,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||||
sigListener.expectMsgType[ChannelSignatureReceived]
|
sigListener.expectMsgType[ChannelSignatureReceived]
|
||||||
sigListener.expectMsgType[ChannelSignatureReceived]
|
sigListener.expectMsgType[ChannelSignatureReceived]
|
||||||
sender.expectMsgType[PaymentSucceeded]
|
sender.expectMsgType[PaymentSucceeded]
|
||||||
|
|
||||||
// we now send a few htlcs C->F and F->C in order to obtain a commitments with multiple htlcs
|
// we now send a few htlcs C->F and F->C in order to obtain a commitments with multiple htlcs
|
||||||
def send(amountMsat: Long, paymentHandler: ActorRef, paymentInitiator: ActorRef) = {
|
def send(amountMsat: Long, paymentHandler: ActorRef, paymentInitiator: ActorRef) = {
|
||||||
sender.send(paymentHandler, ReceivePayment(Some(MilliSatoshi(amountMsat)), "1 coffee"))
|
sender.send(paymentHandler, ReceivePayment(Some(MilliSatoshi(amountMsat)), "1 coffee"))
|
||||||
|
@ -694,6 +693,7 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||||
val sendReq = SendPayment(amountMsat, pr.paymentHash, pr.nodeId)
|
val sendReq = SendPayment(amountMsat, pr.paymentHash, pr.nodeId)
|
||||||
sender.send(paymentInitiator, sendReq)
|
sender.send(paymentInitiator, sendReq)
|
||||||
}
|
}
|
||||||
|
|
||||||
val buffer = TestProbe()
|
val buffer = TestProbe()
|
||||||
send(100000000, paymentHandlerF, nodes("C").paymentInitiator) // will be left pending
|
send(100000000, paymentHandlerF, nodes("C").paymentInitiator) // will be left pending
|
||||||
forwardHandlerF.expectMsgType[UpdateAddHtlc]
|
forwardHandlerF.expectMsgType[UpdateAddHtlc]
|
||||||
|
@ -799,12 +799,9 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
|
||||||
// then we make the announcements
|
// then we make the announcements
|
||||||
val announcements = channels.map(c => AnnouncementsBatchValidationSpec.makeChannelAnnouncement(c))
|
val announcements = channels.map(c => AnnouncementsBatchValidationSpec.makeChannelAnnouncement(c))
|
||||||
announcements.foreach(ann => nodes("A").router ! PeerRoutingMessage(sender.ref, remoteNodeId, ann))
|
announcements.foreach(ann => nodes("A").router ! PeerRoutingMessage(sender.ref, remoteNodeId, ann))
|
||||||
// we need to send channel_update otherwise router won't validate the channels
|
|
||||||
val updates = channels.zip(announcements).map(x => AnnouncementsBatchValidationSpec.makeChannelUpdate(x._1, x._2.shortChannelId))
|
|
||||||
updates.foreach(update => nodes("A").router ! PeerRoutingMessage(sender.ref, remoteNodeId, update))
|
|
||||||
awaitCond({
|
awaitCond({
|
||||||
sender.send(nodes("D").router, 'channels)
|
sender.send(nodes("D").router, 'channels)
|
||||||
sender.expectMsgType[Iterable[ChannelAnnouncement]](5 seconds).size == channels.size + 5 // 5 remaining channels because D->F{1-F4} have disappeared
|
sender.expectMsgType[Iterable[ChannelAnnouncement]](5 seconds).size == channels.size + 5 // 5 remaining channels because D->F{1-5} have disappeared
|
||||||
}, max = 120 seconds, interval = 1 second)
|
}, max = 120 seconds, interval = 1 second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||||
import fr.acinq.eclair.channel._
|
import fr.acinq.eclair.channel._
|
||||||
import fr.acinq.eclair.payment.NoopPaymentHandler
|
import fr.acinq.eclair.payment.NoopPaymentHandler
|
||||||
import fr.acinq.eclair.wire.Init
|
import fr.acinq.eclair.wire.Init
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.{BeforeAndAfterAll, Matchers, Outcome, fixture}
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, Matchers, fixture}
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.io.Source
|
import scala.io.Source
|
||||||
|
@ -38,12 +36,12 @@ import scala.io.Source
|
||||||
/**
|
/**
|
||||||
* Created by PM on 30/05/2016.
|
* Created by PM on 30/05/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fixture.FunSuiteLike with BeforeAndAfterAll {
|
class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fixture.FunSuiteLike with BeforeAndAfterAll {
|
||||||
|
|
||||||
type FixtureParam = Tuple2[List[String], List[String]]
|
case class FixtureParam(ref: List[String], res: List[String])
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
Globals.blockCount.set(0)
|
Globals.blockCount.set(0)
|
||||||
val latch = new CountDownLatch(1)
|
val latch = new CountDownLatch(1)
|
||||||
val pipe: ActorRef = system.actorOf(Props(new SynchronizationPipe(latch)))
|
val pipe: ActorRef = system.actorOf(Props(new SynchronizationPipe(latch)))
|
||||||
|
@ -74,12 +72,12 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix
|
||||||
bob2blockchain.expectMsgType[WatchLost]
|
bob2blockchain.expectMsgType[WatchLost]
|
||||||
awaitCond(alice.stateName == NORMAL)
|
awaitCond(alice.stateName == NORMAL)
|
||||||
awaitCond(bob.stateName == NORMAL)
|
awaitCond(bob.stateName == NORMAL)
|
||||||
|
pipe ! new File(getClass.getResource(s"/scenarii/${test.name}.script").getFile)
|
||||||
|
latch.await(30, TimeUnit.SECONDS)
|
||||||
|
val ref = Source.fromFile(getClass.getResource(s"/scenarii/${test.name}.script.expected").getFile).getLines().filterNot(_.startsWith("#")).toList
|
||||||
|
val res = Source.fromFile(new File(s"${System.getProperty("buildDirectory")}/result.tmp")).getLines().filterNot(_.startsWith("#")).toList
|
||||||
|
withFixture(test.toNoArgTest(FixtureParam(ref, res)))
|
||||||
}
|
}
|
||||||
pipe ! new File(getClass.getResource(s"/scenarii/${test.name}.script").getFile)
|
|
||||||
latch.await(30, TimeUnit.SECONDS)
|
|
||||||
val ref = Source.fromFile(getClass.getResource(s"/scenarii/${test.name}.script.expected").getFile).getLines().filterNot(_.startsWith("#")).toList
|
|
||||||
val res = Source.fromFile(new File(s"${System.getProperty("buildDirectory")}/result.tmp")).getLines().filterNot(_.startsWith("#")).toList
|
|
||||||
test((ref, res))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def afterAll {
|
override def afterAll {
|
||||||
|
@ -87,15 +85,15 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix
|
||||||
TestKit.shutdownActorSystem(system)
|
TestKit.shutdownActorSystem(system)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("01-offer1") { case (ref, res) => assert(ref === res) }
|
test("01-offer1") { f => assert(f.ref === f.res) }
|
||||||
test("02-offer2") { case (ref, res) => assert(ref === res) }
|
test("02-offer2") { f => assert(f.ref === f.res) }
|
||||||
//test("03-fulfill1") { case (ref, res) => assert(ref === res) }
|
//test("03-fulfill1") { f => assert(f.ref === f.res) }
|
||||||
// test("04-two-commits-onedir") { case (ref, res) => assert(ref === res) } DOES NOT PASS : we now automatically sign back when we receive a revocation and have acked changes
|
// test("04-two-commits-onedir") { f => assert(f.ref === f.res) } DOES NOT PASS : we now automatically sign back when we receive a revocation and have acked changes
|
||||||
// test("05-two-commits-in-flight") { case (ref, res) => assert(ref === res)} DOES NOT PASS : cannot send two commit in a row (without having first revocation)
|
// test("05-two-commits-in-flight") { f => assert(f.ref === f.res)} DOES NOT PASS : cannot send two commit in a row (without having first revocation)
|
||||||
test("10-offers-crossover") { case (ref, res) => assert(ref === res) }
|
test("10-offers-crossover") { f => assert(f.ref === f.res) }
|
||||||
test("11-commits-crossover") { case (ref, res) => assert(ref === res) }
|
test("11-commits-crossover") { f => assert(f.ref === f.res) }
|
||||||
/*test("13-fee") { case (ref, res) => assert(ref === res)}
|
/*test("13-fee") { f => assert(f.ref === f.res)}
|
||||||
test("14-fee-twice") { case (ref, res) => assert(ref === res)}
|
test("14-fee-twice") { f => assert(f.ref === f.res)}
|
||||||
test("15-fee-twice-back-to-back") { case (ref, res) => assert(ref === res)}*/
|
test("15-fee-twice-back-to-back") { f => assert(f.ref === f.res)}*/
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,11 +16,9 @@
|
||||||
|
|
||||||
package fr.acinq.eclair.io
|
package fr.acinq.eclair.io
|
||||||
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class NodeURISpec extends FunSuite {
|
class NodeURISpec extends FunSuite {
|
||||||
|
|
||||||
val PUBKEY = "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134"
|
val PUBKEY = "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134"
|
||||||
|
|
|
@ -4,22 +4,20 @@ import java.net.InetSocketAddress
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import fr.acinq.bitcoin.Block
|
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||||
import fr.acinq.eclair.TestConstants._
|
import fr.acinq.eclair.TestConstants._
|
||||||
import fr.acinq.eclair.blockchain.EclairWallet
|
import fr.acinq.eclair.blockchain.EclairWallet
|
||||||
import fr.acinq.eclair.crypto.TransportHandler
|
import fr.acinq.eclair.crypto.TransportHandler
|
||||||
import fr.acinq.eclair.io.Peer.ResumeAnnouncements
|
import fr.acinq.eclair.io.Peer.{CHANNELID_ZERO, ResumeAnnouncements}
|
||||||
import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
|
import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
|
||||||
import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast, RouteCalculationSpec}
|
import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast}
|
||||||
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, randomKey, wire}
|
import fr.acinq.eclair.wire.Error
|
||||||
import org.junit.runner.RunWith
|
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, wire}
|
||||||
import org.scalatest.Outcome
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Random
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PeerSpec extends TestkitBaseClass {
|
class PeerSpec extends TestkitBaseClass {
|
||||||
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds.take(100)
|
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds.take(100)
|
||||||
val fakeRoutingInfo = shortChannelIds.map(makeFakeRoutingInfo)
|
val fakeRoutingInfo = shortChannelIds.map(makeFakeRoutingInfo)
|
||||||
|
@ -27,42 +25,9 @@ class PeerSpec extends TestkitBaseClass {
|
||||||
val updates = (fakeRoutingInfo.map(_._2) ++ fakeRoutingInfo.map(_._3)).toList
|
val updates = (fakeRoutingInfo.map(_._2) ++ fakeRoutingInfo.map(_._3)).toList
|
||||||
val nodes = (fakeRoutingInfo.map(_._4) ++ fakeRoutingInfo.map(_._5)).toList
|
val nodes = (fakeRoutingInfo.map(_._4) ++ fakeRoutingInfo.map(_._5)).toList
|
||||||
|
|
||||||
override type FixtureParam = TestProbe
|
case class FixtureParam(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef)
|
||||||
|
|
||||||
override protected def withFixture(test: OneArgTest): Outcome = {
|
override protected def withFixture(test: OneArgTest): Outcome = {
|
||||||
val probe = TestProbe()
|
|
||||||
test(probe)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("filter gossip message (no filtering)") { probe =>
|
|
||||||
val rebroadcast = Rebroadcast(channels.map(_ -> Set.empty[ActorRef]).toMap, updates.map(_ -> Set.empty[ActorRef]).toMap, nodes.map(_ -> Set.empty[ActorRef]).toMap)
|
|
||||||
val (channels1, updates1, nodes1) = Peer.filterGossipMessages(rebroadcast, probe.ref, None)
|
|
||||||
assert(channels1.toSet == channels.toSet)
|
|
||||||
assert(updates1.toSet == updates.toSet)
|
|
||||||
assert(nodes1.toSet == nodes.toSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("filter gossip message (filtered by origin)") { probe =>
|
|
||||||
val rebroadcast = Rebroadcast(
|
|
||||||
channels.map(_ -> Set.empty[ActorRef]).toMap + (channels(5) -> Set(probe.ref)),
|
|
||||||
updates.map(_ -> Set.empty[ActorRef]).toMap + (updates(6) -> Set(probe.ref)) + (updates(10) -> Set(probe.ref)),
|
|
||||||
nodes.map(_ -> Set.empty[ActorRef]).toMap + (nodes(4) -> Set(probe.ref)))
|
|
||||||
val (channels1, updates1, nodes1) = Peer.filterGossipMessages(rebroadcast, probe.ref, None)
|
|
||||||
assert(channels1.toSet == channels.toSet - channels(5))
|
|
||||||
assert(updates1.toSet == updates.toSet - updates(6) - updates(10))
|
|
||||||
assert(nodes1.toSet == nodes.toSet - nodes(4))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("filter gossip message (filtered by timestamp)") { probe =>
|
|
||||||
val rebroadcast = Rebroadcast(channels.map(_ -> Set.empty[ActorRef]).toMap, updates.map(_ -> Set.empty[ActorRef]).toMap, nodes.map(_ -> Set.empty[ActorRef]).toMap)
|
|
||||||
val timestamps = updates.map(_.timestamp).sorted.drop(10).take(20)
|
|
||||||
val (channels1, updates1, nodes1) = Peer.filterGossipMessages(rebroadcast, probe.ref, Some(wire.GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, timestamps.head, timestamps.last - timestamps.head)))
|
|
||||||
assert(updates1.toSet == updates.filter(u => timestamps.contains(u.timestamp)).toSet)
|
|
||||||
assert(nodes1.toSet == nodes.filter(u => timestamps.contains(u.timestamp)).toSet)
|
|
||||||
assert(channels1.toSet == channels.filter(ca => updates1.map(_.shortChannelId).toSet.contains(ca.shortChannelId)).toSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("react to peer's bad behavior") { probe =>
|
|
||||||
val authenticator = TestProbe()
|
val authenticator = TestProbe()
|
||||||
val watcher = TestProbe()
|
val watcher = TestProbe()
|
||||||
val router = TestProbe()
|
val router = TestProbe()
|
||||||
|
@ -72,8 +37,12 @@ class PeerSpec extends TestkitBaseClass {
|
||||||
val wallet: EclairWallet = null // unused
|
val wallet: EclairWallet = null // unused
|
||||||
val remoteNodeId = Bob.nodeParams.nodeId
|
val remoteNodeId = Bob.nodeParams.nodeId
|
||||||
val peer = system.actorOf(Peer.props(Alice.nodeParams, remoteNodeId, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet))
|
val peer = system.actorOf(Peer.props(Alice.nodeParams, remoteNodeId, authenticator.ref, watcher.ref, router.ref, relayer.ref, wallet))
|
||||||
|
withFixture(test.toNoArgTest(FixtureParam(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def connect(remoteNodeId: PublicKey, authenticator: TestProbe, watcher: TestProbe, router: TestProbe, relayer: TestProbe, connection: TestProbe, transport: TestProbe, peer: ActorRef): Unit = {
|
||||||
// let's simulate a connection
|
// let's simulate a connection
|
||||||
|
val probe = TestProbe()
|
||||||
probe.send(peer, Peer.Init(None, Set.empty))
|
probe.send(peer, Peer.Init(None, Set.empty))
|
||||||
authenticator.send(peer, Authenticator.Authenticated(connection.ref, transport.ref, remoteNodeId, InetSocketAddress.createUnresolved("foo.bar", 42000), false, None))
|
authenticator.send(peer, Authenticator.Authenticated(connection.ref, transport.ref, remoteNodeId, InetSocketAddress.createUnresolved("foo.bar", 42000), false, None))
|
||||||
transport.expectMsgType[TransportHandler.Listener]
|
transport.expectMsgType[TransportHandler.Listener]
|
||||||
|
@ -83,10 +52,56 @@ class PeerSpec extends TestkitBaseClass {
|
||||||
router.expectNoMsg(1 second) // bob's features require no sync
|
router.expectNoMsg(1 second) // bob's features require no sync
|
||||||
probe.send(peer, Peer.GetPeerInfo)
|
probe.send(peer, Peer.GetPeerInfo)
|
||||||
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")
|
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")
|
||||||
|
}
|
||||||
|
|
||||||
val channels = for (_ <- 0 until 12) yield RouteCalculationSpec.makeChannel(Random.nextInt(10000000), randomKey.publicKey, randomKey.publicKey)
|
test("filter gossip message (no filtering)") { f =>
|
||||||
val updates = for (_ <- 0 until 20) yield RouteCalculationSpec.makeUpdate(Random.nextInt(10000000), randomKey.publicKey, randomKey.publicKey, Random.nextInt(1000), Random.nextInt(1000))._2
|
import f._
|
||||||
val query = wire.QueryShortChannelIds(Block.RegtestGenesisBlock.hash, ChannelRangeQueries.encodeShortChannelIdsSingle(Seq(ShortChannelId(42000)), ChannelRangeQueries.UNCOMPRESSED_FORMAT, useGzip = false))
|
val probe = TestProbe()
|
||||||
|
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)
|
||||||
|
val rebroadcast = Rebroadcast(channels.map(_ -> Set.empty[ActorRef]).toMap, updates.map(_ -> Set.empty[ActorRef]).toMap, nodes.map(_ -> Set.empty[ActorRef]).toMap)
|
||||||
|
probe.send(peer, rebroadcast)
|
||||||
|
channels.foreach(transport.expectMsg(_))
|
||||||
|
updates.foreach(transport.expectMsg(_))
|
||||||
|
nodes.foreach(transport.expectMsg(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("filter gossip message (filtered by origin)") { f =>
|
||||||
|
import f._
|
||||||
|
val probe = TestProbe()
|
||||||
|
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)
|
||||||
|
val rebroadcast = Rebroadcast(
|
||||||
|
channels.map(_ -> Set.empty[ActorRef]).toMap + (channels(5) -> Set(peer)),
|
||||||
|
updates.map(_ -> Set.empty[ActorRef]).toMap + (updates(6) -> Set(peer)) + (updates(10) -> Set(peer)),
|
||||||
|
nodes.map(_ -> Set.empty[ActorRef]).toMap + (nodes(4) -> Set(peer)))
|
||||||
|
probe.send(peer, rebroadcast)
|
||||||
|
// peer won't send out announcements that came from itself
|
||||||
|
(channels.toSet - channels(5)).foreach(transport.expectMsg(_))
|
||||||
|
(updates.toSet - updates(6) - updates(10)).foreach(transport.expectMsg(_))
|
||||||
|
(nodes.toSet - nodes(4)).foreach(transport.expectMsg(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("filter gossip message (filtered by timestamp)") { f =>
|
||||||
|
import f._
|
||||||
|
val probe = TestProbe()
|
||||||
|
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)
|
||||||
|
val rebroadcast = Rebroadcast(channels.map(_ -> Set.empty[ActorRef]).toMap, updates.map(_ -> Set.empty[ActorRef]).toMap, nodes.map(_ -> Set.empty[ActorRef]).toMap)
|
||||||
|
val timestamps = updates.map(_.timestamp).sorted.drop(10).take(20)
|
||||||
|
val filter = wire.GossipTimestampFilter(Alice.nodeParams.chainHash, timestamps.head, timestamps.last - timestamps.head)
|
||||||
|
probe.send(peer, filter)
|
||||||
|
probe.send(peer, rebroadcast)
|
||||||
|
// peer doesn't filter channel announcements
|
||||||
|
channels.foreach(transport.expectMsg(_))
|
||||||
|
// but it will only send updates and node announcements matching the filter
|
||||||
|
updates.filter(u => timestamps.contains(u.timestamp)).foreach(transport.expectMsg(_))
|
||||||
|
nodes.filter(u => timestamps.contains(u.timestamp)).foreach(transport.expectMsg(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("react to peer's bad behavior") { f =>
|
||||||
|
import f._
|
||||||
|
val probe = TestProbe()
|
||||||
|
connect(remoteNodeId, authenticator, watcher, router, relayer, connection, transport, peer)
|
||||||
|
|
||||||
|
val query = wire.QueryShortChannelIds(Alice.nodeParams.chainHash, ChannelRangeQueries.encodeShortChannelIdsSingle(Seq(ShortChannelId(42000)), ChannelRangeQueries.UNCOMPRESSED_FORMAT, useGzip = false))
|
||||||
|
|
||||||
// make sure that routing messages go through
|
// make sure that routing messages go through
|
||||||
for (ann <- channels ++ updates) {
|
for (ann <- channels ++ updates) {
|
||||||
|
@ -142,5 +157,12 @@ class PeerSpec extends TestkitBaseClass {
|
||||||
router.expectMsg(Peer.PeerRoutingMessage(transport.ref, remoteNodeId, c))
|
router.expectMsg(Peer.PeerRoutingMessage(transport.ref, remoteNodeId, c))
|
||||||
}
|
}
|
||||||
transport.expectNoMsg(1 second) // peer hasn't acknowledged the messages
|
transport.expectNoMsg(1 second) // peer hasn't acknowledged the messages
|
||||||
|
|
||||||
|
// let's assume that one of the sigs were invalid
|
||||||
|
router.send(peer, Peer.InvalidSignature(channels(0)))
|
||||||
|
// peer will return a connection-wide error, including the hex-encoded representation of the bad message
|
||||||
|
val error = transport.expectMsgType[Error]
|
||||||
|
assert(error.channelId === CHANNELID_ZERO)
|
||||||
|
assert(new String(error.data).startsWith("bad announcement sig! bin=0100"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,7 @@ import fr.acinq.eclair.payment.PaymentLifecycle._
|
||||||
import fr.acinq.eclair.router.Hop
|
import fr.acinq.eclair.router.Hop
|
||||||
import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
|
import fr.acinq.eclair.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
|
||||||
import fr.acinq.eclair.{ShortChannelId, TestConstants, nodeFee, randomBytes}
|
import fr.acinq.eclair.{ShortChannelId, TestConstants, nodeFee, randomBytes}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
|
@ -35,7 +33,7 @@ import scala.util.Success
|
||||||
/**
|
/**
|
||||||
* Created by PM on 31/05/2016.
|
* Created by PM on 31/05/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class HtlcGenerationSpec extends FunSuite {
|
class HtlcGenerationSpec extends FunSuite {
|
||||||
|
|
||||||
test("compute fees") {
|
test("compute fees") {
|
||||||
|
@ -156,7 +154,7 @@ object HtlcGenerationSpec {
|
||||||
val (priv_a, priv_b, priv_c, priv_d, priv_e) = (TestConstants.Alice.keyManager.nodeKey, TestConstants.Bob.keyManager.nodeKey, randomExtendedPrivateKey, randomExtendedPrivateKey, randomExtendedPrivateKey)
|
val (priv_a, priv_b, priv_c, priv_d, priv_e) = (TestConstants.Alice.keyManager.nodeKey, TestConstants.Bob.keyManager.nodeKey, randomExtendedPrivateKey, randomExtendedPrivateKey, randomExtendedPrivateKey)
|
||||||
val (a, b, c, d, e) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey)
|
val (a, b, c, d, e) = (priv_a.publicKey, priv_b.publicKey, priv_c.publicKey, priv_d.publicKey, priv_e.publicKey)
|
||||||
val sig = Crypto.encodeSignature(Crypto.sign(Crypto.sha256(BinaryData.empty), priv_a.privateKey)) :+ 1.toByte
|
val sig = Crypto.encodeSignature(Crypto.sign(Crypto.sha256(BinaryData.empty), priv_a.privateKey)) :+ 1.toByte
|
||||||
val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0, "0000", 0, 42000, 0, 0)
|
val defaultChannelUpdate = ChannelUpdate(sig, Block.RegtestGenesisBlock.hash, ShortChannelId(0), 0, 1, 0, 0, 42000, 0, 0, Some(500000000L))
|
||||||
val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(1), cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7)
|
val channelUpdate_ab = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(1), cltvExpiryDelta = 4, feeBaseMsat = 642000, feeProportionalMillionths = 7)
|
||||||
val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(2), cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
val channelUpdate_bc = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(2), cltvExpiryDelta = 5, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
||||||
val channelUpdate_cd = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(3), cltvExpiryDelta = 10, feeBaseMsat = 60000, feeProportionalMillionths = 1)
|
val channelUpdate_cd = defaultChannelUpdate.copy(shortChannelId = ShortChannelId(3), cltvExpiryDelta = 10, feeBaseMsat = 60000, feeProportionalMillionths = 1)
|
||||||
|
|
|
@ -26,16 +26,14 @@ import fr.acinq.eclair.payment.PaymentLifecycle.{CheckPayment, ReceivePayment}
|
||||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||||
import fr.acinq.eclair.wire.{FinalExpiryTooSoon, UpdateAddHtlc}
|
import fr.acinq.eclair.wire.{FinalExpiryTooSoon, UpdateAddHtlc}
|
||||||
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
|
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuiteLike
|
import org.scalatest.FunSuiteLike
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 24/03/2017.
|
* Created by PM on 24/03/2017.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
||||||
|
|
||||||
test("LocalPaymentHandler should reply with a fulfill/fail, emit a PaymentReceived and adds payment in DB") {
|
test("LocalPaymentHandler should reply with a fulfill/fail, emit a PaymentReceived and adds payment in DB") {
|
||||||
|
|
|
@ -30,13 +30,11 @@ import fr.acinq.eclair.payment.PaymentLifecycle._
|
||||||
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
|
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
|
||||||
import fr.acinq.eclair.router._
|
import fr.acinq.eclair.router._
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 29/08/2016.
|
* Created by PM on 29/08/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PaymentLifecycleSpec extends BaseRouterSpec {
|
class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
|
|
||||||
val initialBlockCount = 420000
|
val initialBlockCount = 420000
|
||||||
|
@ -45,7 +43,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
val defaultAmountMsat = 142000000L
|
val defaultAmountMsat = 142000000L
|
||||||
val defaultPaymentHash = BinaryData("42" * 32)
|
val defaultPaymentHash = BinaryData("42" * 32)
|
||||||
|
|
||||||
test("payment failed (route not found)") { case (router, _) =>
|
test("payment failed (route not found)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
||||||
val monitor = TestProbe()
|
val monitor = TestProbe()
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
@ -60,7 +59,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(PaymentFailed(request.paymentHash, LocalFailure(RouteNotFound) :: Nil))
|
sender.expectMsg(PaymentFailed(request.paymentHash, LocalFailure(RouteNotFound) :: Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (route too expensive)") { case (router, _) =>
|
test("payment failed (route too expensive)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
||||||
val monitor = TestProbe()
|
val monitor = TestProbe()
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
@ -75,7 +75,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
val Seq(LocalFailure(RouteTooExpensive(_, _))) = sender.expectMsgType[PaymentFailed].failures
|
val Seq(LocalFailure(RouteTooExpensive(_, _))) = sender.expectMsgType[PaymentFailed].failures
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (unparsable failure)") { case (router, _) =>
|
test("payment failed (unparsable failure)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -112,7 +113,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(PaymentFailed(request.paymentHash, UnreadableRemoteFailure(hops) :: UnreadableRemoteFailure(hops) :: Nil))
|
sender.expectMsg(PaymentFailed(request.paymentHash, UnreadableRemoteFailure(hops) :: UnreadableRemoteFailure(hops) :: Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (local error)") { case (router, _) =>
|
test("payment failed (local error)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -139,7 +141,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (first hop returns an UpdateFailMalformedHtlc)") { case (router, _) =>
|
test("payment failed (first hop returns an UpdateFailMalformedHtlc)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -166,7 +169,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (TemporaryChannelFailure)") { case (router, _) =>
|
test("payment failed (TemporaryChannelFailure)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -202,7 +206,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: LocalFailure(RouteNotFound) :: Nil))
|
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: LocalFailure(RouteNotFound) :: Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (Update)") { case (router, _) =>
|
test("payment failed (Update)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -223,7 +228,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
relayer.expectMsg(ForwardShortId(channelId_ab, cmd1))
|
relayer.expectMsg(ForwardShortId(channelId_ab, cmd1))
|
||||||
|
|
||||||
// we change the cltv expiry
|
// we change the cltv expiry
|
||||||
val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 42, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
|
val channelUpdate_bc_modified = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 42, htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get)
|
||||||
val failure = IncorrectCltvExpiry(5, channelUpdate_bc_modified)
|
val failure = IncorrectCltvExpiry(5, channelUpdate_bc_modified)
|
||||||
// and node replies with a failure containing a new channel update
|
// and node replies with a failure containing a new channel update
|
||||||
sender.send(paymentFSM, UpdateFailHtlc("00" * 32, 0, Sphinx.createErrorPacket(sharedSecrets1.head._1, failure)))
|
sender.send(paymentFSM, UpdateFailHtlc("00" * 32, 0, Sphinx.createErrorPacket(sharedSecrets1.head._1, failure)))
|
||||||
|
@ -240,7 +245,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
relayer.expectMsg(ForwardShortId(channelId_ab, cmd2))
|
relayer.expectMsg(ForwardShortId(channelId_ab, cmd2))
|
||||||
|
|
||||||
// we change the cltv expiry one more time
|
// we change the cltv expiry one more time
|
||||||
val channelUpdate_bc_modified_2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 43, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
|
val channelUpdate_bc_modified_2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 43, htlcMinimumMsat = channelUpdate_bc.htlcMinimumMsat, feeBaseMsat = channelUpdate_bc.feeBaseMsat, feeProportionalMillionths = channelUpdate_bc.feeProportionalMillionths, htlcMaximumMsat = channelUpdate_bc.htlcMaximumMsat.get)
|
||||||
val failure2 = IncorrectCltvExpiry(5, channelUpdate_bc_modified_2)
|
val failure2 = IncorrectCltvExpiry(5, channelUpdate_bc_modified_2)
|
||||||
// and node replies with a failure containing a new channel update
|
// and node replies with a failure containing a new channel update
|
||||||
sender.send(paymentFSM, UpdateFailHtlc("00" * 32, 0, Sphinx.createErrorPacket(sharedSecrets2.head._1, failure2)))
|
sender.send(paymentFSM, UpdateFailHtlc("00" * 32, 0, Sphinx.createErrorPacket(sharedSecrets2.head._1, failure2)))
|
||||||
|
@ -258,7 +263,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: RemoteFailure(hops2, ErrorPacket(b, failure2)) :: LocalFailure(RouteNotFound) :: Nil))
|
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: RemoteFailure(hops2, ErrorPacket(b, failure2)) :: LocalFailure(RouteNotFound) :: Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment failed (PermanentChannelFailure)") { case (router, _) =>
|
test("payment failed (PermanentChannelFailure)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val relayer = TestProbe()
|
val relayer = TestProbe()
|
||||||
val routerForwarder = TestProbe()
|
val routerForwarder = TestProbe()
|
||||||
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
|
||||||
|
@ -292,7 +298,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: LocalFailure(RouteNotFound) :: Nil))
|
sender.expectMsg(PaymentFailed(request.paymentHash, RemoteFailure(hops, ErrorPacket(b, failure)) :: LocalFailure(RouteNotFound) :: Nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("payment succeeded") { case (router, _) =>
|
test("payment succeeded") { fixture =>
|
||||||
|
import fixture._
|
||||||
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
|
||||||
val monitor = TestProbe()
|
val monitor = TestProbe()
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
@ -315,7 +322,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
|
||||||
assert(fee === MilliSatoshi(paymentOK.amountMsat - request.amountMsat))
|
assert(fee === MilliSatoshi(paymentOK.amountMsat - request.amountMsat))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("filter errors properly") { case _ =>
|
test("filter errors properly") { fixture =>
|
||||||
val failures = LocalFailure(RouteNotFound) :: RemoteFailure(Hop(a, b, channelUpdate_ab) :: Nil, ErrorPacket(a, TemporaryNodeFailure)) :: LocalFailure(AddHtlcFailed("00" * 32, "00" * 32, ChannelUnavailable("00" * 32), Local(None), None, None)) :: LocalFailure(RouteNotFound) :: Nil
|
val failures = LocalFailure(RouteNotFound) :: RemoteFailure(Hop(a, b, channelUpdate_ab) :: Nil, ErrorPacket(a, TemporaryNodeFailure)) :: LocalFailure(AddHtlcFailed("00" * 32, "00" * 32, ChannelUnavailable("00" * 32), Local(None), None, None)) :: LocalFailure(RouteNotFound) :: Nil
|
||||||
val filtered = PaymentLifecycle.transformForUser(failures)
|
val filtered = PaymentLifecycle.transformForUser(failures)
|
||||||
assert(filtered == LocalFailure(RouteNotFound) :: RemoteFailure(Hop(a, b, channelUpdate_ab) :: Nil, ErrorPacket(a, TemporaryNodeFailure)) :: LocalFailure(ChannelUnavailable("00" * 32)) :: Nil)
|
assert(filtered == LocalFailure(RouteNotFound) :: RemoteFailure(Hop(a, b, channelUpdate_ab) :: Nil, ErrorPacket(a, TemporaryNodeFailure)) :: LocalFailure(ChannelUnavailable("00" * 32)) :: Nil)
|
||||||
|
|
|
@ -22,14 +22,12 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.bitcoin.{Bech32, BinaryData, Block, Btc, Crypto, MilliBtc, MilliSatoshi, Protocol, Satoshi}
|
import fr.acinq.bitcoin.{Bech32, BinaryData, Block, Btc, Crypto, MilliBtc, MilliSatoshi, Protocol, Satoshi}
|
||||||
import fr.acinq.eclair.ShortChannelId
|
import fr.acinq.eclair.ShortChannelId
|
||||||
import fr.acinq.eclair.payment.PaymentRequest._
|
import fr.acinq.eclair.payment.PaymentRequest._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by fabrice on 15/05/17.
|
* Created by fabrice on 15/05/17.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PaymentRequestSpec extends FunSuite {
|
class PaymentRequestSpec extends FunSuite {
|
||||||
|
|
||||||
val priv = PrivateKey(BinaryData("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734"), compressed = true)
|
val priv = PrivateKey(BinaryData("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734"), compressed = true)
|
||||||
|
|
|
@ -26,31 +26,29 @@ import fr.acinq.eclair.router.Announcements
|
||||||
import fr.acinq.eclair.transactions.CommitmentSpec
|
import fr.acinq.eclair.transactions.CommitmentSpec
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestConstants, TestkitBaseClass, randomBytes, randomKey}
|
import fr.acinq.eclair.{TestConstants, TestkitBaseClass, randomBytes, randomKey}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 29/08/2016.
|
* Created by PM on 29/08/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RelayerSpec extends TestkitBaseClass {
|
class RelayerSpec extends TestkitBaseClass {
|
||||||
|
|
||||||
// let's reuse the existing test data
|
// let's reuse the existing test data
|
||||||
import HtlcGenerationSpec._
|
import HtlcGenerationSpec._
|
||||||
|
|
||||||
type FixtureParam = Tuple3[ActorRef, TestProbe, TestProbe]
|
case class FixtureParam(relayer: ActorRef, register: TestProbe, paymentHandler: TestProbe)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
|
||||||
|
|
||||||
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
within(30 seconds) {
|
within(30 seconds) {
|
||||||
val register = TestProbe()
|
val register = TestProbe()
|
||||||
val paymentHandler = TestProbe()
|
val paymentHandler = TestProbe()
|
||||||
// we are node B in the route A -> B -> C -> ....
|
// we are node B in the route A -> B -> C -> ....
|
||||||
//val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams.copy(nodeKey = priv_b), register.ref, paymentHandler.ref))
|
//val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams.copy(nodeKey = priv_b), register.ref, paymentHandler.ref))
|
||||||
val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams, register.ref, paymentHandler.ref))
|
val relayer = system.actorOf(Relayer.props(TestConstants.Bob.nodeParams, register.ref, paymentHandler.ref))
|
||||||
test((relayer, register, paymentHandler))
|
withFixture(test.toNoArgTest(FixtureParam(relayer, register, paymentHandler)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +59,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
RemoteCommit(42, CommitmentSpec(Set.empty, 20000, 5000000, 100000000), "00" * 32, randomKey.toPoint),
|
RemoteCommit(42, CommitmentSpec(Set.empty, 20000, 5000000, 100000000), "00" * 32, randomKey.toPoint),
|
||||||
null, null, 0, 0, Map.empty, null, null, null, channelId)
|
null, null, 0, 0, Map.empty, null, null, null, channelId)
|
||||||
|
|
||||||
test("relay an htlc-add") { case (relayer, register, paymentHandler) =>
|
test("relay an htlc-add") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
|
@ -80,7 +79,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when we have no channel_update for the next channel") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when we have no channel_update for the next channel") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
|
@ -99,7 +99,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when register returns an error") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when register returns an error") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
|
@ -125,7 +126,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when the channel is advertised as unusable (down)") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when the channel is advertised as unusable (down)") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// check that payments are sent properly
|
// check that payments are sent properly
|
||||||
|
@ -143,7 +145,7 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
|
|
||||||
// now tell the relayer that the channel is down and try again
|
// now tell the relayer that the channel is down and try again
|
||||||
relayer ! LocalChannelDown(sender.ref, channelId = channelId_bc, shortChannelId = channelUpdate_bc.shortChannelId, remoteNodeId = TestConstants.Bob.nodeParams.nodeId)
|
relayer ! LocalChannelDown(sender.ref, channelId = channelId_bc, shortChannelId = channelUpdate_bc.shortChannelId, remoteNodeId = TestConstants.Bob.nodeParams.nodeId)
|
||||||
|
|
||||||
val (cmd1, _) = buildCommand(finalAmountMsat, finalExpiry, "02" * 32, hops)
|
val (cmd1, _) = buildCommand(finalAmountMsat, finalExpiry, "02" * 32, hops)
|
||||||
val add_ab1 = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd1.amountMsat, cmd1.paymentHash, cmd1.expiry, cmd1.onion)
|
val add_ab1 = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd1.amountMsat, cmd1.paymentHash, cmd1.expiry, cmd1.onion)
|
||||||
|
@ -157,27 +159,29 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when the requested channel is disabled") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when the requested channel is disabled") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops)
|
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops)
|
||||||
// and then manually build an htlc
|
// and then manually build an htlc
|
||||||
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
val add_ab = UpdateAddHtlc(channelId = channelId_ab, id = 123456, cmd.amountMsat, cmd.paymentHash, cmd.expiry, cmd.onion)
|
||||||
val channelUpdate_bc_disabled = channelUpdate_bc.copy(flags = Announcements.makeFlags(Announcements.isNode1(channelUpdate_bc.flags), enable = false))
|
val channelUpdate_bc_disabled = channelUpdate_bc.copy(channelFlags = Announcements.makeChannelFlags(Announcements.isNode1(channelUpdate_bc.channelFlags), enable = false))
|
||||||
relayer ! LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc_disabled, makeCommitments(channelId_bc))
|
relayer ! LocalChannelUpdate(null, channelId_bc, channelUpdate_bc.shortChannelId, c, None, channelUpdate_bc_disabled, makeCommitments(channelId_bc))
|
||||||
|
|
||||||
sender.send(relayer, ForwardAdd(add_ab))
|
sender.send(relayer, ForwardAdd(add_ab))
|
||||||
|
|
||||||
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
|
||||||
assert(fail.id === add_ab.id)
|
assert(fail.id === add_ab.id)
|
||||||
assert(fail.reason == Right(ChannelDisabled(channelUpdate_bc_disabled.flags, channelUpdate_bc_disabled)))
|
assert(fail.reason == Right(ChannelDisabled(channelUpdate_bc_disabled.messageFlags, channelUpdate_bc_disabled.channelFlags, channelUpdate_bc_disabled)))
|
||||||
|
|
||||||
register.expectNoMsg(100 millis)
|
register.expectNoMsg(100 millis)
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when the onion is malformed") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when the onion is malformed") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
|
@ -196,7 +200,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when amount is below the next hop's requirements") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when amount is below the next hop's requirements") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we use this to build a valid onion
|
// we use this to build a valid onion
|
||||||
|
@ -215,7 +220,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when expiry does not match next hop's requirements") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when expiry does not match next hop's requirements") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(cltvExpiryDelta = 0)))
|
val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(cltvExpiryDelta = 0)))
|
||||||
|
@ -234,7 +240,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail to relay an htlc-add when relay fee isn't sufficient") { case (relayer, register, paymentHandler) =>
|
test("fail to relay an htlc-add when relay fee isn't sufficient") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(feeBaseMsat = hops(1).lastUpdate.feeBaseMsat / 2)))
|
val hops1 = hops.updated(1, hops(1).copy(lastUpdate = hops(1).lastUpdate.copy(feeBaseMsat = hops(1).lastUpdate.feeBaseMsat / 2)))
|
||||||
|
@ -253,7 +260,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail an htlc-add at the final node when amount has been modified by second-to-last node") { case (relayer, register, paymentHandler) =>
|
test("fail an htlc-add at the final node when amount has been modified by second-to-last node") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
||||||
|
@ -273,7 +281,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("fail an htlc-add at the final node when expiry has been modified by second-to-last node") { case (relayer, register, paymentHandler) =>
|
test("fail an htlc-add at the final node when expiry has been modified by second-to-last node") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
// to simulate this we use a zero-hop route A->B where A is the 'attacker'
|
||||||
|
@ -293,7 +302,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("correctly translates errors returned by channel when attempting to add an htlc") { case (relayer, register, paymentHandler) =>
|
test("correctly translates errors returned by channel when attempting to add an htlc") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
val paymentHash = randomBytes(32)
|
val paymentHash = randomBytes(32)
|
||||||
|
@ -308,9 +318,9 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, InsufficientFunds(channelId_bc, origin.amountMsatOut, 100, 0, 0), origin, Some(channelUpdate_bc), None)))
|
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, InsufficientFunds(channelId_bc, origin.amountMsatOut, 100, 0, 0), origin, Some(channelUpdate_bc), None)))
|
||||||
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(TemporaryChannelFailure(channelUpdate_bc)))
|
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(TemporaryChannelFailure(channelUpdate_bc)))
|
||||||
|
|
||||||
val channelUpdate_bc_disabled = channelUpdate_bc.copy(flags = "0002")
|
val channelUpdate_bc_disabled = channelUpdate_bc.copy(channelFlags = 2)
|
||||||
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ChannelUnavailable(channelId_bc), origin, Some(channelUpdate_bc_disabled), None)))
|
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, ChannelUnavailable(channelId_bc), origin, Some(channelUpdate_bc_disabled), None)))
|
||||||
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(ChannelDisabled(channelUpdate_bc_disabled.flags, channelUpdate_bc_disabled)))
|
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(ChannelDisabled(channelUpdate_bc_disabled.messageFlags, channelUpdate_bc_disabled.channelFlags, channelUpdate_bc_disabled)))
|
||||||
|
|
||||||
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, HtlcTimedout(channelId_bc), origin, None, None)))
|
sender.send(relayer, Status.Failure(AddHtlcFailed(channelId_bc, paymentHash, HtlcTimedout(channelId_bc), origin, None, None)))
|
||||||
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(PermanentChannelFailure))
|
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(PermanentChannelFailure))
|
||||||
|
@ -322,7 +332,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
paymentHandler.expectNoMsg(100 millis)
|
paymentHandler.expectNoMsg(100 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("relay an htlc-fulfill") { case (relayer, register, _) =>
|
test("relay an htlc-fulfill") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val eventListener = TestProbe()
|
val eventListener = TestProbe()
|
||||||
|
|
||||||
|
@ -342,7 +353,8 @@ class RelayerSpec extends TestkitBaseClass {
|
||||||
assert(paymentRelayed.copy(timestamp = 0) === PaymentRelayed(MilliSatoshi(origin.amountMsatIn), MilliSatoshi(origin.amountMsatOut), add_bc.paymentHash, channelId_ab, channelId_bc, timestamp = 0))
|
assert(paymentRelayed.copy(timestamp = 0) === PaymentRelayed(MilliSatoshi(origin.amountMsatIn), MilliSatoshi(origin.amountMsatOut), add_bc.paymentHash, channelId_ab, channelId_bc, timestamp = 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("relay an htlc-fail") { case (relayer, register, _) =>
|
test("relay an htlc-fail") { f =>
|
||||||
|
import f._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
|
|
||||||
// we build a fake htlc for the downstream channel
|
// we build a fake htlc for the downstream channel
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
package fr.acinq.eclair.router
|
package fr.acinq.eclair.router
|
||||||
|
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
import akka.testkit.TestProbe
|
|
||||||
import akka.pattern.pipe
|
import akka.pattern.pipe
|
||||||
|
import akka.testkit.TestProbe
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block, Satoshi, Script, Transaction}
|
import fr.acinq.bitcoin.{BinaryData, Block, Satoshi, Script, Transaction}
|
||||||
import fr.acinq.eclair.blockchain.ValidateResult
|
import fr.acinq.eclair.blockchain.ValidateResult
|
||||||
|
@ -27,9 +27,7 @@ import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, Exten
|
||||||
import fr.acinq.eclair.transactions.Scripts
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate}
|
import fr.acinq.eclair.wire.{ChannelAnnouncement, ChannelUpdate}
|
||||||
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{Await, ExecutionContext}
|
import scala.concurrent.{Await, ExecutionContext}
|
||||||
|
@ -37,7 +35,7 @@ import scala.concurrent.{Await, ExecutionContext}
|
||||||
/**
|
/**
|
||||||
* Created by PM on 31/05/2016.
|
* Created by PM on 31/05/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class AnnouncementsBatchValidationSpec extends FunSuite {
|
class AnnouncementsBatchValidationSpec extends FunSuite {
|
||||||
|
|
||||||
import AnnouncementsBatchValidationSpec._
|
import AnnouncementsBatchValidationSpec._
|
||||||
|
@ -103,6 +101,6 @@ object AnnouncementsBatchValidationSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
def makeChannelUpdate(c: SimulatedChannel, shortChannelId: ShortChannelId): ChannelUpdate =
|
def makeChannelUpdate(c: SimulatedChannel, shortChannelId: ShortChannelId): ChannelUpdate =
|
||||||
Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, c.node1Key, c.node2Key.publicKey, shortChannelId, 10, 1000, 10, 100)
|
Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, c.node1Key, c.node2Key.publicKey, shortChannelId, 10, 1000, 10, 100, 500000000L)
|
||||||
|
|
||||||
}
|
}
|
|
@ -21,14 +21,12 @@ import fr.acinq.bitcoin.{BinaryData, Block}
|
||||||
import fr.acinq.eclair.TestConstants.Alice
|
import fr.acinq.eclair.TestConstants.Alice
|
||||||
import fr.acinq.eclair._
|
import fr.acinq.eclair._
|
||||||
import fr.acinq.eclair.router.Announcements._
|
import fr.acinq.eclair.router.Announcements._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 31/05/2016.
|
* Created by PM on 31/05/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class AnnouncementsSpec extends FunSuite {
|
class AnnouncementsSpec extends FunSuite {
|
||||||
|
|
||||||
test("check nodeId1/nodeId2 lexical ordering") {
|
test("check nodeId1/nodeId2 lexical ordering") {
|
||||||
|
@ -55,7 +53,7 @@ class AnnouncementsSpec extends FunSuite {
|
||||||
}
|
}
|
||||||
|
|
||||||
ignore("create valid signed channel update announcement") {
|
ignore("create valid signed channel update announcement") {
|
||||||
val ann = makeChannelUpdate(Block.RegtestGenesisBlock.hash, Alice.nodeParams.privateKey, randomKey.publicKey, ShortChannelId(45561L), Alice.nodeParams.expiryDeltaBlocks, Alice.nodeParams.htlcMinimumMsat, Alice.nodeParams.feeBaseMsat, Alice.nodeParams.feeProportionalMillionth)
|
val ann = makeChannelUpdate(Block.RegtestGenesisBlock.hash, Alice.nodeParams.privateKey, randomKey.publicKey, ShortChannelId(45561L), Alice.nodeParams.expiryDeltaBlocks, Alice.nodeParams.htlcMinimumMsat, Alice.nodeParams.feeBaseMsat, Alice.nodeParams.feeProportionalMillionth, 500000000L)
|
||||||
assert(checkSig(ann, Alice.nodeParams.nodeId))
|
assert(checkSig(ann, Alice.nodeParams.nodeId))
|
||||||
assert(checkSig(ann, randomKey.publicKey) === false)
|
assert(checkSig(ann, randomKey.publicKey) === false)
|
||||||
}
|
}
|
||||||
|
@ -66,22 +64,22 @@ class AnnouncementsSpec extends FunSuite {
|
||||||
// NB: node1 < node2 (public keys)
|
// NB: node1 < node2 (public keys)
|
||||||
assert(isNode1(node1_priv.publicKey.toBin, node2_priv.publicKey.toBin))
|
assert(isNode1(node1_priv.publicKey.toBin, node2_priv.publicKey.toBin))
|
||||||
assert(!isNode1(node2_priv.publicKey.toBin, node1_priv.publicKey.toBin))
|
assert(!isNode1(node2_priv.publicKey.toBin, node1_priv.publicKey.toBin))
|
||||||
val channelUpdate1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = true)
|
val channelUpdate1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, 500000000L, enable = true)
|
||||||
val channelUpdate1_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = false)
|
val channelUpdate1_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, 500000000L, enable = false)
|
||||||
val channelUpdate2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = true)
|
val channelUpdate2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, 500000000L, enable = true)
|
||||||
val channelUpdate2_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = false)
|
val channelUpdate2_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, 500000000L, enable = false)
|
||||||
assert(channelUpdate1.flags == BinaryData("0000")) // ....00
|
assert(channelUpdate1.channelFlags == 0) // ....00
|
||||||
assert(channelUpdate1_disabled.flags == BinaryData("0002")) // ....10
|
assert(channelUpdate1_disabled.channelFlags == 2) // ....10
|
||||||
assert(channelUpdate2.flags == BinaryData("0001")) // ....01
|
assert(channelUpdate2.channelFlags == 1) // ....01
|
||||||
assert(channelUpdate2_disabled.flags == BinaryData("0003")) // ....11
|
assert(channelUpdate2_disabled.channelFlags == 3) // ....11
|
||||||
assert(isNode1(channelUpdate1.flags))
|
assert(isNode1(channelUpdate1.channelFlags))
|
||||||
assert(isNode1(channelUpdate1_disabled.flags))
|
assert(isNode1(channelUpdate1_disabled.channelFlags))
|
||||||
assert(!isNode1(channelUpdate2.flags))
|
assert(!isNode1(channelUpdate2.channelFlags))
|
||||||
assert(!isNode1(channelUpdate2_disabled.flags))
|
assert(!isNode1(channelUpdate2_disabled.channelFlags))
|
||||||
assert(isEnabled(channelUpdate1.flags))
|
assert(isEnabled(channelUpdate1.channelFlags))
|
||||||
assert(!isEnabled(channelUpdate1_disabled.flags))
|
assert(!isEnabled(channelUpdate1_disabled.channelFlags))
|
||||||
assert(isEnabled(channelUpdate2.flags))
|
assert(isEnabled(channelUpdate2.channelFlags))
|
||||||
assert(!isEnabled(channelUpdate2_disabled.flags))
|
assert(!isEnabled(channelUpdate2_disabled.channelFlags))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ import fr.acinq.eclair.router.Announcements._
|
||||||
import fr.acinq.eclair.transactions.Scripts
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{TestkitBaseClass, randomKey, _}
|
import fr.acinq.eclair.{TestkitBaseClass, randomKey, _}
|
||||||
import org.junit.runner.RunWith
|
import org.scalatest.Outcome
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
@ -38,12 +37,10 @@ import scala.concurrent.duration._
|
||||||
* It is re-used in payment FSM tests
|
* It is re-used in payment FSM tests
|
||||||
* Created by PM on 29/08/2016.
|
* Created by PM on 29/08/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
abstract class BaseRouterSpec extends TestkitBaseClass {
|
abstract class BaseRouterSpec extends TestkitBaseClass {
|
||||||
|
|
||||||
import BaseRouterSpec._
|
case class FixtureParam(router: ActorRef, watcher: TestProbe)
|
||||||
|
|
||||||
type FixtureParam = Tuple2[ActorRef, TestProbe]
|
|
||||||
|
|
||||||
val remoteNodeId = PrivateKey(BinaryData("01" * 32), compressed = true).publicKey
|
val remoteNodeId = PrivateKey(BinaryData("01" * 32), compressed = true).publicKey
|
||||||
|
|
||||||
|
@ -78,16 +75,16 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
|
||||||
val chan_cd = channelAnnouncement(channelId_cd, priv_c, priv_d, priv_funding_c, priv_funding_d)
|
val chan_cd = channelAnnouncement(channelId_cd, priv_c, priv_d, priv_funding_c, priv_funding_d)
|
||||||
val chan_ef = channelAnnouncement(channelId_ef, priv_e, priv_f, priv_funding_e, priv_funding_f)
|
val chan_ef = channelAnnouncement(channelId_ef, priv_e, priv_f, priv_funding_e, priv_funding_f)
|
||||||
|
|
||||||
val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, b, channelId_ab, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, b, channelId_ab, cltvExpiryDelta = 7, htlcMinimumMsat = 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, a, channelId_ab, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, a, channelId_ab, cltvExpiryDelta = 7, htlcMinimumMsat = 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_bc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
|
val channelUpdate_bc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 5, htlcMinimumMsat = 0, feeBaseMsat = 233000, feeProportionalMillionths = 1, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_cb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, b, channelId_bc, cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
|
val channelUpdate_cb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, b, channelId_bc, cltvExpiryDelta = 5, htlcMinimumMsat = 0, feeBaseMsat = 233000, feeProportionalMillionths = 1, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_cd = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
val channelUpdate_cd = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, htlcMinimumMsat = 0, feeBaseMsat = 153000, feeProportionalMillionths = 4, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
|
val channelUpdate_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, channelId_cd, cltvExpiryDelta = 3, htlcMinimumMsat = 0, feeBaseMsat = 153000, feeProportionalMillionths = 4, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, channelId_ef, cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
|
val channelUpdate_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, channelId_ef, cltvExpiryDelta = 9, htlcMinimumMsat = 0, feeBaseMsat = 786000, feeProportionalMillionths = 8, htlcMaximumMsat = 500000000L)
|
||||||
val channelUpdate_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, channelId_ef, cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
|
val channelUpdate_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, channelId_ef, cltvExpiryDelta = 9, htlcMinimumMsat = 0, feeBaseMsat = 786000, feeProportionalMillionths = 8, htlcMaximumMsat = 500000000L)
|
||||||
|
|
||||||
override def withFixture(test: OneArgTest) = {
|
override def withFixture(test: OneArgTest): Outcome = {
|
||||||
// the network will be a --(1)--> b ---(2)--> c --(3)--> d and e --(4)--> f (we are a)
|
// the network will be a --(1)--> b ---(2)--> c --(3)--> d and e --(4)--> f (we are a)
|
||||||
|
|
||||||
within(30 seconds) {
|
within(30 seconds) {
|
||||||
|
@ -151,7 +148,7 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
|
||||||
channels.size === 4 && updates.size === 8
|
channels.size === 4 && updates.size === 8
|
||||||
}, max = 10 seconds, interval = 1 second)
|
}, max = 10 seconds, interval = 1 second)
|
||||||
|
|
||||||
test((router, watcher))
|
withFixture(test.toNoArgTest(FixtureParam(router, watcher)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,13 @@ package fr.acinq.eclair.router
|
||||||
|
|
||||||
import fr.acinq.bitcoin.Block
|
import fr.acinq.bitcoin.Block
|
||||||
import fr.acinq.eclair.ShortChannelId
|
import fr.acinq.eclair.ShortChannelId
|
||||||
import fr.acinq.eclair.router.ChannelRangeQueriesSpec.shortChannelIds
|
import fr.acinq.eclair.wire.ReplyChannelRangeEx
|
||||||
import fr.acinq.eclair.wire.{ReplyChannelRange, ReplyChannelRangeEx}
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.collection.immutable.SortedMap
|
import scala.collection.immutable.SortedMap
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ChannelRangeQueriesExSpec extends FunSuite {
|
class ChannelRangeQueriesExSpec extends FunSuite {
|
||||||
import ChannelRangeQueriesEx._
|
|
||||||
val random = new Random()
|
val random = new Random()
|
||||||
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds
|
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds
|
||||||
val timestamps = shortChannelIds.map(id => id -> random.nextInt(400000).toLong).toMap
|
val timestamps = shortChannelIds.map(id => id -> random.nextInt(400000).toLong).toMap
|
||||||
|
|
|
@ -3,13 +3,11 @@ package fr.acinq.eclair.router
|
||||||
import fr.acinq.bitcoin.Block
|
import fr.acinq.bitcoin.Block
|
||||||
import fr.acinq.eclair.ShortChannelId
|
import fr.acinq.eclair.ShortChannelId
|
||||||
import fr.acinq.eclair.wire.ReplyChannelRange
|
import fr.acinq.eclair.wire.ReplyChannelRange
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.collection.{SortedSet, immutable}
|
import scala.collection.{SortedSet, immutable}
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ChannelRangeQueriesSpec extends FunSuite {
|
class ChannelRangeQueriesSpec extends FunSuite {
|
||||||
import ChannelRangeQueriesSpec._
|
import ChannelRangeQueriesSpec._
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,11 @@ import fr.acinq.eclair.router.RoutingSyncSpec.makeFakeRoutingInfo
|
||||||
import fr.acinq.eclair.transactions.Scripts
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, TxCoordinates}
|
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, TxCoordinates}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
import org.scalatest.{BeforeAndAfterAll, Outcome}
|
import org.scalatest.{BeforeAndAfterAll, Outcome}
|
||||||
|
|
||||||
import scala.collection.{SortedSet, immutable}
|
import scala.collection.{SortedSet, immutable}
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class PruningSpec extends TestkitBaseClass with BeforeAndAfterAll {
|
class PruningSpec extends TestkitBaseClass with BeforeAndAfterAll {
|
||||||
import PruningSpec._
|
import PruningSpec._
|
||||||
|
|
||||||
|
|
|
@ -20,19 +20,16 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
|
||||||
import fr.acinq.bitcoin.{BinaryData, Block, Crypto}
|
import fr.acinq.bitcoin.{BinaryData, Block, Crypto}
|
||||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
|
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
||||||
import org.jgrapht.graph.DirectedWeightedPseudograph
|
import org.jgrapht.graph.DirectedWeightedPseudograph
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.compat.Platform
|
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 31/05/2016.
|
* Created by PM on 31/05/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RouteCalculationSpec extends FunSuite {
|
class RouteCalculationSpec extends FunSuite {
|
||||||
|
|
||||||
import RouteCalculationSpec._
|
import RouteCalculationSpec._
|
||||||
|
@ -191,14 +188,14 @@ class RouteCalculationSpec extends FunSuite {
|
||||||
|
|
||||||
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
|
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
|
||||||
|
|
||||||
val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, "0000", 1, 42, 2500, 140)
|
val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, 0, 0, 1, 42, 2500, 140, None)
|
||||||
val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, "0001", 1, 43, 2501, 141)
|
val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, 0, 1, 1, 43, 2501, 141, None)
|
||||||
val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, "0000", 1, 44, 2502, 142)
|
val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 0, 1, 44, 2502, 142, None)
|
||||||
val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, "0001", 1, 45, 2503, 143)
|
val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, 0, 1, 1, 45, 2503, 143, None)
|
||||||
val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, "0000", 1, 46, 2504, 144)
|
val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 1, 0, 1, 46, 2504, 144, Some(500000000L))
|
||||||
val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, "0001", 1, 47, 2505, 145)
|
val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, 0, 1, 1, 47, 2505, 145, None)
|
||||||
val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, "0000", 1, 48, 2506, 146)
|
val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 0, 1, 48, 2506, 146, None)
|
||||||
val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, "0001", 1, 49, 2507, 147)
|
val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, 0, 1, 1, 49, 2507, 147, None)
|
||||||
|
|
||||||
val updates = Map(
|
val updates = Map(
|
||||||
ChannelDesc(ShortChannelId(1L), a, b) -> uab,
|
ChannelDesc(ShortChannelId(1L), a, b) -> uab,
|
||||||
|
@ -339,7 +336,7 @@ object RouteCalculationSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
def makeUpdate(shortChannelId: Long, nodeId1: PublicKey, nodeId2: PublicKey, feeBaseMsat: Int, feeProportionalMillionth: Int): (ChannelDesc, ChannelUpdate) =
|
def makeUpdate(shortChannelId: Long, nodeId1: PublicKey, nodeId2: PublicKey, feeBaseMsat: Int, feeProportionalMillionth: Int): (ChannelDesc, ChannelUpdate) =
|
||||||
(ChannelDesc(ShortChannelId(shortChannelId), nodeId1, nodeId2) -> ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(shortChannelId), 0L, "0000", 1, 42, feeBaseMsat, feeProportionalMillionth))
|
(ChannelDesc(ShortChannelId(shortChannelId), nodeId1, nodeId2) -> ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(shortChannelId), 0L, 0, 0, 1, 42, feeBaseMsat, feeProportionalMillionth, None))
|
||||||
|
|
||||||
|
|
||||||
def makeGraph(updates: Map[ChannelDesc, ChannelUpdate]) = {
|
def makeGraph(updates: Map[ChannelDesc, ChannelUpdate]) = {
|
||||||
|
|
|
@ -28,40 +28,42 @@ import fr.acinq.eclair.crypto.TransportHandler
|
||||||
import fr.acinq.eclair.io.Peer.{InvalidSignature, PeerRoutingMessage}
|
import fr.acinq.eclair.io.Peer.{InvalidSignature, PeerRoutingMessage}
|
||||||
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
|
||||||
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
|
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
|
||||||
import fr.acinq.eclair.wire.Error
|
import fr.acinq.eclair.transactions.Scripts
|
||||||
import fr.acinq.eclair.{ShortChannelId, randomKey}
|
import fr.acinq.eclair.wire.QueryShortChannelIds
|
||||||
import org.junit.runner.RunWith
|
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
|
import scala.collection.SortedSet
|
||||||
|
import scala.compat.Platform
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by PM on 29/08/2016.
|
* Created by PM on 29/08/2016.
|
||||||
*/
|
*/
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RouterSpec extends BaseRouterSpec {
|
class RouterSpec extends BaseRouterSpec {
|
||||||
|
|
||||||
test("properly announce valid new channels and ignore invalid ones") { case (router, watcher) =>
|
test("properly announce valid new channels and ignore invalid ones") { fixture =>
|
||||||
|
import fixture._
|
||||||
val eventListener = TestProbe()
|
val eventListener = TestProbe()
|
||||||
system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent])
|
system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent])
|
||||||
|
|
||||||
val channelId_ac = ShortChannelId(420000, 5, 0)
|
val channelId_ac = ShortChannelId(420000, 5, 0)
|
||||||
val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c)
|
val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c)
|
||||||
val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId_ac, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val update_ac = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId_ac, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L)
|
||||||
// a-x will not be found
|
// a-x will not be found
|
||||||
val priv_x = randomKey
|
val priv_x = randomKey
|
||||||
val chan_ax = channelAnnouncement(ShortChannelId(42001), priv_a, priv_x, priv_funding_a, randomKey)
|
val chan_ax = channelAnnouncement(ShortChannelId(42001), priv_a, priv_x, priv_funding_a, randomKey)
|
||||||
val update_ax = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_x.publicKey, chan_ax.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val update_ax = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_x.publicKey, chan_ax.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L)
|
||||||
// a-y will have an invalid script
|
// a-y will have an invalid script
|
||||||
val priv_y = randomKey
|
val priv_y = randomKey
|
||||||
val priv_funding_y = randomKey
|
val priv_funding_y = randomKey
|
||||||
val chan_ay = channelAnnouncement(ShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y)
|
val chan_ay = channelAnnouncement(ShortChannelId(42002), priv_a, priv_y, priv_funding_a, priv_funding_y)
|
||||||
val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val update_ay = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_y.publicKey, chan_ay.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L)
|
||||||
// a-z will be spent
|
// a-z will be spent
|
||||||
val priv_z = randomKey
|
val priv_z = randomKey
|
||||||
val priv_funding_z = randomKey
|
val priv_funding_z = randomKey
|
||||||
val chan_az = channelAnnouncement(ShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z)
|
val chan_az = channelAnnouncement(ShortChannelId(42003), priv_a, priv_z, priv_funding_a, priv_funding_z)
|
||||||
val update_az = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_z.publicKey, chan_az.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
|
val update_az = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_z.publicKey, chan_az.shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L)
|
||||||
|
|
||||||
router ! PeerRoutingMessage(null, remoteNodeId, chan_ac)
|
router ! PeerRoutingMessage(null, remoteNodeId, chan_ac)
|
||||||
router ! PeerRoutingMessage(null, remoteNodeId, chan_ax)
|
router ! PeerRoutingMessage(null, remoteNodeId, chan_ax)
|
||||||
|
@ -86,7 +88,8 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
//eventListener.expectMsg(ChannelDiscovered(chan_ac, Satoshi(1000000)))
|
//eventListener.expectMsg(ChannelDiscovered(chan_ac, Satoshi(1000000)))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("properly announce lost channels and nodes") { case (router, _) =>
|
test("properly announce lost channels and nodes") { fixture =>
|
||||||
|
import fixture._
|
||||||
val eventListener = TestProbe()
|
val eventListener = TestProbe()
|
||||||
system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent])
|
system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent])
|
||||||
|
|
||||||
|
@ -110,7 +113,8 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ignore("handle bad signature for ChannelAnnouncement") { case (router, _) =>
|
ignore("handle bad signature for ChannelAnnouncement") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val channelId_ac = ShortChannelId(420000, 5, 0)
|
val channelId_ac = ShortChannelId(420000, 5, 0)
|
||||||
val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c)
|
val chan_ac = channelAnnouncement(channelId_ac, priv_a, priv_c, priv_funding_a, priv_funding_c)
|
||||||
|
@ -120,7 +124,8 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(InvalidSignature(buggy_chan_ac))
|
sender.expectMsg(InvalidSignature(buggy_chan_ac))
|
||||||
}
|
}
|
||||||
|
|
||||||
ignore("handle bad signature for NodeAnnouncement") { case (router, _) =>
|
ignore("handle bad signature for NodeAnnouncement") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val buggy_ann_a = ann_a.copy(signature = ann_b.signature, timestamp = ann_a.timestamp + 1)
|
val buggy_ann_a = ann_a.copy(signature = ann_b.signature, timestamp = ann_a.timestamp + 1)
|
||||||
sender.send(router, PeerRoutingMessage(null, remoteNodeId, buggy_ann_a))
|
sender.send(router, PeerRoutingMessage(null, remoteNodeId, buggy_ann_a))
|
||||||
|
@ -128,7 +133,8 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(InvalidSignature(buggy_ann_a))
|
sender.expectMsg(InvalidSignature(buggy_ann_a))
|
||||||
}
|
}
|
||||||
|
|
||||||
ignore("handle bad signature for ChannelUpdate") { case (router, _) =>
|
ignore("handle bad signature for ChannelUpdate") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val buggy_channelUpdate_ab = channelUpdate_ab.copy(signature = ann_b.signature, timestamp = channelUpdate_ab.timestamp + 1)
|
val buggy_channelUpdate_ab = channelUpdate_ab.copy(signature = ann_b.signature, timestamp = channelUpdate_ab.timestamp + 1)
|
||||||
sender.send(router, PeerRoutingMessage(null, remoteNodeId, buggy_channelUpdate_ab))
|
sender.send(router, PeerRoutingMessage(null, remoteNodeId, buggy_channelUpdate_ab))
|
||||||
|
@ -136,28 +142,32 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
sender.expectMsg(InvalidSignature(buggy_channelUpdate_ab))
|
sender.expectMsg(InvalidSignature(buggy_channelUpdate_ab))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route not found (unreachable target)") { case (router, _) =>
|
test("route not found (unreachable target)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
// no route a->f
|
// no route a->f
|
||||||
sender.send(router, RouteRequest(a, f))
|
sender.send(router, RouteRequest(a, f))
|
||||||
sender.expectMsg(Failure(RouteNotFound))
|
sender.expectMsg(Failure(RouteNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route not found (non-existing source)") { case (router, _) =>
|
test("route not found (non-existing source)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
// no route a->f
|
// no route a->f
|
||||||
sender.send(router, RouteRequest(randomKey.publicKey, f))
|
sender.send(router, RouteRequest(randomKey.publicKey, f))
|
||||||
sender.expectMsg(Failure(RouteNotFound))
|
sender.expectMsg(Failure(RouteNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route not found (non-existing target)") { case (router, _) =>
|
test("route not found (non-existing target)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
// no route a->f
|
// no route a->f
|
||||||
sender.send(router, RouteRequest(a, randomKey.publicKey))
|
sender.send(router, RouteRequest(a, randomKey.publicKey))
|
||||||
sender.expectMsg(Failure(RouteNotFound))
|
sender.expectMsg(Failure(RouteNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route found") { case (router, _) =>
|
test("route found") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(router, RouteRequest(a, d))
|
sender.send(router, RouteRequest(a, d))
|
||||||
val res = sender.expectMsgType[RouteResponse]
|
val res = sender.expectMsgType[RouteResponse]
|
||||||
|
@ -165,7 +175,8 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
assert(res.hops.last.nextNodeId === d)
|
assert(res.hops.last.nextNodeId === d)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route found (with extra routing info)") { case (router, _) =>
|
test("route found (with extra routing info)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
val x = randomKey.publicKey
|
val x = randomKey.publicKey
|
||||||
val y = randomKey.publicKey
|
val y = randomKey.publicKey
|
||||||
|
@ -179,21 +190,23 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
assert(res.hops.last.nextNodeId === z)
|
assert(res.hops.last.nextNodeId === z)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("route not found (channel disabled)") { case (router, _) =>
|
test("route not found (channel disabled)") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(router, RouteRequest(a, d))
|
sender.send(router, RouteRequest(a, d))
|
||||||
val res = sender.expectMsgType[RouteResponse]
|
val res = sender.expectMsgType[RouteResponse]
|
||||||
assert(res.hops.map(_.nodeId).toList === a :: b :: c :: Nil)
|
assert(res.hops.map(_.nodeId).toList === a :: b :: c :: Nil)
|
||||||
assert(res.hops.last.nextNodeId === d)
|
assert(res.hops.last.nextNodeId === d)
|
||||||
|
|
||||||
val channelUpdate_cd1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4, enable = false)
|
val channelUpdate_cd1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4, htlcMaximumMsat = 500000000L, enable = false)
|
||||||
sender.send(router, PeerRoutingMessage(null, remoteNodeId, channelUpdate_cd1))
|
sender.send(router, PeerRoutingMessage(null, remoteNodeId, channelUpdate_cd1))
|
||||||
sender.expectMsg(TransportHandler.ReadAck(channelUpdate_cd1))
|
sender.expectMsg(TransportHandler.ReadAck(channelUpdate_cd1))
|
||||||
sender.send(router, RouteRequest(a, d))
|
sender.send(router, RouteRequest(a, d))
|
||||||
sender.expectMsg(Failure(RouteNotFound))
|
sender.expectMsg(Failure(RouteNotFound))
|
||||||
}
|
}
|
||||||
|
|
||||||
test("temporary channel exclusion") { case (router, _) =>
|
test("temporary channel exclusion") { fixture =>
|
||||||
|
import fixture._
|
||||||
val sender = TestProbe()
|
val sender = TestProbe()
|
||||||
sender.send(router, RouteRequest(a, d))
|
sender.send(router, RouteRequest(a, d))
|
||||||
sender.expectMsgType[RouteResponse]
|
sender.expectMsgType[RouteResponse]
|
||||||
|
@ -211,4 +224,57 @@ class RouterSpec extends BaseRouterSpec {
|
||||||
sender.expectMsgType[RouteResponse]
|
sender.expectMsgType[RouteResponse]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ignore("export graph in dot format") { fixture =>
|
||||||
|
import fixture._
|
||||||
|
val sender = TestProbe()
|
||||||
|
sender.send(router, 'dot)
|
||||||
|
val dot = sender.expectMsgType[String]
|
||||||
|
/*Files.write(dot.getBytes(), new File("graph.dot"))
|
||||||
|
|
||||||
|
import scala.sys.process._
|
||||||
|
val input = new ByteArrayInputStream(dot.getBytes)
|
||||||
|
val output = new ByteArrayOutputStream()
|
||||||
|
"dot -Tpng" #< input #> output !
|
||||||
|
val img = output.toByteArray
|
||||||
|
Files.write(img, new File("graph.png"))*/
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore("send routing state") { fixture =>
|
||||||
|
import fixture._
|
||||||
|
val sender = TestProbe()
|
||||||
|
sender.send(router, GetRoutingState)
|
||||||
|
val state = sender.expectMsgType[RoutingState]
|
||||||
|
assert(state.channels.size == 4)
|
||||||
|
assert(state.nodes.size == 6)
|
||||||
|
assert(state.updates.size == 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore("ask for channels that we marked as stale for which we receive a new update") { fixture =>
|
||||||
|
import fixture._
|
||||||
|
val blockHeight = Globals.blockCount.get().toInt - 2020
|
||||||
|
val channelId = ShortChannelId(blockHeight, 5, 0)
|
||||||
|
val announcement = channelAnnouncement(channelId, priv_a, priv_c, priv_funding_a, priv_funding_c)
|
||||||
|
val timestamp = Platform.currentTime / 1000 - 1209600 - 1
|
||||||
|
val update = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, cltvExpiryDelta = 7, htlcMinimumMsat = 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, htlcMaximumMsat = 5, timestamp = timestamp)
|
||||||
|
val probe = TestProbe()
|
||||||
|
probe.ignoreMsg { case _: TransportHandler.ReadAck => true }
|
||||||
|
probe.send(router, PeerRoutingMessage(null, remoteNodeId, announcement))
|
||||||
|
watcher.expectMsgType[ValidateRequest]
|
||||||
|
probe.send(router, PeerRoutingMessage(null, remoteNodeId, update))
|
||||||
|
watcher.send(router, ValidateResult(announcement, Some(Transaction(version = 0, txIn = Nil, txOut = TxOut(Satoshi(1000000), write(pay2wsh(Scripts.multiSig2of2(funding_a, funding_c)))) :: Nil, lockTime = 0)), true, None))
|
||||||
|
|
||||||
|
probe.send(router, TickPruneStaleChannels)
|
||||||
|
val sender = TestProbe()
|
||||||
|
sender.send(router, GetRoutingState)
|
||||||
|
val state = sender.expectMsgType[RoutingState]
|
||||||
|
|
||||||
|
|
||||||
|
val update1 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, c, channelId, cltvExpiryDelta = 7, htlcMinimumMsat = 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, htlcMaximumMsat = 500000000L, timestamp = Platform.currentTime / 1000)
|
||||||
|
|
||||||
|
// we want to make sure that transport receives the query
|
||||||
|
val transport = TestProbe()
|
||||||
|
probe.send(router, PeerRoutingMessage(transport.ref, remoteNodeId, update1))
|
||||||
|
val query = transport.expectMsgType[QueryShortChannelIds]
|
||||||
|
assert(ChannelRangeQueries.decodeShortChannelIds(query.data)._2 == SortedSet(channelId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,12 @@ import fr.acinq.eclair._
|
||||||
import fr.acinq.eclair.crypto.TransportHandler
|
import fr.acinq.eclair.crypto.TransportHandler
|
||||||
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
|
import fr.acinq.eclair.io.Peer.PeerRoutingMessage
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuiteLike
|
import org.scalatest.FunSuiteLike
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.collection.immutable.TreeMap
|
import scala.collection.immutable.TreeMap
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RoutingSyncExSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
class RoutingSyncExSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
||||||
import RoutingSyncSpec.makeFakeRoutingInfo
|
import RoutingSyncSpec.makeFakeRoutingInfo
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,11 @@ import fr.acinq.eclair.io.Peer.PeerRoutingMessage
|
||||||
import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnouncement}
|
import fr.acinq.eclair.router.Announcements.{makeChannelUpdate, makeNodeAnnouncement}
|
||||||
import fr.acinq.eclair.router.BaseRouterSpec.channelAnnouncement
|
import fr.acinq.eclair.router.BaseRouterSpec.channelAnnouncement
|
||||||
import fr.acinq.eclair.wire._
|
import fr.acinq.eclair.wire._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuiteLike
|
import org.scalatest.FunSuiteLike
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
||||||
|
|
||||||
import RoutingSyncSpec.makeFakeRoutingInfo
|
import RoutingSyncSpec.makeFakeRoutingInfo
|
||||||
|
@ -37,7 +35,7 @@ class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
|
||||||
val QueryChannelRange(chainHash, firstBlockNum, numberOfBlocks) = sender.expectMsgType[QueryChannelRange]
|
val QueryChannelRange(chainHash, firstBlockNum, numberOfBlocks) = sender.expectMsgType[QueryChannelRange]
|
||||||
sender.expectMsgType[GossipTimestampFilter]
|
sender.expectMsgType[GossipTimestampFilter]
|
||||||
|
|
||||||
// split our anwser in 3 blocks
|
// split our answer in 3 blocks
|
||||||
val List(block1) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.take(100), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
val List(block1) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.take(100), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
||||||
val List(block2) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.drop(100).take(100), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
val List(block2) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.drop(100).take(100), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
||||||
val List(block3) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.drop(200).take(150), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
val List(block3) = ChannelRangeQueries.encodeShortChannelIds(firstBlockNum, numberOfBlocks, shortChannelIds.drop(200).take(150), ChannelRangeQueries.UNCOMPRESSED_FORMAT)
|
||||||
|
@ -118,8 +116,8 @@ object RoutingSyncSpec {
|
||||||
val (priv_a, priv_b, priv_funding_a, priv_funding_b) = (randomKey, randomKey, randomKey, randomKey)
|
val (priv_a, priv_b, priv_funding_a, priv_funding_b) = (randomKey, randomKey, randomKey, randomKey)
|
||||||
val channelAnn_ab = channelAnnouncement(shortChannelId, priv_a, priv_b, priv_funding_a, priv_funding_b)
|
val channelAnn_ab = channelAnnouncement(shortChannelId, priv_a, priv_b, priv_funding_a, priv_funding_b)
|
||||||
val TxCoordinates(blockHeight, _, _) = ShortChannelId.coordinates(shortChannelId)
|
val TxCoordinates(blockHeight, _, _) = ShortChannelId.coordinates(shortChannelId)
|
||||||
val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, timestamp = blockHeight)
|
val channelUpdate_ab = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_a, priv_b.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L, timestamp = blockHeight)
|
||||||
val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, timestamp = blockHeight)
|
val channelUpdate_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.publicKey, shortChannelId, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, 500000000L, timestamp = blockHeight)
|
||||||
val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Alice.nodeParams.color, List())
|
val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Alice.nodeParams.color, List())
|
||||||
val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Bob.nodeParams.color, List())
|
val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Bob.nodeParams.color, List())
|
||||||
(channelAnn_ab, channelUpdate_ab, channelUpdate_ba, nodeAnnouncement_a, nodeAnnouncement_b)
|
(channelAnn_ab, channelUpdate_ab, channelUpdate_ba, nodeAnnouncement_a, nodeAnnouncement_b)
|
||||||
|
|
|
@ -19,11 +19,9 @@ package fr.acinq.eclair.transactions
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair.transactions.Scripts._
|
import fr.acinq.eclair.transactions.Scripts._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ClaimReceivedHtlcSpec extends FunSuite {
|
class ClaimReceivedHtlcSpec extends FunSuite {
|
||||||
|
|
||||||
object Alice {
|
object Alice {
|
||||||
|
|
|
@ -19,11 +19,9 @@ package fr.acinq.eclair.transactions
|
||||||
import fr.acinq.bitcoin.Crypto.PrivateKey
|
import fr.acinq.bitcoin.Crypto.PrivateKey
|
||||||
import fr.acinq.bitcoin._
|
import fr.acinq.bitcoin._
|
||||||
import fr.acinq.eclair.transactions.Scripts._
|
import fr.acinq.eclair.transactions.Scripts._
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class ClaimSentHtlcSpec extends FunSuite {
|
class ClaimSentHtlcSpec extends FunSuite {
|
||||||
|
|
||||||
object Alice {
|
object Alice {
|
||||||
|
|
|
@ -18,11 +18,9 @@ package fr.acinq.eclair.transactions
|
||||||
|
|
||||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||||
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
|
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
import org.scalatest.junit.JUnitRunner
|
|
||||||
|
|
||||||
@RunWith(classOf[JUnitRunner])
|
|
||||||
class CommitmentSpecSpec extends FunSuite {
|
class CommitmentSpecSpec extends FunSuite {
|
||||||
test("add, fulfill and fail htlcs from the sender side") {
|
test("add, fulfill and fail htlcs from the sender side") {
|
||||||
val spec = CommitmentSpec(htlcs = Set(), feeratePerKw = 1000, toLocalMsat = 5000 * 1000, toRemoteMsat = 0)
|
val spec = CommitmentSpec(htlcs = Set(), feeratePerKw = 1000, toLocalMsat = 5000 * 1000, toRemoteMsat = 0)
|
||||||
|
|
|
@ -22,11 +22,12 @@ import fr.acinq.eclair.channel.Helpers.Funding
|
||||||
import fr.acinq.eclair.crypto.Generators
|
import fr.acinq.eclair.crypto.Generators
|
||||||
import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo}
|
import fr.acinq.eclair.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo}
|
||||||
import fr.acinq.eclair.wire.UpdateAddHtlc
|
import fr.acinq.eclair.wire.UpdateAddHtlc
|
||||||
|
import grizzled.slf4j.Logging
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
|
|
||||||
import scala.io.Source
|
import scala.io.Source
|
||||||
|
|
||||||
class TestVectorsSpec extends FunSuite {
|
class TestVectorsSpec extends FunSuite with Logging {
|
||||||
|
|
||||||
val results = collection.mutable.HashMap.empty[String, Map[String, String]]
|
val results = collection.mutable.HashMap.empty[String, Map[String, String]]
|
||||||
val current = collection.mutable.HashMap.empty[String, String]
|
val current = collection.mutable.HashMap.empty[String, String]
|
||||||
|
@ -120,7 +121,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
|
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
|
||||||
val fundingAmount = fundingTx.txOut(0).amount
|
val fundingAmount = fundingTx.txOut(0).amount
|
||||||
println(s"# funding-tx: $fundingTx}")
|
logger.info(s"# funding-tx: $fundingTx}")
|
||||||
|
|
||||||
|
|
||||||
val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey)
|
val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey)
|
||||||
|
@ -128,18 +129,18 @@ class TestVectorsSpec extends FunSuite {
|
||||||
val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, true, Local.payment_basepoint, Remote.payment_basepoint)
|
val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, true, Local.payment_basepoint, Remote.payment_basepoint)
|
||||||
assert(obscured_tx_number === (0x2bb038521914L ^ 42L))
|
assert(obscured_tx_number === (0x2bb038521914L ^ 42L))
|
||||||
|
|
||||||
println(s"local_payment_basepoint: ${Local.payment_basepoint}")
|
logger.info(s"local_payment_basepoint: ${Local.payment_basepoint}")
|
||||||
println(s"remote_payment_basepoint: ${Remote.payment_basepoint}")
|
logger.info(s"remote_payment_basepoint: ${Remote.payment_basepoint}")
|
||||||
println(s"local_funding_privkey: ${Local.funding_privkey}")
|
logger.info(s"local_funding_privkey: ${Local.funding_privkey}")
|
||||||
println(s"local_funding_pubkey: ${Local.funding_pubkey}")
|
logger.info(s"local_funding_pubkey: ${Local.funding_pubkey}")
|
||||||
println(s"remote_funding_privkey: ${Remote.funding_privkey}")
|
logger.info(s"remote_funding_privkey: ${Remote.funding_privkey}")
|
||||||
println(s"remote_funding_pubkey: ${Remote.funding_pubkey}")
|
logger.info(s"remote_funding_pubkey: ${Remote.funding_pubkey}")
|
||||||
println(s"local_secretkey: ${Local.payment_privkey}")
|
logger.info(s"local_secretkey: ${Local.payment_privkey}")
|
||||||
println(s"localkey: ${Local.payment_privkey.publicKey}")
|
logger.info(s"localkey: ${Local.payment_privkey.publicKey}")
|
||||||
println(s"remotekey: ${Remote.payment_privkey.publicKey}")
|
logger.info(s"remotekey: ${Remote.payment_privkey.publicKey}")
|
||||||
println(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}")
|
logger.info(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}")
|
||||||
println(s"local_revocation_key: ${Local.revocation_pubkey}")
|
logger.info(s"local_revocation_key: ${Local.revocation_pubkey}")
|
||||||
println(s"# funding wscript = ${commitmentInput.redeemScript}")
|
logger.info(s"# funding wscript = ${commitmentInput.redeemScript}")
|
||||||
assert(commitmentInput.redeemScript == BinaryData("5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae"))
|
assert(commitmentInput.redeemScript == BinaryData("5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae"))
|
||||||
|
|
||||||
val paymentPreimages = Seq(
|
val paymentPreimages = Seq(
|
||||||
|
@ -168,17 +169,16 @@ class TestVectorsSpec extends FunSuite {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i <- 0 until htlcs.length) {
|
for (i <- 0 until htlcs.length) {
|
||||||
println(s"htlc $i direction: ${dir2string(htlcs(i).direction)}")
|
logger.info(s"htlc $i direction: ${dir2string(htlcs(i).direction)}")
|
||||||
println(s"htlc $i amount_msat: ${htlcs(i).add.amountMsat}")
|
logger.info(s"htlc $i amount_msat: ${htlcs(i).add.amountMsat}")
|
||||||
println(s"htlc $i expiry: ${htlcs(i).add.expiry}")
|
logger.info(s"htlc $i expiry: ${htlcs(i).add.expiry}")
|
||||||
println(s"htlc $i payment_preimage: ${paymentPreimages(i)}")
|
logger.info(s"htlc $i payment_preimage: ${paymentPreimages(i)}")
|
||||||
}
|
}
|
||||||
println()
|
|
||||||
|
|
||||||
def run(spec: CommitmentSpec) = {
|
def run(spec: CommitmentSpec) = {
|
||||||
println(s"to_local_msat: ${spec.toLocalMsat}")
|
logger.info(s"to_local_msat: ${spec.toLocalMsat}")
|
||||||
println(s"to_remote_msat: ${spec.toRemoteMsat}")
|
logger.info(s"to_remote_msat: ${spec.toRemoteMsat}")
|
||||||
println(s"local_feerate_per_kw: ${spec.feeratePerKw}")
|
logger.info(s"local_feerate_per_kw: ${spec.feeratePerKw}")
|
||||||
|
|
||||||
val commitTx = {
|
val commitTx = {
|
||||||
val tx = Transactions.makeCommitTx(
|
val tx = Transactions.makeCommitTx(
|
||||||
|
@ -197,16 +197,16 @@ class TestVectorsSpec extends FunSuite {
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)
|
val baseFee = Transactions.commitTxFee(Local.dustLimit, spec)
|
||||||
println(s"# base commitment transaction fee = ${baseFee.toLong}")
|
logger.info(s"# base commitment transaction fee = ${baseFee.toLong}")
|
||||||
val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum
|
val actualFee = fundingAmount - commitTx.tx.txOut.map(_.amount).sum
|
||||||
println(s"# actual commitment transaction fee = ${actualFee.toLong}")
|
logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}")
|
||||||
commitTx.tx.txOut.map(txOut => {
|
commitTx.tx.txOut.map(txOut => {
|
||||||
txOut.publicKeyScript.length match {
|
txOut.publicKeyScript.length match {
|
||||||
case 22 => println(s"# to-remote amount ${txOut.amount.toLong} P2WPKH(${Remote.payment_privkey.publicKey})")
|
case 22 => logger.info(s"# to-remote amount ${txOut.amount.toLong} P2WPKH(${Remote.payment_privkey.publicKey})")
|
||||||
case 34 =>
|
case 34 =>
|
||||||
val index = htlcScripts.indexWhere(s => Script.write(Script.pay2wsh(s)) == txOut.publicKeyScript)
|
val index = htlcScripts.indexWhere(s => Script.write(Script.pay2wsh(s)) == txOut.publicKeyScript)
|
||||||
if (index == -1) println(s"# to-local amount ${txOut.amount.toLong} wscript ${Script.write(Scripts.toLocalDelayed(Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey))}")
|
if (index == -1) logger.info(s"# to-local amount ${txOut.amount.toLong} wscript ${Script.write(Scripts.toLocalDelayed(Local.revocation_pubkey, Local.toSelfDelay, Local.delayed_payment_privkey.publicKey))}")
|
||||||
else println(s"# HTLC ${if (htlcs(index).direction == OUT) "offered" else "received"} amount ${txOut.amount.toLong} wscript ${Script.write(htlcScripts(index))}")
|
else logger.info(s"# HTLC ${if (htlcs(index).direction == OUT) "offered" else "received"} amount ${txOut.amount.toLong} wscript ${Script.write(htlcScripts(index))}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -221,14 +221,14 @@ class TestVectorsSpec extends FunSuite {
|
||||||
spec)
|
spec)
|
||||||
|
|
||||||
val local_sig = Transactions.sign(tx, Local.funding_privkey)
|
val local_sig = Transactions.sign(tx, Local.funding_privkey)
|
||||||
println(s"# local_signature = ${toHexString(local_sig.dropRight(1))}")
|
logger.info(s"# local_signature = ${toHexString(local_sig.dropRight(1))}")
|
||||||
val remote_sig = Transactions.sign(tx, Remote.funding_privkey)
|
val remote_sig = Transactions.sign(tx, Remote.funding_privkey)
|
||||||
println(s"remote_signature: ${toHexString(remote_sig.dropRight(1))}")
|
logger.info(s"remote_signature: ${toHexString(remote_sig.dropRight(1))}")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(Transactions.getCommitTxNumber(commitTx.tx, true, Local.payment_basepoint, Remote.payment_basepoint) === Local.commitTxNumber)
|
assert(Transactions.getCommitTxNumber(commitTx.tx, true, Local.payment_basepoint, Remote.payment_basepoint) === Local.commitTxNumber)
|
||||||
Transaction.correctlySpends(commitTx.tx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
Transaction.correctlySpends(commitTx.tx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||||
println(s"output commit_tx: ${commitTx.tx}")
|
logger.info(s"output commit_tx: ${commitTx.tx}")
|
||||||
|
|
||||||
val (unsignedHtlcTimeoutTxs, unsignedHtlcSuccessTxs) = Transactions.makeHtlcTxs(
|
val (unsignedHtlcTimeoutTxs, unsignedHtlcSuccessTxs) = Transactions.makeHtlcTxs(
|
||||||
commitTx.tx,
|
commitTx.tx,
|
||||||
|
@ -238,7 +238,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key
|
Local.payment_privkey.publicKey, Remote.payment_privkey.publicKey, // note: we have payment_key = htlc_key
|
||||||
spec)
|
spec)
|
||||||
|
|
||||||
println(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}")
|
logger.info(s"num_htlcs: ${(unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).length}")
|
||||||
val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index)
|
val htlcTxs: Seq[TransactionWithInputInfo] = (unsignedHtlcTimeoutTxs ++ unsignedHtlcSuccessTxs).sortBy(_.input.outPoint.index)
|
||||||
|
|
||||||
|
|
||||||
|
@ -246,13 +246,13 @@ class TestVectorsSpec extends FunSuite {
|
||||||
case tx: HtlcSuccessTx =>
|
case tx: HtlcSuccessTx =>
|
||||||
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
||||||
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
||||||
println(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
|
logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
|
||||||
println(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
|
logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
|
||||||
case tx: HtlcTimeoutTx =>
|
case tx: HtlcTimeoutTx =>
|
||||||
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
||||||
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
||||||
println(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
|
logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
|
||||||
println(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
|
logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val signedTxs = htlcTxs collect {
|
val signedTxs = htlcTxs collect {
|
||||||
|
@ -264,27 +264,26 @@ class TestVectorsSpec extends FunSuite {
|
||||||
val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage)
|
val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage)
|
||||||
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||||
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
||||||
println(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
|
logger.info(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
|
||||||
println(s"output htlc_success_tx ${htlcIndex}: ${tx1.tx}")
|
logger.info(s"output htlc_success_tx ${htlcIndex}: ${tx1.tx}")
|
||||||
tx1
|
tx1
|
||||||
case tx: HtlcTimeoutTx =>
|
case tx: HtlcTimeoutTx =>
|
||||||
val localSig = Transactions.sign(tx, Local.payment_privkey)
|
val localSig = Transactions.sign(tx, Local.payment_privkey)
|
||||||
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
|
||||||
val tx1 = Transactions.addSigs(tx, localSig, remoteSig)
|
val tx1 = Transactions.addSigs(tx, localSig, remoteSig)
|
||||||
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
|
||||||
println(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
|
logger.info(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
|
||||||
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
|
||||||
println(s"output htlc_timeout_tx ${htlcIndex}: ${tx1.tx}")
|
logger.info(s"output htlc_timeout_tx ${htlcIndex}: ${tx1.tx}")
|
||||||
tx1
|
tx1
|
||||||
}
|
}
|
||||||
|
|
||||||
println
|
|
||||||
(commitTx, signedTxs)
|
(commitTx, signedTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
test("simple commitment tx with no HTLCs") {
|
test("simple commitment tx with no HTLCs") {
|
||||||
val name = "simple commitment tx with no HTLCs"
|
val name = "simple commitment tx with no HTLCs"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val spec = CommitmentSpec(htlcs = Set.empty, feeratePerKw = 15000, toLocalMsat = 7000000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = Set.empty, feeratePerKw = 15000, toLocalMsat = 7000000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
val (commitTx, htlcTxs) = run(spec)
|
val (commitTx, htlcTxs) = run(spec)
|
||||||
|
@ -295,7 +294,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with all 5 htlcs untrimmed (minimum feerate)") {
|
test("commitment tx with all 5 htlcs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with all 5 htlcs untrimmed (minimum feerate)"
|
val name = "commitment tx with all 5 htlcs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 0, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 0, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
val (commitTx, htlcTxs) = run(spec)
|
val (commitTx, htlcTxs) = run(spec)
|
||||||
|
@ -305,7 +304,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 7 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 7 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 7 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 7 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -319,7 +318,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 6 outputs untrimmed (minimum feerate)") {
|
test("commitment tx with 6 outputs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 6 outputs untrimmed (minimum feerate)"
|
val name = "commitment tx with 6 outputs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -333,7 +332,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 6 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 6 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 6 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 6 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 1454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 1454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -347,7 +346,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 5 outputs untrimmed (minimum feerate)") {
|
test("commitment tx with 5 outputs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 5 outputs untrimmed (minimum feerate)"
|
val name = "commitment tx with 5 outputs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 1454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 1454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -361,7 +360,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 5 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 5 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 5 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 5 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 1454999 / Transactions.htlcTimeoutWeight
|
val feeratePerKw = 1454999 / Transactions.htlcTimeoutWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -375,7 +374,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 4 outputs untrimmed (minimum feerate)") {
|
test("commitment tx with 4 outputs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 4 outputs untrimmed (minimum feerate)"
|
val name = "commitment tx with 4 outputs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 1454999 / Transactions.htlcTimeoutWeight
|
val feeratePerKw = 1454999 / Transactions.htlcTimeoutWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -389,7 +388,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 4 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 4 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 4 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 4 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 2454999 / Transactions.htlcTimeoutWeight
|
val feeratePerKw = 2454999 / Transactions.htlcTimeoutWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -403,7 +402,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 3 outputs untrimmed (minimum feerate)") {
|
test("commitment tx with 3 outputs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 3 outputs untrimmed (minimum feerate)"
|
val name = "commitment tx with 3 outputs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 2454999 / Transactions.htlcTimeoutWeight
|
val feeratePerKw = 2454999 / Transactions.htlcTimeoutWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -417,7 +416,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 3 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 3 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 3 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 3 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 3454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 3454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -431,7 +430,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 2 outputs untrimmed (minimum feerate)") {
|
test("commitment tx with 2 outputs untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 2 outputs untrimmed (minimum feerate)"
|
val name = "commitment tx with 2 outputs untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val feeratePerKw = 3454999 / Transactions.htlcSuccessWeight
|
val feeratePerKw = 3454999 / Transactions.htlcSuccessWeight
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = feeratePerKw + 1, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
|
@ -445,7 +444,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 2 outputs untrimmed (maximum feerate)") {
|
test("commitment tx with 2 outputs untrimmed (maximum feerate)") {
|
||||||
val name = "commitment tx with 2 outputs untrimmed (maximum feerate)"
|
val name = "commitment tx with 2 outputs untrimmed (maximum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651180, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651180, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
val (commitTx, htlcTxs) = run(spec)
|
val (commitTx, htlcTxs) = run(spec)
|
||||||
|
@ -458,7 +457,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with 1 output untrimmed (minimum feerate)") {
|
test("commitment tx with 1 output untrimmed (minimum feerate)") {
|
||||||
val name = "commitment tx with 1 output untrimmed (minimum feerate)"
|
val name = "commitment tx with 1 output untrimmed (minimum feerate)"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651181, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651181, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
val (commitTx, htlcTxs) = run(spec)
|
val (commitTx, htlcTxs) = run(spec)
|
||||||
|
@ -471,7 +470,7 @@ class TestVectorsSpec extends FunSuite {
|
||||||
|
|
||||||
test("commitment tx with fee greater than funder amount") {
|
test("commitment tx with fee greater than funder amount") {
|
||||||
val name = "commitment tx with fee greater than funder amount"
|
val name = "commitment tx with fee greater than funder amount"
|
||||||
println(s"name: $name")
|
logger.info(s"name: $name")
|
||||||
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651936, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
val spec = CommitmentSpec(htlcs = htlcs.toSet, feeratePerKw = 9651936, toLocalMsat = 6988000000L, toRemoteMsat = 3000000000L)
|
||||||
|
|
||||||
val (commitTx, htlcTxs) = run(spec)
|
val (commitTx, htlcTxs) = run(spec)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue