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 by2c1811d
: we now don't force sending a channel_update at the same time with channel_announcement. This greatly simplifies the rebroadcast logic, and is what caused the integration test to fail. Added proper test on Peer, testing the actor, not only static methods. * Routing sync fixes (#712) * Router: reset sync state on reconnection When we're reconnected to a peer we will start a new sync process and should reset our sync state with that peer. * Ignore 'origin htlc not found' in CLOSING (#708) If we don't have the origin, it means that we already have forwarded the fulfill so that's not a big deal. This can happen if they send a signature containing the fulfill, then fail the channel before we have time to sign it. * Fix handling of born again channels (#717) * Fix handling of born again channels When we receive a recent update for a channel that we had marked as stale we must send a query to the underlying transport, not the origin of the update (which would send the query back to the router) * Replace `update_fee` in commitments (#709) This is a simple optimisation, we don't have to keep all `update_fee`, just the last one. cf BOLT 2: > An update_fee message is sent by the node which is paying the Bitcoin fee. Like any update, it's first committed to the receiver's commitment transaction and then (once acknowledged) committed to the sender's. Unlike an HTLC, update_fee is never closed but simply replaced. * Tests: use bitcoind 0.16.3 (#715) Bitcoind 0.16.0 is no longer available * Make `publishTransaction` idempotent (#711) Bitcoin core returns an error `missing inputs (code: -25)` if the tx that we want to publish has already been published and its output have been spent. When we receive this error, we try to get the tx, in order to know if it is in the blockchain, or if its inputs were spent by another tx. Note: If the outputs of the tx were still unspent, bitcoin core would return "transaction already in block chain (code: -27)" and this is already handled. * Improved eclair-cli (#718) This fixes #695, and also adds the channel point in the default channel output. ```bash $ ./eclair-cli channel 00fd4d56d94af93765561bb6cb081f519b9627d3f455eba3215a7846a1af0e46 { "nodeId": "0232e20e7b68b9b673fb25f48322b151a93186bffe4550045040673797ceca43cf", "shortChannelId": "845230006070001", "channelId": "00fd4d56d94af93765561bb6cb081f519b9627d3f455eba3215a7846a1af0e46", "state": "NORMAL", "balanceSat": 9858759, "capacitySat": 10000000, "channelPoint": "470eafa146785a21a3eb55f4d327969b511f08cbb61b566537f94ad9564dfd00:1" } ``` * Handle update relay fee in OFFLINE state (#719) Previously it was only possible to update relay fee in NORMAL state, which is not very convenient because most of the time there are always some channels in OFFLINE state. This works like the NORMAL case, except that the new `channel_update` won't be broadcast immediately. It will be sent out next time the channel goes back to NORMAL, in the same `channel_update` that sets the `enable` flag to true. Also added a default handler that properly rejects the CMD_UPDATE_RELAY_FEE command in all other states. * Fixed regression caused by7a4f175
(#722) When updating relay fee in state OFFLINE, the new channel_update must have the disabled flag on. This caused tests to be flaky, added necessary checks to always make them fail in case that kind of regression happens again. * Logging: use a rolling file appender (#721) * Logging: use a rolling file appender Use one file per day, keep 90 days of logs with a total maximum size capped at 5 Gb * Router: log routing broadcast in debug level only * set version to 0.2-beta6 * set version back to 0.2-SNAPSHOT * Simplify bitcoind version check (#731) Bitcoind returns version as MMmmrr (major, minor, revision), use an int representation and compare it to our minimum version target. * Update scalatest and remove junit runner (#728) * updated to scalatest 3.0.5 * use scalatest runner instead of junit Output is far more readable, and makes console (incl. travis) reports actually usable. Turned off test logs as error reporting is enough to figure out what happens. The only downside is that we can't use junit's categories to group tests, like we did for docker related tests. We could use nested suites, but that seems to be overkill so I just removed the categories. Users will only have the possibility to either skip/run all tests. * update scala-maven-plugin to 3.4.2 NB: This requires maven 3.5.4, which means that we currently need to manually install maven on travis. Also updated Docker java version to 8u181 (8u171 for compiling). * Add instructions for Bitcoin Core 0.17.0 [ci skip] (#732) * Add instructions for Bitcoin Core 0.17.0 [ci skip] Bitcoin Core 0.17.0 deprecates the `signrawtransaction` RPC call, which will be removed in version 0.18.0, you need to enable this call if you want your eclair node to use a 0.1.70 node. * README: add an example of how to use the new bitcoin.conf sections [ci skip] * Only persist trimmed htlcs (#724) We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our counterparty, so only htlcs above remote's `dust_limit` matter. Removed the TODO because we need data to be indexed by commit number so it is ok to write the same htlc data for every commitment it is included in. * set version to 0.2-beta7 * set version to 0.2-SNAPSHOT * Add `htlcMaximumMsat` field to `ChannelUpdate` message (#738) * Add `htlcMaximumMsat` field to `ChannelUpdate` message * added compatibility test with c-lightning * Fix encoding of FinalIncorrectHtlcAmount error message (#740) * set version to 0.2-beta8 * set version to 0.2-SNAPSHOT * Always add 1 block to the `finalCltvExpiry` (#742) This fixes #651. * ignore IntegrationSpec (no server on android) * back to SNAPSHOT * use proper [gs]etNullableLong method for Sqlite
This commit is contained in:
parent
2379807f2e
commit
dae5cc718a
108 changed files with 4314 additions and 4203 deletions
|
@ -7,6 +7,11 @@ scala:
|
|||
- 2.11.11
|
||||
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:
|
||||
|
|
6
BUILD.md
6
BUILD.md
|
@ -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
|
||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
FROM openjdk:8u151-jdk-alpine as BUILD
|
||||
FROM openjdk:8u171-jdk-alpine as BUILD
|
||||
|
||||
# Setup maven, we don't use https://hub.docker.com/_/maven/ as it declare .m2 as volume, we loose all mvn cache
|
||||
# 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 .
|
||||
|
|
36
README.md
36
README.md
|
@ -30,7 +30,7 @@ Please see the latest [release note](https://github.com/ACINQ/eclair/releases) f
|
|||
|
||||
### Configuring Bitcoin Core
|
||||
|
||||
: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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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) } ++
|
||||
|
|
|
@ -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)]
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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)
|
||||
}*/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2018 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.blockchain.bitcoind
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.Status.Failure
|
||||
import akka.pattern.pipe
|
||||
import akka.testkit.{TestKit, TestProbe}
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import fr.acinq.bitcoin.Transaction
|
||||
import fr.acinq.eclair.blockchain.bitcoind.rpc.{BasicBitcoinJsonRPCClient, ExtendedBitcoinClient}
|
||||
import grizzled.slf4j.Logging
|
||||
import org.json4s.JsonAST._
|
||||
import org.json4s.{DefaultFormats, JString}
|
||||
import org.scalatest.{BeforeAndAfterAll, FunSuiteLike}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
|
||||
class ExtendedBitcoinClientSpec extends TestKit(ActorSystem("test")) with BitcoindService with FunSuiteLike with BeforeAndAfterAll with Logging {
|
||||
|
||||
val commonConfig = ConfigFactory.parseMap(Map("eclair.chain" -> "regtest", "eclair.spv" -> false, "eclair.server.public-ips.1" -> "localhost", "eclair.bitcoind.port" -> 28333, "eclair.bitcoind.rpcport" -> 28332, "eclair.bitcoind.zmq" -> "tcp://127.0.0.1:28334", "eclair.router-broadcast-interval" -> "2 second", "eclair.auto-reconnect" -> false))
|
||||
val config = ConfigFactory.load(commonConfig).getConfig("eclair")
|
||||
|
||||
implicit val formats = DefaultFormats
|
||||
|
||||
override def beforeAll(): Unit = {
|
||||
startBitcoind()
|
||||
}
|
||||
|
||||
override def afterAll(): Unit = {
|
||||
stopBitcoind()
|
||||
}
|
||||
|
||||
test("wait bitcoind ready") {
|
||||
waitForBitcoindReady()
|
||||
}
|
||||
|
||||
test("send transaction idempotent") {
|
||||
val bitcoinClient = new BasicBitcoinJsonRPCClient(
|
||||
user = config.getString("bitcoind.rpcuser"),
|
||||
password = config.getString("bitcoind.rpcpassword"),
|
||||
host = config.getString("bitcoind.host"),
|
||||
port = config.getInt("bitcoind.rpcport"))
|
||||
|
||||
val sender = TestProbe()
|
||||
bitcoinClient.invoke("getnewaddress").pipeTo(sender.ref)
|
||||
val JString(address) = sender.expectMsgType[JString]
|
||||
bitcoinClient.invoke("createrawtransaction", Array.empty, Map(address -> 6)).pipeTo(sender.ref)
|
||||
val JString(noinputTx) = sender.expectMsgType[JString]
|
||||
bitcoinClient.invoke("fundrawtransaction", noinputTx).pipeTo(sender.ref)
|
||||
val json = sender.expectMsgType[JValue]
|
||||
val JString(unsignedtx) = json \ "hex"
|
||||
val JInt(changePos) = json \ "changepos"
|
||||
bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref)
|
||||
val JString(signedTx) = sender.expectMsgType[JValue] \ "hex"
|
||||
val tx = Transaction.read(signedTx)
|
||||
val txid = tx.txid.toString()
|
||||
|
||||
// test starts here
|
||||
val client = new ExtendedBitcoinClient(bitcoinClient)
|
||||
// we publish it a first time
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
sender.expectMsg(txid)
|
||||
// we publish the tx a second time to test idempotence
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
sender.expectMsg(txid)
|
||||
// let's confirm the tx
|
||||
bitcoinClient.invoke("generate", 1).pipeTo(sender.ref)
|
||||
sender.expectMsgType[JValue]
|
||||
// and publish the tx a third time to test idempotence
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
sender.expectMsg(txid)
|
||||
|
||||
// now let's spent the output of the tx
|
||||
val spendingTx = {
|
||||
val pos = if (changePos == 0) 1 else 0
|
||||
bitcoinClient.invoke("createrawtransaction", Array(Map("txid" -> txid, "vout" -> pos)), Map(address -> 5.99999)).pipeTo(sender.ref)
|
||||
val JString(unsignedtx) = sender.expectMsgType[JValue]
|
||||
bitcoinClient.invoke("signrawtransaction", unsignedtx).pipeTo(sender.ref)
|
||||
val JString(signedTx) = sender.expectMsgType[JValue] \ "hex"
|
||||
signedTx
|
||||
}
|
||||
bitcoinClient.invoke("sendrawtransaction", spendingTx).pipeTo(sender.ref)
|
||||
val JString(spendingTxid) = sender.expectMsgType[JValue]
|
||||
|
||||
// and publish the tx a fourth time to test idempotence
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
sender.expectMsg(txid)
|
||||
// let's confirm the tx
|
||||
bitcoinClient.invoke("generate", 1).pipeTo(sender.ref)
|
||||
sender.expectMsgType[JValue]
|
||||
// and publish the tx a fifth time to test idempotence
|
||||
client.publishTransaction(tx).pipeTo(sender.ref)
|
||||
sender.expectMsg(txid)
|
||||
|
||||
// this one should be rejected
|
||||
client.publishTransaction(Transaction.read("02000000000101b9e2a3f518fd74e696d258fed3c78c43f84504e76c99212e01cf225083619acf00000000000d0199800136b34b00000000001600145464ce1e5967773922506e285780339d72423244040047304402206795df1fd93c285d9028c384aacf28b43679f1c3f40215fd7bd1abbfb816ee5a022047a25b8c128e692d4717b6dd7b805aa24ecbbd20cfd664ab37a5096577d4a15d014730440220770f44121ed0e71ec4b482dded976f2febd7500dfd084108e07f3ce1e85ec7f5022025b32dc0d551c47136ce41bfb80f5a10de95c0babb22a3ae2d38e6688b32fcb20147522102c2662ab3e4fa18a141d3be3317c6ee134aff10e6cd0a91282a25bf75c0481ebc2102e952dd98d79aa796289fa438e4fdeb06ed8589ff2a0f032b0cfcb4d7b564bc3252aea58d1120")).pipeTo(sender.ref)
|
||||
sender.expectMsgType[Failure]
|
||||
}
|
||||
}
|
|
@ -21,14 +21,12 @@ import akka.testkit.{TestKit, TestProbe}
|
|||
import fr.acinq.bitcoin.{BinaryData, Crypto, Transaction}
|
||||
import fr.acinq.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()
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,36 +23,37 @@ import fr.acinq.eclair.blockchain.{PublishAsap, WatchEventSpent}
|
|||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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))))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:")
|
||||
|
|
|
@ -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:")
|
||||
|
|
|
@ -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:")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)}*/
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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._
|
||||
|
||||
|
|
|
@ -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._
|
||||
|
||||
|
|
|
@ -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]) = {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue