1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 19:37:35 +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
env:
- 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:
- mvn install
cache:

View file

@ -2,7 +2,7 @@
## Requirements
- [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)
- [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
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:
```shell
$ 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
# 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
ARG MAVEN_VERSION=3.3.9
ARG MAVEN_VERSION=3.5.4
ARG USER_HOME_DIR="/root"
ARG SHA=6e3e9c949ab4695a204f74038717aa7b2689b1be94875899ac1b3fe42800ff82
ARG SHA=ce50b1c91364cb77efe3776f756a6d92b76d9038b0a0782f7d53acf1e997a14d
ARG BASE_URL=https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries
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
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
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
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
# 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
# Eclair only needs the eclair-node-*.jar to run
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
: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 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
```
:warning: If you are using Bitcoin Core 0.17.0 you need to add following line to your `bitcoin.conf`:
```
deprecatedrpc=signrawtransaction
```
### Installing Eclair
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
```
: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.chain=mainnet
eclair.bitcoind.rpcport=8332
eclair.bitcoind.rpcuser=<your-bitcoin-core-rpc-user-here>
eclair.bitcoind.rpcpassword=<your-bitcoin-core-rpc-passsword-here>
eclair.bitcoind.rpcuser=<your-mainnet-rpc-user-here>
eclair.bitcoind.rpcpassword=<your-mainnet-rpc-password-here>
```
## Resources
- [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

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_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, balanceMsat: .data.commitments.localCommit.spec.toLocalMsat, capacitySat: .data.commitments.commitInput.txOut.amount.amount } ) 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" ;;
"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)
"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)
*) # Default case.

View file

@ -21,7 +21,7 @@
<parent>
<groupId>fr.acinq.eclair</groupId>
<artifactId>eclair_2.11</artifactId>
<version>0.2-android-beta12</version>
<version>0.2-android-SNAPSHOT</version>
</parent>
<artifactId>eclair-core_2.11</artifactId>
@ -79,10 +79,10 @@
<activeByDefault>true</activeByDefault>
</activation>
<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.md5>1375c9f908b0327d9772d4bff0d9f03f</bitcoind.md5>
<bitcoind.sha1>d0b05b51e1d572c44ef5b2cabbfcb662679cf7cb</bitcoind.sha1>
<bitcoind.md5>c371e383f024c6c45fb255d528a6beec</bitcoind.md5>
<bitcoind.sha1>e6d8ab1f7661a6654fd81e236b9b5fd35a3d4dcb</bitcoind.sha1>
</properties>
</profile>
<profile>
@ -93,10 +93,10 @@
</os>
</activation>
<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.md5>fc10d5cb12a4c3905d97df33f249eb1c</bitcoind.md5>
<bitcoind.sha1>3b1ab75439ca7a9b547827ec3e59c7e61c1f6fcd</bitcoind.sha1>
<bitcoind.md5>bacd87d0c3f65a5acd666e33d094a59e</bitcoind.md5>
<bitcoind.sha1>62cc5bd9ced610bb9e8d4a854396bfe2139e3d0f</bitcoind.sha1>
</properties>
</profile>
<profile>
@ -107,9 +107,9 @@
</os>
</activation>
<properties>
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-win64.zip</bitcoind.url>
<bitcoind.md5>5b9034754752b7e1b3117eaa5434792e</bitcoind.md5>
<bitcoind.sha1>90d72e25782a4b454f5f507a26a3cf0f53baecef</bitcoind.sha1>
<bitcoind.url>https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-win64.zip</bitcoind.url>
<bitcoind.md5>bbde9b1206956d19298034319e9f405e</bitcoind.md5>
<bitcoind.sha1>85e3dc8a9c6f93b1b20cb79fa5850b5ce81da221</bitcoind.sha1>
</properties>
</profile>
</profiles>

View file

@ -190,7 +190,6 @@ class ZmqWatcher(client: ExtendedBitcoinClient)(implicit ec: ExecutionContext =
import scala.concurrent.duration._
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")
}
}

View file

@ -106,13 +106,27 @@ class ExtendedBitcoinClient(val rpcClient: BitcoinJsonRPCClient) {
future
}
def publishTransaction(hex: String)(implicit ec: ExecutionContext): Future[String] =
rpcClient.invoke("sendrawtransaction", hex) collect {
case JString(txid) => txid
}
/**
* Publish a transaction on the bitcoin network.
*
* 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] =
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

View file

@ -187,7 +187,9 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
// we rebuild a channel_update for two reasons:
// - 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
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)
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)
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))
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))
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
context.system.eventStream.publish(ShortChannelIdAssigned(self, d.channelId, shortChannelId))
// 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
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
@ -807,13 +809,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
case Event(TickRefreshChannelUpdate, d: DATA_NORMAL) =>
// periodic refresh is used as a keep alive
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
goto(NORMAL) using store(d.copy(channelUpdate = channelUpdate))
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, 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
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) =>
// we disable the channel
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 {
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")
val extracted = Closing.extractPreimages(d.commitments.localCommit, tx)
extracted map { case (htlc, fulfill) =>
val origin = d.commitments.originChannels(fulfill.id)
log.warning(s"fulfilling htlc #${fulfill.id} paymentHash=${sha256(fulfill.paymentPreimage)} origin=$origin")
relayer ! ForwardFulfill(fulfill, origin, htlc)
d.commitments.originChannels.get(fulfill.id) match {
case Some(origin) =>
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 (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) ++
d.commitments.remoteNextCommitInfo.left.toSeq.flatMap(r => Closing.timedoutHtlcs(r.nextRemoteCommit, Satoshi(d.commitments.remoteParams.dustLimitSatoshis), tx))
timedoutHtlcs.foreach { add =>
val origin = d.commitments.originChannels(add.id)
log.warning(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))
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
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
val overridenHtlcs = Closing.overriddenHtlcs(d.commitments.localCommit, d.commitments.remoteCommit, d.commitments.remoteNextCommitInfo.left.toOption.map(_.nextRemoteCommit), tx)
overridenHtlcs.foreach { add =>
val origin = d.commitments.originChannels(add.id)
log.warning(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))
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
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
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
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
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
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)
}
@ -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 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) =>
d match {
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
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 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 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 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")
@ -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 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 CommandUnavailableInThisState (override val channelId: BinaryData, command: String, state: State) extends ChannelException(channelId, s"cannot execute command=$command in state=$state")
// @formatter:on

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.channel
import akka.event.LoggingAdapter
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.payment.Origin
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
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)
// 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)
// 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)
// 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
val htlcInfos = db.listHtlcHtlcInfos(commitments.channelId, txnumber)
val htlcInfos = db.listHtlcInfos(commitments.channelId, txnumber)
log.info(s"got htlcs=${htlcInfos.size} for txnumber=$txnumber")
val htlcsRedeemScripts = (
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 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 =>
statement.setBytes(1, channelId)
statement.setLong(2, commitmentNumber)

View file

@ -21,7 +21,7 @@ import java.sql.Connection
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi}
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.wire.LightningMessageCodecs.nodeAnnouncementCodec
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.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 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 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 = {
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.setBoolean(2, Announcements.isNode1(u.flags))
statement.setBoolean(2, Announcements.isNode1(u.channelFlags))
statement.setLong(3, u.timestamp)
statement.setBytes(4, u.flags)
statement.setBytes(4, Array(u.messageFlags, u.channelFlags))
statement.setInt(5, u.cltvExpiryDelta)
statement.setLong(6, u.htlcMinimumMsat)
statement.setLong(7, u.feeBaseMsat)
statement.setLong(8, u.feeProportionalMillionths)
setNullableLong(statement, 9, u.htlcMaximumMsat)
statement.executeUpdate()
}
}
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.setBytes(2, u.flags)
statement.setBytes(2, Array(u.messageFlags, u.channelFlags))
statement.setInt(3, u.cltvExpiryDelta)
statement.setLong(4, u.htlcMinimumMsat)
statement.setLong(5, u.feeBaseMsat)
statement.setLong(6, u.feeProportionalMillionths)
statement.setLong(7, u.shortChannelId.toLong)
statement.setBoolean(8, Announcements.isNode1(u.flags))
setNullableLong(statement, 7, u.htlcMaximumMsat)
statement.setLong(8, u.shortChannelId.toLong)
statement.setBoolean(9, Announcements.isNode1(u.channelFlags))
statement.executeUpdate()
}
}
@ -155,11 +157,13 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb {
chainHash = null,
shortChannelId = ShortChannelId(rs.getLong("short_channel_id")),
timestamp = rs.getLong("timestamp"),
flags = rs.getBytes("flags"),
messageFlags = rs.getBytes("flags")(0),
channelFlags = rs.getBytes("flags")(1),
cltvExpiryDelta = rs.getInt("cltv_expiry_delta"),
htlcMinimumMsat = rs.getLong("htlc_minimum_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
}

View file

@ -16,7 +16,7 @@
package fr.acinq.eclair.db.sqlite
import java.sql.{ResultSet, Statement}
import java.sql.{PreparedStatement, ResultSet, Statement}
import scodec.Codec
import scodec.bits.BitVector
@ -74,4 +74,30 @@ object SqliteUtils {
}
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.wire._
import fr.acinq.eclair.{wire, _}
import scodec.Attempt
import scala.concurrent.duration._
import scala.util.Random
@ -264,34 +265,39 @@ class Peer(nodeParams: NodeParams, remoteNodeId: PublicKey, authenticator: Actor
stay
case Event(rebroadcast: Rebroadcast, ConnectedData(_, transport, _, _, maybeGossipTimestampFilter, _, _)) =>
val (channels1, updates1, nodes1) = Peer.filterGossipMessages(rebroadcast, self, maybeGossipTimestampFilter)
/**
* Send and count in a single iteration
*/
def sendAndCount(msgs: Iterable[RoutingMessage]): Int = msgs.foldLeft(0) {
case (count, msg) =>
def sendAndCount(msgs: Map[_ <: RoutingMessage, Set[ActorRef]]): Int = msgs.foldLeft(0) {
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
count + 1
}
val channelsSent = sendAndCount(channels1)
val updatesSent = sendAndCount(updates1)
val nodesSent = sendAndCount(nodes1)
val channelsSent = sendAndCount(rebroadcast.channels)
val updatesSent = sendAndCount(rebroadcast.updates)
val nodesSent = sendAndCount(rebroadcast.nodes)
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
case Event(msg: GossipTimestampFilter, data: ConnectedData) =>
// special case: time range filters are peer specific and must not be sent to
// the router
// special case: time range filters are peer specific and must not be sent to the router
sender ! TransportHandler.ReadAck(msg)
if (msg.chainHash != nodeParams.chainHash) {
log.warning("received gossip_timestamp_range message for chain {}, we're on {}", msg.chainHash, nodeParams.chainHash)
stay
} else {
log.info(s"setting up gossipTimestampFilter=$msg")
// update their timestamp filter
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)) =>
val behavior1 = badMessage match {
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")
// 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())
@ -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 self messages which have been sent by `self` will be filtered out
* @param gossipTimestampFilter optional gossip timestamp range
* @return a filtered (channel announcements, channel updates, node announcements) tuple
* @param gossipTimestampFilter_opt optional gossip timestamp range
* @return
* - true if the msg's timestamp is in the requested range, or if there is no filtering
* - 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
def checkTimestamp(routingMessage: RoutingMessage): Boolean = gossipTimestampFilter match {
gossipTimestampFilter_opt match {
case None => true // no filtering
case Some(GossipTimestampFilter(_, firstTimestamp, timestampRange)) => routingMessage match {
case hts: HasTimestamp => hts.timestamp >= firstTimestamp && hts.timestamp <= firstTimestamp + timestampRange
case _ => true
}
case Some(GossipTimestampFilter(_, firstTimestamp, timestampRange)) => msg.timestamp >= firstTimestamp && msg.timestamp <= firstTimestamp + timestampRange
}
// 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)) =>
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 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 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
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)
*/
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")
}
case class CheckPayment(paymentHash: BinaryData)

View file

