1
0
Fork 0
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 by 2c1811d: 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 by 7a4f175 (#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:
Pierre-Marie Padiou 2018-10-25 17:50:48 +02:00 committed by GitHub
parent 2379807f2e
commit dae5cc718a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 4314 additions and 4203 deletions

View file

@ -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:

View file

@ -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

View file

@ -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 .

View file

@ -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

View file

@ -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.

View file

@ -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>

View file

@ -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")
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) } ++

View file

@ -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)]
} }

View file

@ -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)

View file

@ -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
} }

View file

@ -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)
}
} }

View file

@ -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)
} }
} }

View file

@ -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)

View file

@ -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 =>

View file

@ -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)
}*/ }*/

View file

@ -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)

View file

@ -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))
} }

View file

@ -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]
} }

View file

@ -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

View file

@ -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>

View file

@ -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") {

View file

@ -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") {

View file

@ -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))
} }
} }

View file

@ -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") {

View file

@ -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") {

View file

@ -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

View file

@ -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") {

View file

@ -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") {

View file

@ -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))

View file

@ -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

View file

@ -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]
}
}

View file

@ -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()

View file

@ -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._

View file

@ -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")
} }
} }
} }

View file

@ -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)

View file

@ -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._

View file

@ -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]

View file

@ -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

View file

@ -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))

View file

@ -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._

View file

@ -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))
} }
} }

View file

@ -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

View file

@ -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(

View file

@ -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)
} }
} }

View file

@ -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)
} }
} }

View file

@ -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)
}
} }
} }

View file

@ -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)
} }
} }

View file

@ -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)
} }
} }

View file

@ -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)
} }
} }

View file

@ -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))
} }
} }

View file

@ -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))
} }
} }

View file

@ -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)
}
} }

View file

@ -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))
}
} }
} }

View file

@ -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))))
}
} }
} }

View file

@ -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._

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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._

View file

@ -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",

View file

@ -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._

View file

@ -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._

View file

@ -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)
} }

View file

@ -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)

View file

@ -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)
} }
} }

View file

@ -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)

View file

@ -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:")

View file

@ -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:")

View file

@ -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:")

View file

@ -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)
} }

View file

@ -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)}*/
} }

View file

@ -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"

View file

@ -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"))
} }
} }

View file

@ -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)

View file

@ -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") {

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)
} }

View file

@ -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))
} }
} }

View file

@ -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)))
} }
} }
} }

View file

@ -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

View file

@ -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._

View file

@ -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._

View file

@ -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]) = {

View file

@ -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))
}
} }

View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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)

View file

@ -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