@ -137,7 +137,7 @@ class Relayer(nodeParams: NodeParams, register: ActorRef, paymentHandler: ActorR
case (_: ExpiryTooBig, _) => ExpiryTooFar
case (_: InsufficientFunds, 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 (_: HtlcTimedout, _) => PermanentChannelFailure
case _ => TemporaryNodeFailure
@ -260,8 +260,8 @@ object Relayer {
channelUpdate_opt match {
case None =>
Left(CMD_FAIL_HTLC(add.id, Right(UnknownNextPeer), commit = true))
case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.flags) =>
Left(CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.flags, channelUpdate)), commit = true))
case Some(channelUpdate) if !Announcements.isEnabled(channelUpdate.channelFlags) =>
Left(CMD_FAIL_HTLC(add.id, Right(ChannelDisabled(channelUpdate.messageFlags, channelUpdate.channelFlags, channelUpdate)), commit = true))
case Some(channelUpdate) if payload.amtToForward < channelUpdate.htlcMinimumMsat =>
Left(CMD_FAIL_HTLC(add.id, Right(AmountBelowMinimum(add.amountMsat, channelUpdate)), commit = true))
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))))
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 =
sha256(sha256(serializationResult(LightningMessageCodecs.channelUpdateWitnessCodec.encode(chainHash :: shortChannelId :: timestamp :: flags :: cltvExpiryDelta :: htlcMinimumMsat :: feeBaseMsat :: feeProportionalMillionths :: HNil))))
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 :: 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) = {
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
*/
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
@ -116,25 +116,31 @@ object Announcements {
*
* @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 = {
val flags = makeFlags(isNode1 = isNode1(nodeSecret.publicKey.toBin, remoteNodeId.toBin), enable = enable)
require(flags.size == 2, "flags must be a 2-bytes field")
val witness = channelUpdateWitnessEncode(chainHash, shortChannelId, timestamp, flags, cltvExpiryDelta, htlcMinimumMsat, feeBaseMsat, feeProportionalMillionths)
def makeChannelFlags(isNode1: Boolean, enable: Boolean): Byte = BitVector.bits(!enable :: !isNode1 :: Nil).padLeft(8).toByte()
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 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
ChannelUpdate(
signature = sig,
chainHash = chainHash,
shortChannelId = shortChannelId,
timestamp = timestamp,
flags = flags,
messageFlags = messageFlags,
channelFlags = channelFlags,
cltvExpiryDelta = cltvExpiryDelta,
htlcMinimumMsat = htlcMinimumMsat,
feeBaseMsat = feeBaseMsat,
feeProportionalMillionths = feeProportionalMillionths
feeProportionalMillionths = feeProportionalMillionths,
htlcMaximumMsat = htlcMaximumMsatOpt
)
}
@ -151,7 +157,7 @@ object Announcements {
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)
verifySignature(witness, ann.signature, nodeId)
}*/

View file

@ -172,6 +172,9 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
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) =>
val lostChannel = d.channels(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) }
stay
case Event(GetRoutingState, d: Data) =>
stay // ignored on Android
case Event(SendChannelQuery(remoteNodeId, remote), d) =>
// 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
@ -305,10 +305,10 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
log.debug("received channel update from {}", sender)
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)
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) =>
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
}
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
val u1 = u.copy(
signature = null,
@ -610,7 +610,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
origin ! InvalidSignature(u)
d
} 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))
db.updateChannelUpdate(u1)
// we also need to update the graph
@ -618,7 +618,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
addEdge(d.graph, desc, u1)
d.copy(updates = d.updates + (desc -> u1))
} 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))
db.addChannelUpdate(u1)
// we also need to update the graph
@ -639,7 +639,7 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
val publicChannel = false
val remoteNodeId = d.privateChannels(u.shortChannelId)
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)) {
log.debug("ignoring {} (stale)", u)
d
@ -651,14 +651,14 @@ class Router(nodeParams: NodeParams, watcher: ActorRef) extends FSMDiagnosticAct
origin ! InvalidSignature(u)
d
} 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))
// we also need to update the graph
removeEdge(d.graph, desc)
addEdge(d.graph, desc, u1)
d.copy(privateUpdates = d.privateUpdates + (desc -> u1))
} 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))
// we also need to update the graph
addEdge(d.graph, desc, u1)
@ -685,7 +685,7 @@ object Router {
def toFakeUpdate(extraHop: ExtraHop): ChannelUpdate =
// 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
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] = {
// 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 = {
require(u.flags.data.size == 2, s"invalid flags length ${u.flags.data.size} != 2")
// the least significant bit tells us if it is node1 or node2
if (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
@ -803,7 +802,7 @@ object Router {
* Note that we only add the edge if the corresponding channel is enabled
*/
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.b)
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 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 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 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" }
@ -87,11 +87,11 @@ object FailureMessageCodecs {
.typecase(UPDATE | 12, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
.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 | 16, provide(IncorrectPaymentAmount))
.typecase(17, provide(FinalExpiryTooSoon))
.typecase(18, (("expiry" | uint32)).as[FinalIncorrectCltvExpiry])
.typecase(19, (("amountMsat" | uint32)).as[FinalIncorrectHtlcAmount])
.typecase(19, (("amountMsat" | uint64)).as[FinalIncorrectHtlcAmount])
.typecase(21, provide(ExpiryTooFar))
}

View file

@ -17,8 +17,7 @@
package fr.acinq.eclair.wire
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 fr.acinq.bitcoin.Crypto.{Point, PrivateKey, PublicKey, Scalar}
import fr.acinq.bitcoin.{BinaryData, Crypto}
@ -28,6 +27,7 @@ import fr.acinq.eclair.{ShortChannelId, UInt64, wire}
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._
import scodec.{Attempt, Codec, DecodeResult, Err, SizeBound}
import shapeless.nat._
import scala.util.{Failure, Success, Try}
@ -280,15 +280,18 @@ object LightningMessageCodecs {
("signature" | signature) ::
nodeAnnouncementWitnessCodec).as[NodeAnnouncement]
val channelUpdateWitnessCodec = (
val channelUpdateWitnessCodec =
("chainHash" | binarydata(32)) ::
("shortChannelId" | shortchannelid) ::
("timestamp" | uint32) ::
("flags" | binarydata(2)) ::
("cltvExpiryDelta" | uint16) ::
("htlcMinimumMsat" | uint64) ::
("feeBaseMsat" | uint32) ::
("feeProportionalMillionths" | uint32))
(("messageFlags" | byte) >>:~ { messageFlags =>
("channelFlags" | byte) ::
("cltvExpiryDelta" | uint16) ::
("htlcMinimumMsat" | uint64) ::
("feeBaseMsat" | uint32) ::
("feeProportionalMillionths" | uint32) ::
("htlcMaximumMsat" | conditional((messageFlags & 1) != 0, uint64))
})
val channelUpdateCodec: Codec[ChannelUpdate] = (
("signature" | signature) ::
@ -347,7 +350,7 @@ object LightningMessageCodecs {
("chainHash" | binarydata(32)) ::
("firstTimestamp" | uint32) ::
("timestampRange" | uint32)
).as[GossipTimestampFilter]
).as[GossipTimestampFilter]
val lightningMessageCodec = discriminated[LightningMessage].by(uint16)
.typecase(16, initCodec)
@ -415,4 +418,4 @@ object LightningMessageCodecs {
("outgoing_cltv_value" | uint32) ::
("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.Crypto.{Point, PublicKey, Scalar}
import fr.acinq.eclair.{ShortChannelId, UInt64}
import scodec.bits.BitVector
/**
* Created by PM on 15/11/2016.
@ -192,11 +193,15 @@ case class ChannelUpdate(signature: BinaryData,
chainHash: BinaryData,
shortChannelId: ShortChannelId,
timestamp: Long,
flags: BinaryData,
messageFlags: Byte,
channelFlags: Byte,
cltvExpiryDelta: Int,
htlcMinimumMsat: 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,
amtToForward: Long,
@ -257,7 +262,7 @@ case class ReplyChannelRangeEx(chainHash: BinaryData,
data: BinaryData) extends RoutingMessage with HasChainHash
case class ReplyShortChannelIdsEnd(chainHash: BinaryData,
complete: Byte) extends RoutingMessage with HasChainHash
complete: Byte) extends RoutingMessage with HasChainHash
case class ReplyShortChannelIdsEndEx(chainHash: BinaryData,
complete: Byte) extends RoutingMessage with HasChainHash

View file

@ -48,8 +48,8 @@
<root level="INFO">
<!--appender-ref ref="FILE"/>
<appender-ref ref="CONSOLEWARN"/-->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="CONSOLEWARN"/>
<appender-ref ref="CONSOLE"/-->
</root>
</configuration>

View file

@ -17,11 +17,9 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.{Btc, MilliBtc, MilliSatoshi, Satoshi}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class CoinUtilsSpec extends FunSuite {
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.eclair.Features._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 27/01/2017.
*/
@RunWith(classOf[JUnitRunner])
class FeaturesSpec extends FunSuite {
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.transactions._
import fr.acinq.eclair.wire.{NodeAddress, UpdateAddHtlc, UpdateFailHtlc}
import org.junit.runner.RunWith
import grizzled.slf4j.Logging
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import upickle.default.write
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class JsonSerializersSpec extends FunSuite {
class JsonSerializersSpec extends FunSuite with Logging {
import JsonSerializers._
test("deserialize Map[OutPoint, BinaryData]") {
@ -60,7 +59,7 @@ class JsonSerializersSpec extends FunSuite {
globalFeatures = randomBytes(256),
localFeatures = randomBytes(256))
println(write(localParams))
logger.info(write(localParams))
}
@ -81,12 +80,12 @@ class JsonSerializersSpec extends FunSuite {
globalFeatures = randomBytes(256),
localFeatures = randomBytes(256))
println(write(remoteParams))
logger.info(write(remoteParams))
}
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)
println(write(spec))
logger.info(write(spec))
}
test("serialize LocalChanges") {
@ -94,7 +93,7 @@ class JsonSerializersSpec extends FunSuite {
val add = UpdateAddHtlc(channelId, 421, 1245, randomBytes(32), 1000, BinaryData("010101"))
val fail = UpdateFailHtlc(channelId, 42, BinaryData("0101"))
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") {
@ -103,16 +102,16 @@ class JsonSerializersSpec extends FunSuite {
for (i <- 0 until 7) {
receiver = receiver.addHash(ShaChain.shaChainFromSeed(seed, 0xFFFFFFFFFFFFL - i), 0xFFFFFFFFFFFFL - i)
}
println(write(receiver))
logger.info(write(receiver))
}
test("serialize Commitments") {
val commitments = ChannelStateSpec.commitments
println(write(commitments))
logger.info(write(commitments))
}
test("serialize DATA_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.{Base58, Base58Check, Bech32, BinaryData, Block, Crypto, Script}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.util.Try
/**
* Created by PM on 27/01/2017.
*/
@RunWith(classOf[JUnitRunner])
class PackageSpec extends FunSuite {
test("compute long channel id") {

View file

@ -16,12 +16,10 @@
package fr.acinq.eclair
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ShortChannelIdSpec extends FunSuite {
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.{BinaryData, Block, Script}
import fr.acinq.eclair.NodeParams.BITCOIND
import fr.acinq.eclair.TestConstants.Alice.sqlite
import fr.acinq.eclair.crypto.LocalKeyManager
import fr.acinq.eclair.db.sqlite._
import fr.acinq.eclair.io.Peer

View file

@ -17,12 +17,10 @@
package fr.acinq.eclair
import fr.acinq.bitcoin.BinaryData
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class UInt64Spec extends FunSuite {
test("handle values from 0 to 2^63-1") {

View file

@ -17,14 +17,12 @@
package fr.acinq.eclair.blockchain
import fr.acinq.bitcoin.Transaction
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 27/01/2017.
*/
@RunWith(classOf[JUnitRunner])
class WatcherSpec extends FunSuite {
test("extract pay2wpkh pubkey script") {

View file

@ -30,8 +30,6 @@ import fr.acinq.eclair.{addressToPublicKeyScript, randomKey}
import grizzled.slf4j.Logging
import org.json4s.JsonAST._
import org.json4s.{DefaultFormats, JString}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.collection.JavaConversions._
@ -40,7 +38,7 @@ import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Random, Try}
@RunWith(classOf[JUnitRunner])
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))

View file

@ -41,7 +41,7 @@ trait BitcoindService extends Logging {
val INTEGRATION_TMP_DIR = s"${System.getProperty("buildDirectory")}/integration-${UUID.randomUUID().toString}"
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")
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.eclair.blockchain.electrum.ElectrumClient._
import grizzled.slf4j.Logging
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.concurrent.duration._
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class ElectrumClientPoolSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
var router: ActorRef = _
val probe = TestProbe()

View file

@ -22,14 +22,12 @@ import akka.actor.{ActorRef, ActorSystem, Props}
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
import grizzled.slf4j.Logging
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class ElectrumClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with Logging with BeforeAndAfterAll {
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._
import fr.acinq.eclair.transactions.Transactions
import org.junit.runner.RunWith
import grizzled.slf4j.Logging
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.util.{Failure, Random, Success, Try}
@RunWith(classOf[JUnitRunner])
class ElectrumWalletBasicSpec extends FunSuite {
class ElectrumWalletBasicSpec extends FunSuite with Logging {
import ElectrumWallet._
import ElectrumWalletBasicSpec._
@ -197,7 +196,7 @@ class ElectrumWalletBasicSpec extends FunSuite {
Try(state1.completeTransaction(tx, feeRatePerKw, minimumFee, dustLimit, true)) match {
case Success((state2, tx1, fee1)) => ()
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.eclair.blockchain.electrum.ElectrumClient.{ScriptHashSubscription, ScriptHashSubscriptionResponse}
import fr.acinq.eclair.blockchain.electrum.ElectrumWallet._
import org.junit.runner.RunWith
import org.scalatest.FunSuiteLike
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
val sender = TestProbe()
@ -93,7 +91,7 @@ class ElectrumWalletSimulatedClientSpec extends TestKit(ActorSystem("test")) wit
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
sender.send(wallet, ElectrumClient.HeaderSubscriptionResponse(header4))
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.electrum.ElectrumClient.{BroadcastTransaction, BroadcastTransactionResponse}
import grizzled.slf4j.Logging
import org.json4s.JsonAST.{JDecimal, JDouble, JString, JValue}
import org.junit.experimental.categories.Category
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.json4s.JsonAST.{JDecimal, JString, JValue}
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
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 {
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 grizzled.slf4j.Logging
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 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 {
override def beforeAll(): Unit = {
@ -59,7 +55,6 @@ class ElectrumWatcherSpec extends TestKit(ActorSystem("test")) with FunSuiteLike
probe.send(bitcoincli, BitcoinReq("getnewaddress"))
val JString(address) = probe.expectMsgType[JValue]
println(address)
probe.send(bitcoincli, BitcoinReq("sendtoaddress", address, 1.0))
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"))
val JString(address) = probe.expectMsgType[JValue]
println(address)
probe.send(bitcoincli, BitcoinReq("dumpprivkey", address))
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.whisk.docker.impl.spotify.SpotifyDockerFactory
import com.whisk.docker.scalatest.DockerTestKit
import com.whisk.docker.{DockerContainer, DockerFactory, LogLineReceiver}
import com.whisk.docker.{DockerContainer, DockerFactory}
import org.scalatest.Suite
trait ElectrumxService extends DockerTestKit {
@ -30,14 +30,14 @@ trait ElectrumxService extends DockerTestKit {
DockerContainer("lukechilds/electrumx")
.withNetworkMode("host")
.withEnv("DAEMON_URL=http://foo:bar@localhost:28332", "COIN=BitcoinSegwit", "NET=regtest")
.withLogLineReceiver(LogLineReceiver(true, println))
//.withLogLineReceiver(LogLineReceiver(true, println))
} else {
// 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
DockerContainer("lukechilds/electrumx")
.withPorts(50001 -> Some(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

View file

@ -7,13 +7,11 @@ import akka.testkit.{TestKit, TestProbe}
import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin._
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 org.json4s.DefaultFormats
import org.json4s.JsonAST._
import org.json4s.jackson.JsonMethods
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scala.collection.JavaConversions._
@ -21,7 +19,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random
@RunWith(classOf[JUnitRunner])
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))

View file

@ -19,14 +19,12 @@ package fr.acinq.eclair.blockchain.fee
import akka.actor.ActorSystem
import akka.util.Timeout
import org.json4s.DefaultFormats
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 27/01/2017.
*/
@RunWith(classOf[JUnitRunner])
class BitgoFeeProviderSpec extends FunSuite {
import BitgoFeeProvider._

View file

@ -18,18 +18,17 @@ package fr.acinq.eclair.blockchain.fee
import akka.actor.ActorSystem
import akka.util.Timeout
import grizzled.slf4j.Logging
import org.json4s.DefaultFormats
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.concurrent.Await
/**
* Created by PM on 27/01/2017.
*/
@RunWith(classOf[JUnitRunner])
class EarnDotComFeeProviderSpec extends FunSuite {
class EarnDotComFeeProviderSpec extends FunSuite with Logging {
import EarnDotComFeeProvider._
import org.json4s.jackson.JsonMethods.parse
@ -74,7 +73,7 @@ class EarnDotComFeeProviderSpec extends FunSuite {
implicit val system = ActorSystem()
implicit val timeout = Timeout(30 seconds)
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
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class FallbackFeeProviderSpec extends FunSuite {
import scala.concurrent.ExecutionContext.Implicits.global

View file

@ -1,14 +1,12 @@
package fr.acinq.eclair.blockchain.fee
import org.junit.runner.RunWith
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.duration._
import scala.concurrent.{Await, Future}
@RunWith(classOf[JUnitRunner])
class SmoothFeeProviderSpec extends FunSuite {
test("smooth fee rates") {
val rates = Array(

View file

@ -31,9 +31,7 @@ import fr.acinq.eclair.payment._
import fr.acinq.eclair.router.Hop
import fr.acinq.eclair.wire._
import grizzled.slf4j.Logging
import org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import scala.collection.immutable.Nil
import scala.concurrent.duration._
@ -42,10 +40,10 @@ import scala.util.Random
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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) = {
val fuzzy = test.tags.contains("fuzzy")
@ -82,7 +80,7 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi
awaitCond(alice.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 {
@ -133,65 +131,65 @@ class FuzzySpec extends TestkitBaseClass with StateTestsHelperMethods with Loggi
}
test("fuzzy test with only one party sending HTLCs", Tag("fuzzy")) {
case (alice, bob, _, _, _, _, paymentHandlerB) =>
val latch = new CountDownLatch(100)
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
awaitCond(latch.getCount == 0, max = 2 minutes)
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
test("fuzzy test with only one party sending HTLCs", Tag("fuzzy")) { f =>
import f._
val latch = new CountDownLatch(100)
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
system.actorOf(Props(new SenderActor(alice, paymentHandlerB, latch)))
awaitCond(latch.getCount == 0, max = 2 minutes)
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
}
test("fuzzy test with both parties sending HTLCs", Tag("fuzzy")) {
case (alice, bob, _, _, _, paymentHandlerA, paymentHandlerB) =>
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(bob, paymentHandlerA, latch)))
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
awaitCond(latch.getCount == 0, max = 2 minutes)
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
test("fuzzy test with both parties sending HTLCs", Tag("fuzzy")) { f =>
import f._
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(bob, paymentHandlerA, latch)))
system.actorOf(Props(new SenderActor(bob, paymentHandlerA, latch)))
awaitCond(latch.getCount == 0, max = 2 minutes)
assert(alice.stateName == NORMAL || alice.stateName == OFFLINE)
assert(bob.stateName == NORMAL || alice.stateName == OFFLINE)
}
test("one party sends lots of htlcs send shutdown") {
case (alice, _, _, _, _, _, paymentHandlerB) =>
val latch = new CountDownLatch(20)
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))) :: Nil
awaitCond(latch.getCount == 0, max = 2 minutes)
val sender = TestProbe()
awaitCond({
sender.send(alice, CMD_CLOSE(None))
sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure]) == "ok"
}, max = 30 seconds)
senders.foreach(_ ! 'stop)
awaitCond(alice.stateName == CLOSING)
awaitCond(alice.stateName == CLOSING)
test("one party sends lots of htlcs send shutdown") { f =>
import f._
val latch = new CountDownLatch(20)
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))) :: Nil
awaitCond(latch.getCount == 0, max = 2 minutes)
val sender = TestProbe()
awaitCond({
sender.send(alice, CMD_CLOSE(None))
sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure]) == "ok"
}, max = 30 seconds)
senders.foreach(_ ! 'stop)
awaitCond(alice.stateName == CLOSING)
awaitCond(alice.stateName == CLOSING)
}
test("both parties send lots of htlcs send shutdown") {
case (alice, bob, _, _, _, paymentHandlerA, paymentHandlerB) =>
val latch = new CountDownLatch(30)
val senders = 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))) :: Nil
awaitCond(latch.getCount == 0, max = 2 minutes)
val sender = TestProbe()
awaitCond({
sender.send(alice, CMD_CLOSE(None))
val resa = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
sender.send(bob, CMD_CLOSE(None))
val resb = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
// we only need that one of them succeeds
resa == "ok" || resb == "ok"
}, max = 30 seconds)
senders.foreach(_ ! 'stop)
awaitCond(alice.stateName == CLOSING)
awaitCond(alice.stateName == CLOSING)
test("both parties send lots of htlcs send shutdown") { f =>
import f._
val latch = new CountDownLatch(30)
val senders = 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))) :: Nil
awaitCond(latch.getCount == 0, max = 2 minutes)
val sender = TestProbe()
awaitCond({
sender.send(alice, CMD_CLOSE(None))
val resa = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
sender.send(bob, CMD_CLOSE(None))
val resb = sender.expectMsgAnyClassOf(classOf[String], classOf[Status.Failure])
// we only need that one of them succeeds
resa == "ok" || resb == "ok"
}, max = 30 seconds)
senders.foreach(_ ! 'stop)
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.wire.{AcceptChannel, Error, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import org.scalatest.{Outcome, Tag}
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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")) {
init(TestConstants.Alice.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash), TestConstants.Bob.nodeParams.copy(chainHash = Block.LivenetGenesisBlock.hash))
} else {
@ -52,112 +50,102 @@ class WaitForAcceptChannelStateSpec extends TestkitBaseClass with StateTestsHelp
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
awaitCond(alice.stateName == WAIT_FOR_ACCEPT_CHANNEL)
}
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)
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
}
}
test("recv AcceptChannel (invalid max accepted htlcs)") { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
// spec says max = 483
val invalidMaxAcceptedHtlcs = 484
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") { f =>
import f._
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
}
test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
// we don't want their dust limit to be below 546
val lowDustLimitSatoshis = 545
alice ! accept.copy(dustLimitSatoshis = lowDustLimitSatoshis)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, DustLimitTooSmall(accept.temporaryChannelId, lowDustLimitSatoshis, Channel.MIN_DUSTLIMIT).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (invalid max accepted htlcs)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
// spec says max = 483
val invalidMaxAcceptedHtlcs = 484
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 (to_self_delay too high)") { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
val delayTooHigh = 10000
alice ! accept.copy(toSelfDelay = delayTooHigh)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (invalid dust limit)", Tag("mainnet")) { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
// we don't want their dust limit to be below 546
val lowDustLimitSatoshis = 545
alice ! accept.copy(dustLimitSatoshis = lowDustLimitSatoshis)
val error = alice2bob.expectMsgType[Error]
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, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
// 30% is huge, recommended ratio is 1%
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
alice ! accept.copy(channelReserveSatoshis = reserveTooHigh)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ChannelReserveTooHigh(accept.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (to_self_delay too high)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
val delayTooHigh = 10000
alice ! accept.copy(toSelfDelay = delayTooHigh)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ToSelfDelayTooHigh(accept.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (reserve below dust limit)") { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
val reserveTooSmall = accept.dustLimitSatoshis - 1
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, DustLimitTooLarge(accept.temporaryChannelId, accept.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (reserve too high)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
// 30% is huge, recommended ratio is 1%
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
alice ! accept.copy(channelReserveSatoshis = reserveTooHigh)
val error = alice2bob.expectMsgType[Error]
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, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
val reserveTooSmall = open.dustLimitSatoshis - 1
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ChannelReserveBelowOurDustLimit(accept.temporaryChannelId, reserveTooSmall, open.dustLimitSatoshis).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (reserve below dust limit)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
val reserveTooSmall = accept.dustLimitSatoshis - 1
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, DustLimitTooLarge(accept.temporaryChannelId, accept.dustLimitSatoshis, reserveTooSmall).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv AcceptChannel (dust limit above our reserve)") { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
val accept = bob2alice.expectMsgType[AcceptChannel]
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 AcceptChannel (reserve below our dust limit)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
val open = alice.stateData.asInstanceOf[DATA_WAIT_FOR_ACCEPT_CHANNEL].lastSent
val reserveTooSmall = open.dustLimitSatoshis - 1
alice ! accept.copy(channelReserveSatoshis = reserveTooSmall)
val error = alice2bob.expectMsgType[Error]
assert(error === Error(accept.temporaryChannelId, ChannelReserveBelowOurDustLimit(accept.temporaryChannelId, reserveTooSmall, open.dustLimitSatoshis).getMessage.getBytes("UTF-8")))
awaitCond(alice.stateName == CLOSED)
}
test("recv Error") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
bob ! Error("00" * 32, "oops".getBytes)
awaitCond(bob.stateName == CLOSED)
}
test("recv AcceptChannel (dust limit above our reserve)") { f =>
import f._
val accept = bob2alice.expectMsgType[AcceptChannel]
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, _) =>
within(30 seconds) {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSED)
}
test("recv Error") { f =>
import f._
alice ! Error("00" * 32, "oops".getBytes)
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.channel._
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 org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
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)
bob ! INPUT_INIT_FUNDEE("00" * 32, Bob.channelParams, bob2alice.ref, aliceInit)
awaitCond(bob.stateName == WAIT_FOR_OPEN_CHANNEL)
}
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)
withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain)))
}
}
test("recv OpenChannel (invalid chain)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
// using livenet genesis block
val livenetChainHash = Block.LivenetGenesisBlock.hash
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") { f =>
import f._
alice2bob.expectMsgType[OpenChannel]
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}
test("recv OpenChannel (funding too low)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val lowFundingMsat = 100
bob ! open.copy(fundingSatoshis = lowFundingMsat)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, new InvalidFundingAmount(open.temporaryChannelId, lowFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (invalid chain)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
// using livenet genesis block
val livenetChainHash = Block.LivenetGenesisBlock.hash
bob ! open.copy(chainHash = livenetChainHash)
val error = bob2alice.expectMsgType[Error]
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, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val highFundingMsat = 100000000
bob ! open.copy(fundingSatoshis = highFundingMsat)
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")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (funding too low)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val lowFundingMsat = 100
bob ! open.copy(fundingSatoshis = lowFundingMsat)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, InvalidFundingAmount(open.temporaryChannelId, lowFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (invalid max accepted htlcs)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1
bob ! open.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, new InvalidMaxAcceptedHtlcs(open.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (funding too high)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val highFundingMsat = 100000000
bob ! open.copy(fundingSatoshis = highFundingMsat)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, InvalidFundingAmount(open.temporaryChannelId, highFundingMsat, Bob.nodeParams.minFundingSatoshis, Channel.MAX_FUNDING_SATOSHIS).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (invalid push_msat)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val invalidPushMsat = 100000000000L
bob ! open.copy(pushMsat = invalidPushMsat)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, new InvalidPushAmount(open.temporaryChannelId, invalidPushMsat, 1000 * open.fundingSatoshis).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (invalid max accepted htlcs)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val invalidMaxAcceptedHtlcs = Channel.MAX_ACCEPTED_HTLCS + 1
bob ! open.copy(maxAcceptedHtlcs = invalidMaxAcceptedHtlcs)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, InvalidMaxAcceptedHtlcs(open.temporaryChannelId, invalidMaxAcceptedHtlcs, Channel.MAX_ACCEPTED_HTLCS).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (to_self_delay too high)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val delayTooHigh = 10000
bob ! open.copy(toSelfDelay = delayTooHigh)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (invalid push_msat)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val invalidPushMsat = 100000000000L
bob ! open.copy(pushMsat = invalidPushMsat)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, InvalidPushAmount(open.temporaryChannelId, invalidPushMsat, 1000 * open.fundingSatoshis).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (reserve too high)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
// 30% is huge, recommended ratio is 1%
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
bob ! open.copy(channelReserveSatoshis = reserveTooHigh)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, new ChannelReserveTooHigh(open.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (to_self_delay too high)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val delayTooHigh = 10000
bob ! open.copy(toSelfDelay = delayTooHigh)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, ToSelfDelayTooHigh(open.temporaryChannelId, delayTooHigh, Alice.nodeParams.maxToLocalDelayBlocks).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (fee too low, but still valid)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
// set a very small fee
val tinyFee = 253
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, "local/remote feerates are too different: remoteFeeratePerKw=253 localFeeratePerKw=10000".getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (reserve too high)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
// 30% is huge, recommended ratio is 1%
val reserveTooHigh = (0.3 * TestConstants.fundingSatoshis).toLong
bob ! open.copy(channelReserveSatoshis = reserveTooHigh)
val error = bob2alice.expectMsgType[Error]
assert(error === Error(open.temporaryChannelId, ChannelReserveTooHigh(open.temporaryChannelId, reserveTooHigh, 0.3, 0.05).getMessage.getBytes("UTF-8")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (fee below absolute valid minimum)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
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 (fee too low, but still valid)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
// set a very small fee
val tinyFee = 253
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, "local/remote feerates are too different: remoteFeeratePerKw=253 localFeeratePerKw=10000".getBytes("UTF-8")))
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, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val reserveTooSmall = open.dustLimitSatoshis - 1
bob ! open.copy(channelReserveSatoshis = reserveTooSmall)
val error = bob2alice.expectMsgType[Error]
// 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")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (reserve below dust)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val reserveTooSmall = open.dustLimitSatoshis - 1
bob ! open.copy(channelReserveSatoshis = reserveTooSmall)
val error = bob2alice.expectMsgType[Error]
// 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")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (toLocal + toRemote below reserve)") { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val open = alice2bob.expectMsgType[OpenChannel]
val fundingSatoshis = open.channelReserveSatoshis + 499
val pushMsat = 500 * 1000
bob ! open.copy(fundingSatoshis = fundingSatoshis, pushMsat = pushMsat)
val error = bob2alice.expectMsgType[Error]
// 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")))
awaitCond(bob.stateName == CLOSED)
}
test("recv OpenChannel (toLocal + toRemote below reserve)") { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
val fundingSatoshis = open.channelReserveSatoshis + 499
val pushMsat = 500 * 1000
bob ! open.copy(fundingSatoshis = fundingSatoshis, pushMsat = pushMsat)
val error = bob2alice.expectMsgType[Error]
// 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")))
awaitCond(bob.stateName == CLOSED)
}
test("recv Error") { case (bob, _, _, _) =>
within(30 seconds) {
bob ! Error("00" * 32, "oops".getBytes())
awaitCond(bob.stateName == CLOSED)
}
test("recv Error") { f =>
import f._
bob ! Error("00" * 32, "oops".getBytes())
awaitCond(bob.stateName == CLOSED)
}
test("recv CMD_CLOSE") { case (bob, _, _, _) =>
within(30 seconds) {
bob ! CMD_CLOSE(None)
awaitCond(bob.stateName == CLOSED)
}
test("recv CMD_CLOSE") { f =>
import f._
bob ! CMD_CLOSE(None)
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.wire._
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
@ -48,22 +47,20 @@ class WaitForFundingCreatedInternalStateSpec extends TestkitBaseClass with State
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL)
}
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)
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
}
}
test("recv CMD_CLOSE") { case (alice, alice2bob, bob2alice, _) =>
within(30 seconds) {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSED)
}
test("recv Error") { f =>
import f._
alice ! Error("00" * 32, "oops".getBytes)
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.wire._
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import org.scalatest.{Outcome, Tag}
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val (fundingSatoshis, pushMsat) = if (test.tags.contains("funder_below_reserve")) {
@ -56,46 +54,42 @@ class WaitForFundingCreatedStateSpec extends TestkitBaseClass with StateTestsHel
bob2alice.expectMsgType[AcceptChannel]
bob2alice.forward(alice)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
}
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]
withFixture(test.toNoArgTest(FixtureParam(bob, alice2bob, bob2alice, bob2blockchain)))
}
}
test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { case (bob, alice2bob, bob2alice, _) =>
within(30 seconds) {
val fees = Transactions.commitWeight * TestConstants.feeratePerKw / 1000
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 FundingCreated") { f =>
import f._
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED)
bob2alice.expectMsgType[FundingSigned]
bob2blockchain.expectMsgType[WatchSpent]
bob2blockchain.expectMsgType[WatchConfirmed]
}
test("recv Error") { case (bob, _, _, _) =>
within(30 seconds) {
bob ! Error("00" * 32, "oops".getBytes)
awaitCond(bob.stateName == CLOSED)
}
test("recv FundingCreated (funder can't pay fees)", Tag("funder_below_reserve")) { f =>
import f._
val fees = Transactions.commitWeight * TestConstants.feeratePerKw / 1000
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, _, _, _) =>
within(30 seconds) {
bob ! CMD_CLOSE(None)
awaitCond(bob.stateName == CLOSED)
}
test("recv Error") { f =>
import f._
bob ! Error("00" * 32, "oops".getBytes)
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.wire.{AcceptChannel, Error, FundingCreated, FundingSigned, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
@ -52,41 +51,37 @@ class WaitForFundingSignedStateSpec extends TestkitBaseClass with StateTestsHelp
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_SIGNED)
}
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]
withFixture(test.toNoArgTest(FixtureParam(alice, alice2bob, bob2alice, alice2blockchain)))
}
}
test("recv FundingSigned with invalid signature") { case (alice, alice2bob, _, _) =>
within(30 seconds) {
// sending an invalid sig
alice ! FundingSigned("00" * 32, BinaryData("00" * 64))
awaitCond(alice.stateName == CLOSED)
alice2bob.expectMsgType[Error]
}
test("recv FundingSigned with valid signature") { f =>
import f._
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
}
test("recv CMD_CLOSE") { case (alice, _, _, _) =>
within(30 seconds) {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSED)
}
test("recv FundingSigned with invalid signature") { f =>
import f._
// sending an invalid sig
alice ! FundingSigned("00" * 32, BinaryData("00" * 64))
awaitCond(alice.stateName == CLOSED)
alice2bob.expectMsgType[Error]
}
test("recv CMD_FORCECLOSE") { case (alice, _, _, _) =>
within(30 seconds) {
alice ! CMD_FORCECLOSE
awaitCond(alice.stateName == CLOSED)
}
test("recv CMD_CLOSE") { f =>
import f._
alice ! CMD_CLOSE(None)
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.wire.{AcceptChannel, Error, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel}
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
@ -57,95 +56,86 @@ class WaitForFundingConfirmedStateSpec extends TestkitBaseClass with StateTestsH
alice2blockchain.expectMsgType[WatchSpent]
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
}
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)
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain)))
}
}
test("recv BITCOIN_FUNDING_DEPTHOK") { case (alice, _, alice2bob, _, alice2blockchain) =>
within(30 seconds) {
alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
alice2blockchain.expectMsgType[WatchLost]
alice2bob.expectMsgType[FundingLocked]
}
test("recv FundingLocked") { f =>
import f._
// 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.contains(msg))
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
}
test("recv BITCOIN_FUNDING_PUBLISH_FAILED") { case (alice, _, alice2bob, _, _) =>
within(30 seconds) {
alice ! BITCOIN_FUNDING_PUBLISH_FAILED
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_FUNDING_DEPTHOK") { f =>
import f._
alice ! WatchEventConfirmed(BITCOIN_FUNDING_DEPTHOK, 42000, 42)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
alice2blockchain.expectMsgType[WatchLost]
alice2bob.expectMsgType[FundingLocked]
}
test("recv BITCOIN_FUNDING_TIMEOUT") { case (alice, _, alice2bob, _, _) =>
within(30 seconds) {
alice ! BITCOIN_FUNDING_TIMEOUT
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == ERR_FUNDING_TIMEOUT)
}
test("recv BITCOIN_FUNDING_PUBLISH_FAILED") { f =>
import f._
alice ! BITCOIN_FUNDING_PUBLISH_FAILED
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, _, _, alice2blockchain) =>
within(30 seconds) {
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
test("recv BITCOIN_FUNDING_TIMEOUT") { f =>
import f._
alice ! BITCOIN_FUNDING_TIMEOUT
alice2bob.expectMsgType[Error]
awaitCond(alice.stateName == ERR_FUNDING_TIMEOUT)
}
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(PublishAsap(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
import f._
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
test("recv Error") { case (alice, _, _, _, alice2blockchain) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
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 BITCOIN_FUNDING_SPENT (other commit)") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(PublishAsap(tx))
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv CMD_CLOSE") { case (alice, _, _, _, _) =>
within(30 seconds) {
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(CannotCloseInThisState(channelId(alice), WAIT_FOR_FUNDING_CONFIRMED)))
}
test("recv Error") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
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) =>
within(30 seconds) {
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))
}
test("recv CMD_CLOSE") { f =>
import f._
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_FUNDING_CONFIRMED)))
}
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
import akka.actor.Status
import akka.actor.Status.Failure
import akka.testkit.{TestFSMRef, TestProbe}
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.wire._
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val aliceInit = Init(Alice.channelParams.globalFeatures, Alice.channelParams.localFeatures)
@ -66,68 +64,62 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass with StateTestsHelp
alice2bob.expectMsgType[FundingLocked]
awaitCond(alice.stateName == WAIT_FOR_FUNDING_LOCKED)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_LOCKED)
}
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)
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, router)))
}
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, router) =>
within(30 seconds) {
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
test("recv FundingLocked") { f =>
import f._
bob2alice.expectMsgType[FundingLocked]
bob2alice.forward(alice)
awaitCond(alice.stateName == NORMAL)
bob2alice.expectNoMsg(200 millis)
}
test("recv BITCOIN_FUNDING_SPENT (other commit)") { case (alice, _, alice2bob, _, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[PublishAsap]
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
import f._
// bob publishes his commitment tx
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, tx)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed]
awaitCond(alice.stateName == CLOSING)
}
test("recv Error") { case (alice, _, _, _, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[PublishAsap]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
}
test("recv BITCOIN_FUNDING_SPENT (other commit)") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, Transaction(0, Nil, Nil, 0))
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[PublishAsap]
awaitCond(alice.stateName == ERR_INFORMATION_LEAK)
}
test("recv CMD_CLOSE") { case (alice, _, _, _, _, _) =>
within(30 seconds) {
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(CannotCloseInThisState(channelId(alice), WAIT_FOR_FUNDING_LOCKED)))
}
test("recv Error") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_LOCKED].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
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, _) =>
within(30 seconds) {
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))
}
test("recv CMD_CLOSE") { f =>
import f._
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(CommandUnavailableInThisState(channelId(alice), "close", WAIT_FOR_FUNDING_LOCKED)))
}
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.{Data, State, _}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{TestConstants, TestkitBaseClass}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
awaitCond(alice.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
*/
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()
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 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)
@ -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
*/
test("re-send lost revocation") { case (alice, bob, alice2bob, bob2alice, _, _, _) =>
test("re-send lost revocation") { f =>
import f._
val sender = TestProbe()
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 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)
// 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 (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()
// 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()
// 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)
}
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
import akka.actor.Status.Failure
import akka.event.LoggingAdapter
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Satoshi
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.{Data, State, _}
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 org.junit.runner.RunWith
import org.scalatest.Tag
import org.scalatest.junit.JUnitRunner
import org.scalatest.{Outcome, Tag}
import scala.concurrent.duration._
import scala.util.Success
@ -38,12 +37,12 @@ import scala.util.Success
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
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))
alice2bob.forward(bob)
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, _, _, _) =>
within(30 seconds) {
alice2bob.expectMsgType[ClosingSigned]
val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis)
}
test("recv CMD_ADD_HTLC") { f =>
import f._
alice2bob.expectMsgType[ClosingSigned]
val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis)
}
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { case (alice, bob, alice2bob, bob2alice, _, _) =>
within(30 seconds) {
// alice initiates the negotiation
val aliceCloseSig1 = alice2bob.expectMsgType[ClosingSigned]
test("recv ClosingSigned (theirCloseFee != ourCloseFee)") { f =>
import f._
// alice initiates the negotiation
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)
// 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)
}
if (!bob2blockchain.msgAvailable) {
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
bob2alice.forward(alice)
}
} while (!alice2blockchain.msgAvailable && !bob2blockchain.msgAvailable)
}
def testFeeConverge(alice: TestFSMRef[State, Data, Channel],
bob: TestFSMRef[State, Data, Channel],
alice2bob: TestProbe,
bob2alice: TestProbe,
alice2blockchain: TestProbe,
bob2blockchain: TestProbe) = {
within(30 seconds) {
var aliceCloseFee, bobCloseFee = 0L
do {
aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
alice2bob.forward(bob)
if (!bob2blockchain.msgAvailable) {
bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 1)") { f =>
testFeeConverge(f)
}
test("recv ClosingSigned (theirCloseFee == ourCloseFee) (fee 2)", Tag("fee2")) { f =>
testFeeConverge(f)
}
test("recv ClosingSigned (fee too high)") { 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(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)
}
} 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) =>
testFeeConverge(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
}
test("recv BITCOIN_FUNDING_SPENT (an older mutual close)") { f =>
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) =>
testFeeConverge(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
}
test("recv ClosingSigned (fee too high)") { 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(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)
}
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, _, _, _, _, _) =>
within(30 seconds) {
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
}
test("recv CMD_CLOSE") { f =>
import f._
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
}
test("recv Error") { case (alice, _, _, _, alice2blockchain, _) =>
within(30 seconds) {
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes())
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[PublishAsap]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(tx))
}
test("recv Error") { f =>
import f._
val tx = alice.stateData.asInstanceOf[DATA_NEGOTIATING].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes())
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[PublishAsap]
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.testkit.{TestFSMRef, TestProbe}
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.fee.FeeratesPerKw
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.transactions.Scripts
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass}
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 05/07/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
import setup._
val bobCommitTxes = within(30 seconds) {
within(30 seconds) {
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
@ -71,9 +70,8 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// - revoked commit
// and we want to be able to test the different scenarii.
// 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],
@ -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
}
test("recv CMD_ADD_HTLC") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
test("recv CMD_ADD_HTLC") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// actual test starts here
val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis)
}
// actual test starts here
val sender = TestProbe()
val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 300000)
sender.send(alice, add)
val error = ChannelUnavailable(channelId(alice))
sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), None, Some(add))))
alice2bob.expectNoMsg(200 millis)
}
test("recv CMD_FULFILL_HTLC (unexisting htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
test("recv CMD_FULFILL_HTLC (unexisting htlc)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// actual test starts here
val sender = TestProbe()
sender.send(alice, CMD_FULFILL_HTLC(42, "42" * 32))
sender.expectMsg(Failure(UnknownHtlcId(channelId(alice), 42)))
// actual test starts here
val sender = TestProbe()
sender.send(alice, CMD_FULFILL_HTLC(42, "42" * 32))
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, _, _) =>
within(30 seconds) {
val sender = TestProbe()
// alice initiates a closing
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[Shutdown]
alice2bob.forward(bob)
bob2alice.expectMsgType[Shutdown]
bob2alice.forward(alice)
// agreeing on a closing fee
val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
Globals.feeratesPerKw.set(FeeratesPerKw.single(100))
alice2bob.forward(bob)
val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
bob2alice.forward(alice)
// they don't converge yet, but alice has a publishable commit tx now
assert(aliceCloseFee != bobCloseFee)
val Some(mutualCloseTx) = alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt
// let's make alice publish this closing tx
alice ! Error("00" * 32, "")
awaitCond(alice.stateName == CLOSING)
assert(mutualCloseTx === alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last)
test("recv BITCOIN_FUNDING_SPENT (mutual close before converging)") { f =>
import f._
val sender = TestProbe()
// alice initiates a closing
sender.send(alice, CMD_CLOSE(None))
alice2bob.expectMsgType[Shutdown]
alice2bob.forward(bob)
bob2alice.expectMsgType[Shutdown]
bob2alice.forward(alice)
// agreeing on a closing fee
val aliceCloseFee = alice2bob.expectMsgType[ClosingSigned].feeSatoshis
Globals.feeratesPerKw.set(FeeratesPerKw.single(100))
alice2bob.forward(bob)
val bobCloseFee = bob2alice.expectMsgType[ClosingSigned].feeSatoshis
bob2alice.forward(alice)
// they don't converge yet, but alice has a publishable commit tx now
assert(aliceCloseFee != bobCloseFee)
val Some(mutualCloseTx) = alice.stateData.asInstanceOf[DATA_NEGOTIATING].bestUnpublishedClosingTx_opt
// let's make alice publish this closing tx
alice ! Error("00" * 32, "")
awaitCond(alice.stateName == CLOSING)
assert(mutualCloseTx === alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last)
// actual test starts here
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_TX_CONFIRMED (mutual close)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last
test("recv BITCOIN_TX_CONFIRMED (mutual close)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val mutualCloseTx = alice.stateData.asInstanceOf[DATA_CLOSING].mutualClosePublished.last
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mutualCloseTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_FUNDING_SPENT (local commit)") { case (alice, _, _, _, alice2blockchain, _, _, _) =>
within(30 seconds) {
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
test("recv BITCOIN_FUNDING_SPENT (local commit)") { f =>
import f._
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
// actual test starts here
// we are notified afterwards from our watcher about the tx that we just published
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, aliceCommitTx)
assert(alice.stateData == initialState) // this was a no-op
}
// actual test starts here
// we are notified afterwards from our watcher about the tx that we just published
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, aliceCommitTx)
assert(alice.stateData == initialState) // this was a no-op
}
test("recv BITCOIN_OUTPUT_SPENT") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer, _) =>
within(30 seconds) {
// alice sends an htlc to bob
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
relayer.expectMsgType[ForwardAdd]
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
alice2blockchain.expectMsgType[PublishAsap] // main-delayed-output
alice2blockchain.expectMsgType[PublishAsap] // htlc-timeout
alice2blockchain.expectMsgType[PublishAsap] // claim-delayed-output
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]) // claim-delayed-output
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
test("recv BITCOIN_OUTPUT_SPENT") { f =>
import f._
// alice sends an htlc to bob
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
relayer.expectMsgType[ForwardAdd]
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
alice2blockchain.expectMsgType[PublishAsap] // main-delayed-output
alice2blockchain.expectMsgType[PublishAsap] // htlc-timeout
alice2blockchain.expectMsgType[PublishAsap] // claim-delayed-output
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]) // claim-delayed-output
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
// actual test starts here
relayer.expectMsgType[LocalChannelDown]
// actual test starts here
relayer.expectMsgType[LocalChannelDown]
// 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)
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessFromCommitTx)
assert(relayer.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
// 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)
alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, claimHtlcSuccessFromCommitTx)
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)
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)
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)
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)
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, _, _, _) =>
within(30 seconds) {
val listener = TestProbe()
system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed])
// alice sends an htlc to bob
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output
val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout
val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output
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]) // claim-delayed-output
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
test("recv BITCOIN_TX_CONFIRMED (local commit)") { f =>
import f._
val listener = TestProbe()
system.eventStream.subscribe(listener.ref, classOf[LocalCommitConfirmed])
// alice sends an htlc to bob
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx
val claimMainDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-delayed-output
val htlcTimeoutTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-timeout
val claimDelayedTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-delayed-output
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]) // claim-delayed-output
assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)
awaitCond(alice.stateName == CLOSING)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(initialState.localCommitPublished.isDefined)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0)
assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimDelayedTx), 202, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 42, 0)
assert(listener.expectMsgType[LocalCommitConfirmed].refundAtBlock == 42 + TestConstants.Bob.channelParams.toSelfDelay)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainDelayedTx), 200, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcTimeoutTx), 201, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimDelayedTx), 202, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_TX_CONFIRMED (local commit with htlcs only signed by local)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer, _) =>
within(30 seconds) {
val sender = TestProbe()
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
// note that bob doesn't receive the new sig!
// then we make alice unilaterally close the channel
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
awaitCond(alice.stateName == CLOSING)
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(aliceData.localCommitPublished.isDefined)
relayer.expectMsgType[LocalChannelDown]
test("recv BITCOIN_TX_CONFIRMED (local commit with htlcs only signed by local)") { f =>
import f._
val sender = TestProbe()
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
// note that bob doesn't receive the new sig!
// then we make alice unilaterally close the channel
alice ! Error("00" * 32, "oops".getBytes)
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
awaitCond(alice.stateName == CLOSING)
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(aliceData.localCommitPublished.isDefined)
relayer.expectMsgType[LocalChannelDown]
// 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
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 0, 0)
// so she fails it
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)))
}
// 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
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(aliceCommitTx), 0, 0)
// so she fails it
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)))
}
test("recv BITCOIN_TX_CONFIRMED (remote commit with htlcs only signed by local in next remote commit)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, relayer, _) =>
within(30 seconds) {
val sender = TestProbe()
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
// then we make alice believe bob unilaterally close the channel
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
awaitCond(alice.stateName == CLOSING)
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(aliceData.remoteCommitPublished.isDefined)
relayer.expectMsgType[LocalChannelDown]
test("recv BITCOIN_TX_CONFIRMED (remote commit with htlcs only signed by local in next remote commit)") { f =>
import f._
val sender = TestProbe()
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
// alice sends an htlc
val (r, htlc) = addHtlc(4200000, alice, bob, alice2bob, bob2alice)
// and signs it (but bob doesn't sign it)
sender.send(alice, CMD_SIGN)
sender.expectMsg("ok")
alice2bob.expectMsgType[CommitSig]
// then we make alice believe bob unilaterally close the channel
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
awaitCond(alice.stateName == CLOSING)
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
assert(aliceData.remoteCommitPublished.isDefined)
relayer.expectMsgType[LocalChannelDown]
// 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
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
// so she fails it
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)))
}
// 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
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
// so she fails it
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)))
}
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
val bobCommitTx = bobCommitTxes.last.commitTx.tx
assert(bobCommitTx.txOut.size == 2) // two main outputs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
val bobCommitTx = bobCommitTxes.last.commitTx.tx
assert(bobCommitTx.txOut.size == 2) // two main outputs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
}
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
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) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
val bobCommitTx = bobCommitTxes.last.commitTx.tx
assert(bobCommitTx.txOut.size == 2) // two main outputs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
test("recv BITCOIN_TX_CONFIRMED (remote commit)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes his last current commit tx, the one it had when entering NEGOTIATING state
val bobCommitTx = bobCommitTxes.last.commitTx.tx
assert(bobCommitTx.txOut.size == 2) // two main outputs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined)
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_TX_CONFIRMED (future remote commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
val sender = TestProbe()
val oldStateData = alice.stateData
val (ra1, htlca1) = addHtlc(25000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(htlca1.id, ra1, bob, alice, bob2alice, alice2bob)
crossSign(bob, alice, bob2alice, alice2bob)
// we simulate a disconnection
sender.send(alice, INPUT_DISCONNECTED)
sender.send(bob, INPUT_DISCONNECTED)
awaitCond(alice.stateName == OFFLINE)
awaitCond(bob.stateName == OFFLINE)
// then we manually replace alice's state with an older one
alice.setState(OFFLINE, oldStateData)
// 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]
// alice then realizes it has an old state...
bob2alice.forward(alice)
// ... and ask bob to publish its current commitment
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data) === PleasePublishYourCommitment(channelId(alice)).getMessage)
// alice now waits for bob to publish its commitment
awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT)
// bob is nice and publishes its commitment
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
// alice is able to claim its main output
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined)
test("recv BITCOIN_TX_CONFIRMED (future remote commit)") { f =>
import f._
val sender = TestProbe()
val oldStateData = alice.stateData
val (ra1, htlca1) = addHtlc(25000000, alice, bob, alice2bob, bob2alice)
crossSign(alice, bob, alice2bob, bob2alice)
fulfillHtlc(htlca1.id, ra1, bob, alice, bob2alice, alice2bob)
crossSign(bob, alice, bob2alice, alice2bob)
// we simulate a disconnection
sender.send(alice, INPUT_DISCONNECTED)
sender.send(bob, INPUT_DISCONNECTED)
awaitCond(alice.stateName == OFFLINE)
awaitCond(bob.stateName == OFFLINE)
// then we manually replace alice's state with an older one
alice.setState(OFFLINE, oldStateData)
// 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]
// alice then realizes it has an old state...
bob2alice.forward(alice)
// ... and ask bob to publish its current commitment
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data) === PleasePublishYourCommitment(channelId(alice)).getMessage)
// alice now waits for bob to publish its commitment
awaitCond(alice.stateName == WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT)
// bob is nice and publishes its commitment
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
// alice is able to claim its main output
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx
Transaction.correctlySpends(claimMainTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].futureRemoteCommitPublished.isDefined)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobCommitTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_FUNDING_SPENT (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
test("recv BITCOIN_FUNDING_SPENT (one revoked tx)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head.commitTx.tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
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].revokedCommitPublished.size == 1)
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) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes multiple revoked txes (last one isn't revoked)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(0).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
test("recv BITCOIN_FUNDING_SPENT (multiple revoked tx)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes multiple revoked txes (last one isn't revoked)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(0).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(1).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
alice2blockchain.expectNoMsg(1 second)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(1).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
alice2blockchain.expectNoMsg(1 second)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(2).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTxes(2).commitTx.tx)
// alice publishes and watches the penalty tx
alice2blockchain.expectMsgType[PublishAsap] // claim-main
alice2blockchain.expectMsgType[PublishAsap] // main-penalty
// alice2blockchain.expectMsgType[PublishAsap] // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
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) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
// alice publishes and watches the penalty tx
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
test("recv BITCOIN_OUTPUT_SPENT (one revoked tx, counterparty published HtlcSuccess tx)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
// alice publishes and watches the penalty tx
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx) // we published this
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-penalty
// val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, bobHtlcSuccessTx) // bob published his HtlcSuccess tx
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-success
// 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(claimHtlcDelayedPenaltyTxs), 0, 0) // bob won
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx) // we published this
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-penalty
// val bobHtlcSuccessTx = bobRevokedTx.htlcTxsAndSigs.head.txinfo.tx
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, bobHtlcSuccessTx) // bob published his HtlcSuccess tx
// alice2blockchain.expectMsgType[WatchConfirmed] // htlc-success
// 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(claimHtlcDelayedPenaltyTxs), 0, 0) // bob won
awaitCond(alice.stateName == CLOSED)
}
test("recv BITCOIN_TX_CONFIRMED (one revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, bobCommitTxes) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
// alice publishes and watches the penalty tx
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
test("recv BITCOIN_TX_CONFIRMED (one revoked tx)") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
// bob publishes one of his revoked txes
val bobRevokedTx = bobCommitTxes.head
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobRevokedTx.commitTx.tx)
// alice publishes and watches the penalty tx
val claimMainTx = alice2blockchain.expectMsgType[PublishAsap].tx // claim-main
val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // main-penalty
// val htlcPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx // htlc-penalty
alice2blockchain.expectMsgType[WatchConfirmed] // revoked commit
alice2blockchain.expectMsgType[WatchConfirmed] // claim-main
alice2blockchain.expectMsgType[WatchSpent] // main-penalty
// alice2blockchain.expectMsgType[WatchSpent] // htlc-penalty
alice2blockchain.expectNoMsg(1 second)
awaitCond(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.head.commitTx == bobRevokedTx.commitTx.tx)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx)
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcPenaltyTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(bobRevokedTx.commitTx.tx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(claimMainTx), 0, 0)
alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(mainPenaltyTx), 0, 0)
// alice ! WatchEventSpent(BITCOIN_OUTPUT_SPENT, htlcPenaltyTx)
// alice ! WatchEventConfirmed(BITCOIN_TX_CONFIRMED(htlcPenaltyTx), 0, 0)
awaitCond(alice.stateName == CLOSED)
}
test("recv ChannelReestablish") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
val sender = TestProbe()
sender.send(alice, ChannelReestablish(channelId(bob), 42, 42))
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data) === FundingTxSpent(channelId(alice), initialState.spendingTxes.head).getMessage)
}
test("recv ChannelReestablish") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
val sender = TestProbe()
sender.send(alice, ChannelReestablish(channelId(bob), 42, 42))
val error = alice2bob.expectMsgType[Error]
assert(new String(error.data) === FundingTxSpent(channelId(alice), initialState.spendingTxes.head).getMessage)
}
test("recv CMD_CLOSE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _, _) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
}
test("recv CMD_CLOSE") { f =>
import f._
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val sender = TestProbe()
sender.send(alice, CMD_CLOSE(None))
sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice))))
}
}

View file

@ -16,15 +16,13 @@
package fr.acinq.eclair.crypto
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.spongycastle.util.encoders.Hex
/**
* Created by fabrice on 22/06/17.
*/
@RunWith(classOf[JUnitRunner])
class BitStreamSpec extends FunSuite {
import BitStream._

View file

@ -17,12 +17,10 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.BinaryData
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ChaCha20Poly1305Spec extends FunSuite {
test("Poly1305") {
// 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.Crypto.{Point, Scalar}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class GeneratorsSpec extends FunSuite {
val base_secret: Scalar = BinaryData("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
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.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.{BinaryData, Block}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class LocalKeyManagerSpec extends FunSuite {
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

View file

@ -100,7 +100,6 @@ object NoiseDemo extends App {
def receive = {
case message: BinaryData =>
println(s"received ${new String(message)}")
sender ! BinaryData("response to ".getBytes() ++ message)
count = count + 1
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.eclair.crypto.Noise._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.spongycastle.crypto.ec.CustomNamedCurves
@RunWith(classOf[JUnitRunner])
class NoiseSpec extends FunSuite {
import Noise._

View file

@ -17,11 +17,9 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.{BinaryData, Hash}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ShaChainSpec extends FunSuite {
val expected: List[BinaryData] = List(
"f85a0f7f237ed2139855703db4264de380ec731f64a3d832c22a5f2ef615f1d5",

View file

@ -19,17 +19,14 @@ package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.BinaryData
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scodec.bits.BitVector
import scala.util.Success
/**
* Created by fabrice on 10/01/17.
*/
@RunWith(classOf[JUnitRunner])
class SphinxSpec extends FunSuite {
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.TransportHandler.{Encryptor, ExtendedCipherState, Listener}
import fr.acinq.eclair.wire.LightningMessageCodecs
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import scodec.Codec
import scodec.codecs._
@ -34,7 +32,7 @@ import scodec.codecs._
import scala.annotation.tailrec
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class TransportHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike with BeforeAndAfterAll {
import TransportHandlerSpec._

View file

@ -27,14 +27,12 @@ import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.{ChannelCodecs, UpdateAddHtlc}
import fr.acinq.eclair.{ShortChannelId, UInt64, randomKey}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by fabrice on 07/02/17.
*/
@RunWith(classOf[JUnitRunner])
class ChannelStateSpec extends FunSuite {
import ChannelStateSpec._
@ -109,7 +107,7 @@ object ChannelStateSpec {
remoteNextCommitInfo = Right(randomKey.publicKey),
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)
}

View file

@ -23,13 +23,11 @@ import fr.acinq.eclair.channel.NetworkFeePaid
import fr.acinq.eclair.db.sqlite.SqliteAuditDb
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
import fr.acinq.eclair.{randomBytes, randomKey}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.compat.Platform
@RunWith(classOf[JUnitRunner])
class SqliteAuditDbSpec extends FunSuite {
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
@ -59,7 +57,7 @@ class SqliteAuditDbSpec extends FunSuite {
db.add(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.listRelayed(from = 0L, to = Long.MaxValue).toList === List(e3))
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.eclair.db.sqlite.{SqliteChannelsDb, SqlitePendingRelayDb}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.sqlite.SQLiteException
@RunWith(classOf[JUnitRunner])
class SqliteChannelsDbSpec extends FunSuite {
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
@ -56,15 +54,15 @@ class SqliteChannelsDbSpec extends FunSuite {
db.addOrUpdateChannel(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, paymentHash2, cltvExpiry2)
assert(db.listHtlcHtlcInfos(channel.channelId, commitNumber).toList == List((paymentHash1, cltvExpiry1), (paymentHash2, cltvExpiry2)))
assert(db.listHtlcHtlcInfos(channel.channelId, 43).toList == Nil)
assert(db.listHtlcInfos(channel.channelId, commitNumber).toList == List((paymentHash1, cltvExpiry1), (paymentHash2, cltvExpiry2)))
assert(db.listHtlcInfos(channel.channelId, 43).toList == Nil)
db.removeChannel(channel.channelId)
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.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.{ShortChannelId, randomKey}
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.Color
import org.junit.runner.RunWith
import fr.acinq.eclair.wire.{ChannelUpdate, Color}
import fr.acinq.eclair.{ShortChannelId, randomKey}
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import org.sqlite.SQLiteException
@RunWith(classOf[JUnitRunner])
class SqliteNetworkDbSpec extends FunSuite {
def inmem = DriverManager.getConnection("jdbc:sqlite::memory:")
@ -85,9 +83,9 @@ class SqliteNetworkDbSpec extends FunSuite {
db.removeChannel(channel_2.shortChannelId)
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_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(43), 5, 7000000, 50000, 100, true)
val channel_update_3 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, randomKey, randomKey.publicKey, ShortChannelId(44), 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, 500000000L, 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)
db.addChannelUpdate(channel_update_1)
@ -95,6 +93,10 @@ class SqliteNetworkDbSpec extends FunSuite {
assert(db.listChannelUpdates().size === 1)
intercept[SQLiteException](db.addChannelUpdate(channel_update_2))
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)
assert(db.listChannels().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.eclair.db.sqlite.SqlitePaymentsDb
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class SqlitePaymentsDbSpec extends FunSuite {
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.randomKey
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class SqlitePeersDbSpec extends FunSuite {
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.randomBytes
import fr.acinq.eclair.wire.FailureMessageCodecs
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class SqlitePendingRelayDbSpec extends FunSuite {
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 org.json4s.JsonAST.JValue
import org.json4s.{DefaultFormats, JString}
import org.junit.Ignore
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike, Ignore}
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
@ -56,7 +53,6 @@ import scala.concurrent.duration._
/**
* Created by PM on 15/03/2017.
*/
@RunWith(classOf[JUnitRunner])
@Ignore
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))
val commitmentBC = sender.expectMsgType[RES_GETINFO].data.asInstanceOf[DATA_NORMAL].commitments
// 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
sender.send(nodes("B").relayer, LocalChannelUpdate(system.deadLetters, commitmentBC.channelId, shortIdBC, commitmentBC.remoteParams.nodeId, None, channelUpdateBC, commitmentBC))
// 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)
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)
// 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, CMD_GETINFO))
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]
sender.expectMsgType[PaymentSucceeded]
// 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) = {
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)
sender.send(paymentInitiator, sendReq)
}
val buffer = TestProbe()
send(100000000, paymentHandlerF, nodes("C").paymentInitiator) // will be left pending
forwardHandlerF.expectMsgType[UpdateAddHtlc]
@ -799,12 +799,9 @@ class IntegrationSpec extends TestKit(ActorSystem("test")) with BitcoindService
// then we make the announcements
val announcements = channels.map(c => AnnouncementsBatchValidationSpec.makeChannelAnnouncement(c))
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({
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)
}

View file

@ -28,9 +28,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
import fr.acinq.eclair.channel._
import fr.acinq.eclair.payment.NoopPaymentHandler
import fr.acinq.eclair.wire.Init
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, Matchers, fixture}
import org.scalatest.{BeforeAndAfterAll, Matchers, Outcome, fixture}
import scala.concurrent.duration._
import scala.io.Source
@ -38,12 +36,12 @@ import scala.io.Source
/**
* Created by PM on 30/05/2016.
*/
@RunWith(classOf[JUnitRunner])
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)
val latch = new CountDownLatch(1)
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]
awaitCond(alice.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 {
@ -87,15 +85,15 @@ class RustyTestsSpec extends TestKit(ActorSystem("test")) with Matchers with fix
TestKit.shutdownActorSystem(system)
}
test("01-offer1") { case (ref, res) => assert(ref === res) }
test("02-offer2") { case (ref, res) => assert(ref === res) }
//test("03-fulfill1") { case (ref, res) => assert(ref === 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("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("10-offers-crossover") { case (ref, res) => assert(ref === res) }
test("11-commits-crossover") { case (ref, res) => assert(ref === res) }
/*test("13-fee") { case (ref, res) => assert(ref === res)}
test("14-fee-twice") { case (ref, res) => assert(ref === res)}
test("15-fee-twice-back-to-back") { case (ref, res) => assert(ref === res)}*/
test("01-offer1") { f => assert(f.ref === f.res) }
test("02-offer2") { f => assert(f.ref === f.res) }
//test("03-fulfill1") { f => assert(f.ref === f.res) }
// 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") { f => assert(f.ref === f.res)} DOES NOT PASS : cannot send two commit in a row (without having first revocation)
test("10-offers-crossover") { f => assert(f.ref === f.res) }
test("11-commits-crossover") { f => assert(f.ref === f.res) }
/*test("13-fee") { f => assert(f.ref === f.res)}
test("14-fee-twice") { f => assert(f.ref === f.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
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class NodeURISpec extends FunSuite {
val PUBKEY = "03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134"

View file

@ -4,22 +4,20 @@ import java.net.InetSocketAddress
import akka.actor.ActorRef
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Block
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.TestConstants._
import fr.acinq.eclair.blockchain.EclairWallet
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.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast, RouteCalculationSpec}
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, randomKey, wire}
import org.junit.runner.RunWith
import fr.acinq.eclair.router.{ChannelRangeQueries, ChannelRangeQueriesSpec, Rebroadcast}
import fr.acinq.eclair.wire.Error
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, wire}
import org.scalatest.Outcome
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class PeerSpec extends TestkitBaseClass {
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds.take(100)
val fakeRoutingInfo = shortChannelIds.map(makeFakeRoutingInfo)
@ -27,42 +25,9 @@ class PeerSpec extends TestkitBaseClass {
val updates = (fakeRoutingInfo.map(_._2) ++ fakeRoutingInfo.map(_._3)).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 = {
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 watcher = TestProbe()
val router = TestProbe()
@ -72,8 +37,12 @@ class PeerSpec extends TestkitBaseClass {
val wallet: EclairWallet = null // unused
val remoteNodeId = Bob.nodeParams.nodeId
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
val probe = TestProbe()
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))
transport.expectMsgType[TransportHandler.Listener]
@ -83,10 +52,56 @@ class PeerSpec extends TestkitBaseClass {
router.expectNoMsg(1 second) // bob's features require no sync
probe.send(peer, Peer.GetPeerInfo)
assert(probe.expectMsgType[Peer.PeerInfo].state == "CONNECTED")
}
val channels = for (_ <- 0 until 12) yield RouteCalculationSpec.makeChannel(Random.nextInt(10000000), randomKey.publicKey, randomKey.publicKey)
val updates = for (_ <- 0 until 20) yield RouteCalculationSpec.makeUpdate(Random.nextInt(10000000), randomKey.publicKey, randomKey.publicKey, Random.nextInt(1000), Random.nextInt(1000))._2
val query = wire.QueryShortChannelIds(Block.RegtestGenesisBlock.hash, ChannelRangeQueries.encodeShortChannelIdsSingle(Seq(ShortChannelId(42000)), ChannelRangeQueries.UNCOMPRESSED_FORMAT, useGzip = false))
test("filter gossip message (no filtering)") { 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)
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
for (ann <- channels ++ updates) {
@ -142,5 +157,12 @@ class PeerSpec extends TestkitBaseClass {
router.expectMsg(Peer.PeerRoutingMessage(transport.ref, remoteNodeId, c))
}
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.wire.{ChannelUpdate, LightningMessageCodecs, PerHopPayload}
import fr.acinq.eclair.{ShortChannelId, TestConstants, nodeFee, randomBytes}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scodec.bits.BitVector
import scala.util.Success
@ -35,7 +33,7 @@ import scala.util.Success
/**
* Created by PM on 31/05/2016.
*/
@RunWith(classOf[JUnitRunner])
class HtlcGenerationSpec extends FunSuite {
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 (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 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_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)

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.wire.{FinalExpiryTooSoon, UpdateAddHtlc}
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
import org.junit.runner.RunWith
import org.scalatest.FunSuiteLike
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
/**
* Created by PM on 24/03/2017.
*/
@RunWith(classOf[JUnitRunner])
class PaymentHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
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._
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 29/08/2016.
*/
@RunWith(classOf[JUnitRunner])
class PaymentLifecycleSpec extends BaseRouterSpec {
val initialBlockCount = 420000
@ -45,7 +43,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
val defaultAmountMsat = 142000000L
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 monitor = TestProbe()
val sender = TestProbe()
@ -60,7 +59,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
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 monitor = TestProbe()
val sender = TestProbe()
@ -75,7 +75,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
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 routerForwarder = TestProbe()
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))
}
test("payment failed (local error)") { case (router, _) =>
test("payment failed (local error)") { fixture =>
import fixture._
val relayer = TestProbe()
val routerForwarder = TestProbe()
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
@ -139,7 +141,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
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 routerForwarder = TestProbe()
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
@ -166,7 +169,8 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
awaitCond(paymentFSM.stateName == WAITING_FOR_ROUTE)
}
test("payment failed (TemporaryChannelFailure)") { case (router, _) =>
test("payment failed (TemporaryChannelFailure)") { fixture =>
import fixture._
val relayer = TestProbe()
val routerForwarder = TestProbe()
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))
}
test("payment failed (Update)") { case (router, _) =>
test("payment failed (Update)") { fixture =>
import fixture._
val relayer = TestProbe()
val routerForwarder = TestProbe()
val paymentFSM = TestFSMRef(new PaymentLifecycle(a, routerForwarder.ref, relayer.ref))
@ -223,7 +228,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
relayer.expectMsg(ForwardShortId(channelId_ab, cmd1))
// 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)
// and node replies with a failure containing a new channel update
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))
// 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)
// and node replies with a failure containing a new channel update
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))
}
test("payment failed (PermanentChannelFailure)") { case (router, _) =>
test("payment failed (PermanentChannelFailure)") { fixture =>
import fixture._
val relayer = TestProbe()
val routerForwarder = TestProbe()
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))
}
test("payment succeeded") { case (router, _) =>
test("payment succeeded") { fixture =>
import fixture._
val paymentFSM = system.actorOf(PaymentLifecycle.props(a, router, TestProbe().ref))
val monitor = TestProbe()
val sender = TestProbe()
@ -315,7 +322,7 @@ class PaymentLifecycleSpec extends BaseRouterSpec {
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 filtered = PaymentLifecycle.transformForUser(failures)
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.eclair.ShortChannelId
import fr.acinq.eclair.payment.PaymentRequest._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by fabrice on 15/05/17.
*/
@RunWith(classOf[JUnitRunner])
class PaymentRequestSpec extends FunSuite {
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.wire._
import fr.acinq.eclair.{TestConstants, TestkitBaseClass, randomBytes, randomKey}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
/**
* Created by PM on 29/08/2016.
*/
@RunWith(classOf[JUnitRunner])
class RelayerSpec extends TestkitBaseClass {
// let's reuse the existing test data
import HtlcGenerationSpec._
type FixtureParam = Tuple3[ActorRef, TestProbe, TestProbe]
override def withFixture(test: OneArgTest) = {
case class FixtureParam(relayer: ActorRef, register: TestProbe, paymentHandler: TestProbe)
override def withFixture(test: OneArgTest): Outcome = {
within(30 seconds) {
val register = TestProbe()
val paymentHandler = TestProbe()
// 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, 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),
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()
// we use this to build a valid onion
@ -80,7 +79,8 @@ class RelayerSpec extends TestkitBaseClass {
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()
// we use this to build a valid onion
@ -99,7 +99,8 @@ class RelayerSpec extends TestkitBaseClass {
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()
// we use this to build a valid onion
@ -125,7 +126,8 @@ class RelayerSpec extends TestkitBaseClass {
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()
// check that payments are sent properly
@ -143,7 +145,7 @@ class RelayerSpec extends TestkitBaseClass {
paymentHandler.expectNoMsg(100 millis)
// 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 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)
}
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()
// we use this to build a valid onion
val (cmd, _) = buildCommand(finalAmountMsat, finalExpiry, paymentHash, hops)
// and then manually build an htlc
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))
sender.send(relayer, ForwardAdd(add_ab))
val fail = register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message
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)
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()
// we use this to build a valid onion
@ -196,7 +200,8 @@ class RelayerSpec extends TestkitBaseClass {
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()
// we use this to build a valid onion
@ -215,7 +220,8 @@ class RelayerSpec extends TestkitBaseClass {
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 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)
}
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 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)
}
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()
// 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)
}
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()
// 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)
}
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 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)))
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)))
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)))
assert(register.expectMsgType[Register.Forward[CMD_FAIL_HTLC]].message.reason === Right(PermanentChannelFailure))
@ -322,7 +332,8 @@ class RelayerSpec extends TestkitBaseClass {
paymentHandler.expectNoMsg(100 millis)
}
test("relay an htlc-fulfill") { case (relayer, register, _) =>
test("relay an htlc-fulfill") { f =>
import f._
val sender = 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))
}
test("relay an htlc-fail") { case (relayer, register, _) =>
test("relay an htlc-fail") { f =>
import f._
val sender = TestProbe()
// we build a fake htlc for the downstream channel

View file

@ -17,8 +17,8 @@
package fr.acinq.eclair.router
import akka.actor.ActorSystem
import akka.testkit.TestProbe
import akka.pattern.pipe
import akka.testkit.TestProbe
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{BinaryData, Block, Satoshi, Script, Transaction}
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.wire.{ChannelAnnouncement, ChannelUpdate}
import fr.acinq.eclair.{ShortChannelId, randomKey}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContext}
@ -37,7 +35,7 @@ import scala.concurrent.{Await, ExecutionContext}
/**
* Created by PM on 31/05/2016.
*/
@RunWith(classOf[JUnitRunner])
class AnnouncementsBatchValidationSpec extends FunSuite {
import AnnouncementsBatchValidationSpec._
@ -103,6 +101,6 @@ object AnnouncementsBatchValidationSpec {
}
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._
import fr.acinq.eclair.router.Announcements._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
/**
* Created by PM on 31/05/2016.
*/
@RunWith(classOf[JUnitRunner])
class AnnouncementsSpec extends FunSuite {
test("check nodeId1/nodeId2 lexical ordering") {
@ -55,7 +53,7 @@ class AnnouncementsSpec extends FunSuite {
}
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, randomKey.publicKey) === false)
}
@ -66,22 +64,22 @@ class AnnouncementsSpec extends FunSuite {
// NB: node1 < node2 (public keys)
assert(isNode1(node1_priv.publicKey.toBin, node2_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_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node1_priv, node2_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = false)
val channelUpdate2 = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = true)
val channelUpdate2_disabled = makeChannelUpdate(Block.RegtestGenesisBlock.hash, node2_priv, node1_priv.publicKey, ShortChannelId(0), 0, 0, 0, 0, enable = false)
assert(channelUpdate1.flags == BinaryData("0000")) // ....00
assert(channelUpdate1_disabled.flags == BinaryData("0002")) // ....10
assert(channelUpdate2.flags == BinaryData("0001")) // ....01
assert(channelUpdate2_disabled.flags == BinaryData("0003")) // ....11
assert(isNode1(channelUpdate1.flags))
assert(isNode1(channelUpdate1_disabled.flags))
assert(!isNode1(channelUpdate2.flags))
assert(!isNode1(channelUpdate2_disabled.flags))
assert(isEnabled(channelUpdate1.flags))
assert(!isEnabled(channelUpdate1_disabled.flags))
assert(isEnabled(channelUpdate2.flags))
assert(!isEnabled(channelUpdate2_disabled.flags))
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, 500000000L, enable = false)
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, 500000000L, enable = false)
assert(channelUpdate1.channelFlags == 0) // ....00
assert(channelUpdate1_disabled.channelFlags == 2) // ....10
assert(channelUpdate2.channelFlags == 1) // ....01
assert(channelUpdate2_disabled.channelFlags == 3) // ....11
assert(isNode1(channelUpdate1.channelFlags))
assert(isNode1(channelUpdate1_disabled.channelFlags))
assert(!isNode1(channelUpdate2.channelFlags))
assert(!isNode1(channelUpdate2_disabled.channelFlags))
assert(isEnabled(channelUpdate1.channelFlags))
assert(!isEnabled(channelUpdate1_disabled.channelFlags))
assert(isEnabled(channelUpdate2.channelFlags))
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.wire._
import fr.acinq.eclair.{TestkitBaseClass, randomKey, _}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.Outcome
import scala.concurrent.duration._
@ -38,12 +37,10 @@ import scala.concurrent.duration._
* It is re-used in payment FSM tests
* Created by PM on 29/08/2016.
*/
@RunWith(classOf[JUnitRunner])
abstract class BaseRouterSpec extends TestkitBaseClass {
import BaseRouterSpec._
type FixtureParam = Tuple2[ActorRef, TestProbe]
case class FixtureParam(router: ActorRef, watcher: TestProbe)
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_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_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, a, channelId_ab, cltvExpiryDelta = 7, 0, feeBaseMsat = 766000, feeProportionalMillionths = 10)
val channelUpdate_bc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, c, channelId_bc, cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
val channelUpdate_cb = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, b, channelId_bc, cltvExpiryDelta = 5, 0, feeBaseMsat = 233000, feeProportionalMillionths = 1)
val channelUpdate_cd = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_c, d, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
val channelUpdate_dc = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_d, c, channelId_cd, cltvExpiryDelta = 3, 0, feeBaseMsat = 153000, feeProportionalMillionths = 4)
val channelUpdate_ef = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_e, f, channelId_ef, cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
val channelUpdate_fe = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_f, e, channelId_ef, cltvExpiryDelta = 9, 0, feeBaseMsat = 786000, feeProportionalMillionths = 8)
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, htlcMinimumMsat = 0, feeBaseMsat = 766000, feeProportionalMillionths = 10, htlcMaximumMsat = 500000000L)
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, htlcMinimumMsat = 0, feeBaseMsat = 233000, feeProportionalMillionths = 1, htlcMaximumMsat = 500000000L)
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, htlcMinimumMsat = 0, feeBaseMsat = 153000, feeProportionalMillionths = 4, htlcMaximumMsat = 500000000L)
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, 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)
within(30 seconds) {
@ -151,7 +148,7 @@ abstract class BaseRouterSpec extends TestkitBaseClass {
channels.size === 4 && updates.size === 8
}, 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.eclair.ShortChannelId
import fr.acinq.eclair.router.ChannelRangeQueriesSpec.shortChannelIds
import fr.acinq.eclair.wire.{ReplyChannelRange, ReplyChannelRangeEx}
import org.junit.runner.RunWith
import fr.acinq.eclair.wire.ReplyChannelRangeEx
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.collection.immutable.SortedMap
import scala.util.Random
@RunWith(classOf[JUnitRunner])
class ChannelRangeQueriesExSpec extends FunSuite {
import ChannelRangeQueriesEx._
val random = new Random()
val shortChannelIds = ChannelRangeQueriesSpec.shortChannelIds
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.eclair.ShortChannelId
import fr.acinq.eclair.wire.ReplyChannelRange
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.collection.{SortedSet, immutable}
@RunWith(classOf[JUnitRunner])
class ChannelRangeQueriesSpec extends FunSuite {
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.wire._
import fr.acinq.eclair.{ShortChannelId, TestkitBaseClass, TxCoordinates}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfterAll, Outcome}
import scala.collection.{SortedSet, immutable}
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class PruningSpec extends TestkitBaseClass with BeforeAndAfterAll {
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.eclair.payment.PaymentRequest.ExtraHop
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.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
import scala.compat.Platform
import scala.util.{Failure, Success}
/**
* Created by PM on 31/05/2016.
*/
@RunWith(classOf[JUnitRunner])
class RouteCalculationSpec extends FunSuite {
import RouteCalculationSpec._
@ -191,14 +188,14 @@ class RouteCalculationSpec extends FunSuite {
val DUMMY_SIG = BinaryData("3045022100e0a180fdd0fe38037cc878c03832861b40a29d32bd7b40b10c9e1efc8c1468a002205ae06d1624896d0d29f4b31e32772ea3cb1b4d7ed4e077e5da28dcc33c0e781201")
val uab = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 0L, "0000", 1, 42, 2500, 140)
val uba = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(1L), 1L, "0001", 1, 43, 2501, 141)
val ubc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, "0000", 1, 44, 2502, 142)
val ucb = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(2L), 1L, "0001", 1, 45, 2503, 143)
val ucd = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, "0000", 1, 46, 2504, 144)
val udc = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(3L), 1L, "0001", 1, 47, 2505, 145)
val ude = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, "0000", 1, 48, 2506, 146)
val ued = ChannelUpdate(DUMMY_SIG, Block.RegtestGenesisBlock.hash, ShortChannelId(4L), 1L, "0001", 1, 49, 2507, 147)
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, 0, 1, 1, 43, 2501, 141, None)
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, 0, 1, 1, 45, 2503, 143, None)
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, 0, 1, 1, 47, 2505, 145, None)
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, 0, 1, 1, 49, 2507, 147, None)
val updates = Map(
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) =
(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]) = {

View file

@ -28,40 +28,42 @@ import fr.acinq.eclair.crypto.TransportHandler
import fr.acinq.eclair.io.Peer.{InvalidSignature, PeerRoutingMessage}
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.router.Announcements.makeChannelUpdate
import fr.acinq.eclair.wire.Error
import fr.acinq.eclair.{ShortChannelId, randomKey}
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import fr.acinq.eclair.transactions.Scripts
import fr.acinq.eclair.wire.QueryShortChannelIds
import fr.acinq.eclair.{Globals, ShortChannelId, randomKey}
import scala.collection.SortedSet
import scala.compat.Platform
import scala.concurrent.duration._
/**
* Created by PM on 29/08/2016.
*/
@RunWith(classOf[JUnitRunner])
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()
system.eventStream.subscribe(eventListener.ref, classOf[NetworkEvent])
val channelId_ac = ShortChannelId(420000, 5, 0)
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
val priv_x = 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
val priv_y = randomKey
val priv_funding_y = randomKey
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
val priv_z = randomKey
val priv_funding_z = randomKey
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_ax)
@ -86,7 +88,8 @@ class RouterSpec extends BaseRouterSpec {
//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()
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 channelId_ac = ShortChannelId(420000, 5, 0)
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))
}
ignore("handle bad signature for NodeAnnouncement") { case (router, _) =>
ignore("handle bad signature for NodeAnnouncement") { fixture =>
import fixture._
val sender = TestProbe()
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))
@ -128,7 +133,8 @@ class RouterSpec extends BaseRouterSpec {
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 buggy_channelUpdate_ab = channelUpdate_ab.copy(signature = ann_b.signature, timestamp = channelUpdate_ab.timestamp + 1)
sender.send(router, PeerRoutingMessage(null, remoteNodeId, buggy_channelUpdate_ab))
@ -136,28 +142,32 @@ class RouterSpec extends BaseRouterSpec {
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()
// no route a->f
sender.send(router, RouteRequest(a, f))
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()
// no route a->f
sender.send(router, RouteRequest(randomKey.publicKey, f))
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()
// no route a->f
sender.send(router, RouteRequest(a, randomKey.publicKey))
sender.expectMsg(Failure(RouteNotFound))
}
test("route found") { case (router, _) =>
test("route found") { fixture =>
import fixture._
val sender = TestProbe()
sender.send(router, RouteRequest(a, d))
val res = sender.expectMsgType[RouteResponse]
@ -165,7 +175,8 @@ class RouterSpec extends BaseRouterSpec {
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 x = randomKey.publicKey
val y = randomKey.publicKey
@ -179,21 +190,23 @@ class RouterSpec extends BaseRouterSpec {
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()
sender.send(router, RouteRequest(a, d))
val res = sender.expectMsgType[RouteResponse]
assert(res.hops.map(_.nodeId).toList === a :: b :: c :: Nil)
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.expectMsg(TransportHandler.ReadAck(channelUpdate_cd1))
sender.send(router, RouteRequest(a, d))
sender.expectMsg(Failure(RouteNotFound))
}
test("temporary channel exclusion") { case (router, _) =>
test("temporary channel exclusion") { fixture =>
import fixture._
val sender = TestProbe()
sender.send(router, RouteRequest(a, d))
sender.expectMsgType[RouteResponse]
@ -211,4 +224,57 @@ class RouterSpec extends BaseRouterSpec {
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.io.Peer.PeerRoutingMessage
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.FunSuiteLike
import org.scalatest.junit.JUnitRunner
import scala.collection.immutable.TreeMap
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class RoutingSyncExSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
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.BaseRouterSpec.channelAnnouncement
import fr.acinq.eclair.wire._
import org.junit.runner.RunWith
import org.scalatest.FunSuiteLike
import org.scalatest.junit.JUnitRunner
import scala.concurrent.duration._
@RunWith(classOf[JUnitRunner])
class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
import RoutingSyncSpec.makeFakeRoutingInfo
@ -37,7 +35,7 @@ class RoutingSyncSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
val QueryChannelRange(chainHash, firstBlockNum, numberOfBlocks) = sender.expectMsgType[QueryChannelRange]
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(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)
@ -118,8 +116,8 @@ object RoutingSyncSpec {
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 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_ba = makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv_b, priv_a.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, 500000000L, timestamp = blockHeight)
val nodeAnnouncement_a = makeNodeAnnouncement(priv_a, "a", Alice.nodeParams.color, List())
val nodeAnnouncement_b = makeNodeAnnouncement(priv_b, "b", Bob.nodeParams.color, List())
(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._
import fr.acinq.eclair.transactions.Scripts._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ClaimReceivedHtlcSpec extends FunSuite {
object Alice {

View file

@ -19,11 +19,9 @@ package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin._
import fr.acinq.eclair.transactions.Scripts._
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class ClaimSentHtlcSpec extends FunSuite {
object Alice {

View file

@ -18,11 +18,9 @@ package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.{BinaryData, Crypto}
import fr.acinq.eclair.wire.{UpdateAddHtlc, UpdateFailHtlc, UpdateFulfillHtlc}
import org.junit.runner.RunWith
import org.scalatest.FunSuite
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class CommitmentSpecSpec extends FunSuite {
test("add, fulfill and fail htlcs from the sender side") {
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.transactions.Transactions.{HtlcSuccessTx, HtlcTimeoutTx, TransactionWithInputInfo}
import fr.acinq.eclair.wire.UpdateAddHtlc
import grizzled.slf4j.Logging
import org.scalatest.FunSuite
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 current = collection.mutable.HashMap.empty[String, String]
@ -120,7 +121,7 @@ class TestVectorsSpec extends FunSuite {
val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000")
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)
@ -128,18 +129,18 @@ class TestVectorsSpec extends FunSuite {
val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, true, Local.payment_basepoint, Remote.payment_basepoint)
assert(obscured_tx_number === (0x2bb038521914L ^ 42L))
println(s"local_payment_basepoint: ${Local.payment_basepoint}")
println(s"remote_payment_basepoint: ${Remote.payment_basepoint}")
println(s"local_funding_privkey: ${Local.funding_privkey}")
println(s"local_funding_pubkey: ${Local.funding_pubkey}")
println(s"remote_funding_privkey: ${Remote.funding_privkey}")
println(s"remote_funding_pubkey: ${Remote.funding_pubkey}")
println(s"local_secretkey: ${Local.payment_privkey}")
println(s"localkey: ${Local.payment_privkey.publicKey}")
println(s"remotekey: ${Remote.payment_privkey.publicKey}")
println(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}")
println(s"local_revocation_key: ${Local.revocation_pubkey}")
println(s"# funding wscript = ${commitmentInput.redeemScript}")
logger.info(s"local_payment_basepoint: ${Local.payment_basepoint}")
logger.info(s"remote_payment_basepoint: ${Remote.payment_basepoint}")
logger.info(s"local_funding_privkey: ${Local.funding_privkey}")
logger.info(s"local_funding_pubkey: ${Local.funding_pubkey}")
logger.info(s"remote_funding_privkey: ${Remote.funding_privkey}")
logger.info(s"remote_funding_pubkey: ${Remote.funding_pubkey}")
logger.info(s"local_secretkey: ${Local.payment_privkey}")
logger.info(s"localkey: ${Local.payment_privkey.publicKey}")
logger.info(s"remotekey: ${Remote.payment_privkey.publicKey}")
logger.info(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}")
logger.info(s"local_revocation_key: ${Local.revocation_pubkey}")
logger.info(s"# funding wscript = ${commitmentInput.redeemScript}")
assert(commitmentInput.redeemScript == BinaryData("5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae"))
val paymentPreimages = Seq(
@ -168,17 +169,16 @@ class TestVectorsSpec extends FunSuite {
}
for (i <- 0 until htlcs.length) {
println(s"htlc $i direction: ${dir2string(htlcs(i).direction)}")
println(s"htlc $i amount_msat: ${htlcs(i).add.amountMsat}")
println(s"htlc $i expiry: ${htlcs(i).add.expiry}")
println(s"htlc $i payment_preimage: ${paymentPreimages(i)}")
logger.info(s"htlc $i direction: ${dir2string(htlcs(i).direction)}")
logger.info(s"htlc $i amount_msat: ${htlcs(i).add.amountMsat}")
logger.info(s"htlc $i expiry: ${htlcs(i).add.expiry}")
logger.info(s"htlc $i payment_preimage: ${paymentPreimages(i)}")
}
println()
def run(spec: CommitmentSpec) = {
println(s"to_local_msat: ${spec.toLocalMsat}")
println(s"to_remote_msat: ${spec.toRemoteMsat}")
println(s"local_feerate_per_kw: ${spec.feeratePerKw}")
logger.info(s"to_local_msat: ${spec.toLocalMsat}")
logger.info(s"to_remote_msat: ${spec.toRemoteMsat}")
logger.info(s"local_feerate_per_kw: ${spec.feeratePerKw}")
val commitTx = {
val tx = Transactions.makeCommitTx(
@ -197,16 +197,16 @@ class TestVectorsSpec extends FunSuite {
}
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
println(s"# actual commitment transaction fee = ${actualFee.toLong}")
logger.info(s"# actual commitment transaction fee = ${actualFee.toLong}")
commitTx.tx.txOut.map(txOut => {
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 =>
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))}")
else println(s"# HTLC ${if (htlcs(index).direction == OUT) "offered" else "received"} amount ${txOut.amount.toLong} wscript ${Script.write(htlcScripts(index))}")
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 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)
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)
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)
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(
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
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)
@ -246,13 +246,13 @@ class TestVectorsSpec extends FunSuite {
case tx: HtlcSuccessTx =>
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
println(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
println(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
case tx: HtlcTimeoutTx =>
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
println(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
println(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
logger.info(s"# signature for output ${tx.input.outPoint.index} (htlc $htlcIndex)")
logger.info(s"remote_htlc_signature: ${toHexString(remoteSig.dropRight(1))}")
}
val signedTxs = htlcTxs collect {
@ -264,27 +264,26 @@ class TestVectorsSpec extends FunSuite {
val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage)
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
println(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
println(s"output htlc_success_tx ${htlcIndex}: ${tx1.tx}")
logger.info(s"# local_signature = ${toHexString(localSig.dropRight(1))}")
logger.info(s"output htlc_success_tx ${htlcIndex}: ${tx1.tx}")
tx1
case tx: HtlcTimeoutTx =>
val localSig = Transactions.sign(tx, Local.payment_privkey)
val remoteSig = Transactions.sign(tx, Remote.payment_privkey)
val tx1 = Transactions.addSigs(tx, localSig, remoteSig)
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))
println(s"output htlc_timeout_tx ${htlcIndex}: ${tx1.tx}")
logger.info(s"output htlc_timeout_tx ${htlcIndex}: ${tx1.tx}")
tx1
}
println
(commitTx, signedTxs)
}
test("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 (commitTx, htlcTxs) = run(spec)
@ -295,7 +294,7 @@ class TestVectorsSpec extends FunSuite {
test("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 (commitTx, htlcTxs) = run(spec)
@ -305,7 +304,7 @@ class TestVectorsSpec extends FunSuite {
test("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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 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)") {
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 (commitTx, htlcTxs) = run(spec)
@ -458,7 +457,7 @@ class TestVectorsSpec extends FunSuite {
test("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 (commitTx, htlcTxs) = run(spec)
@ -471,7 +470,7 @@ class TestVectorsSpec extends FunSuite {
test("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 (commitTx, htlcTxs) = run(spec)

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