diff --git a/.travis.yml b/.travis.yml index 5214a79ec..50b8f56a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/BUILD.md b/BUILD.md index fb1bbfb0b..699b57e69 100644 --- a/BUILD.md +++ b/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 diff --git a/Dockerfile b/Dockerfile index ed09582d8..00ca879c6 100644 --- a/Dockerfile +++ b/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 . diff --git a/README.md b/README.md index 0c8dc64ba..17d28b3e2 100644 --- a/README.md +++ b/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= +rpcpassword= +zmqpubrawblock=tcp://127.0.0.1:29000 +zmqpubrawtx=tcp://127.0.0.1:29000 +[test] +rpcuser= +rpcpassword= +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= -eclair.bitcoind.rpcpassword= +eclair.bitcoind.rpcuser= +eclair.bitcoind.rpcpassword= ``` - ## 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 diff --git a/eclair-core/eclair-cli b/eclair-core/eclair-cli index 3d28660e0..013346957 100755 --- a/eclair-core/eclair-cli +++ b/eclair-core/eclair-cli @@ -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. diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml index e3648e5f6..c294e7e73 100644 --- a/eclair-core/pom.xml +++ b/eclair-core/pom.xml @@ -21,7 +21,7 @@ fr.acinq.eclair eclair_2.11 - 0.2-android-beta12 + 0.2-android-SNAPSHOT eclair-core_2.11 @@ -79,10 +79,10 @@ true - https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-x86_64-linux-gnu.tar.gz + https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz - 1375c9f908b0327d9772d4bff0d9f03f - d0b05b51e1d572c44ef5b2cabbfcb662679cf7cb + c371e383f024c6c45fb255d528a6beec + e6d8ab1f7661a6654fd81e236b9b5fd35a3d4dcb @@ -93,10 +93,10 @@ - https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-osx64.tar.gz + https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-osx64.tar.gz - fc10d5cb12a4c3905d97df33f249eb1c - 3b1ab75439ca7a9b547827ec3e59c7e61c1f6fcd + bacd87d0c3f65a5acd666e33d094a59e + 62cc5bd9ced610bb9e8d4a854396bfe2139e3d0f @@ -107,9 +107,9 @@ - https://bitcoin.org/bin/bitcoin-core-0.16.0/bitcoin-0.16.0-win64.zip - 5b9034754752b7e1b3117eaa5434792e - 90d72e25782a4b454f5f507a26a3cf0f53baecef + https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-win64.zip + bbde9b1206956d19298034319e9f405e + 85e3dc8a9c6f93b1b20cb79fa5850b5ce81da221 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala index aa08262f2..71cb6c1d8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/ZmqWatcher.scala @@ -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") } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/ExtendedBitcoinClient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/ExtendedBitcoinClient.scala index 2b023e718..b2dd293d4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/ExtendedBitcoinClient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/ExtendedBitcoinClient.scala @@ -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 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala index 83a4f6c8a..bce5327f6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -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 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala index 809a5279d..e8b09888b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala @@ -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 \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index fcba08952..b8704aee4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -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 diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala index 4e7c82aec..e369f2ef7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala @@ -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) } ++ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala index 63ea93bc5..07d4f87d0 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/ChannelsDb.scala @@ -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)] } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala index 1a8f4a0b5..e81cea31d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteChannelsDb.scala @@ -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) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala index a3df654a3..c7ae493e8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala @@ -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 } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala index 366c571cf..cee818d1d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteUtils.scala @@ -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) + } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 31b102455..3829f3aa1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -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) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala index 832d90e9c..1e7d688bb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentLifecycle.scala @@ -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) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala index 2bf61a4e1..afee2cdfa 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Relayer.scala @@ -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 => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala index 8d1d91ea4..4230f4b87 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Announcements.scala @@ -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) }*/ diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala index 95eeaabfe..c621e4d20 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala @@ -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) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala index bdaf362ed..0fff5385b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/FailureMessage.scala @@ -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)) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala index 80d2bb4ff..da41eed0f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala @@ -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] -} +} \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index 3906c4aed..469460eb7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -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 diff --git a/eclair-core/src/test/resources/logback-test.xml b/eclair-core/src/test/resources/logback-test.xml index cb52679f1..44274d468 100644 --- a/eclair-core/src/test/resources/logback-test.xml +++ b/eclair-core/src/test/resources/logback-test.xml @@ -48,8 +48,8 @@ - + + diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala index f0f83e42b..68ce42634 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/CoinUtilsSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala index 7a874d9fb..c629470ff 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/FeaturesSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/JsonSerializersSpec.scala index e704de873..524c278a4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/JsonSerializersSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala index dd9f8b6b5..12eafa3ab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/PackageSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala index ce4a45a71..d6eb0f22c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 19188c7e8..117ea4a7d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala index 596858b49..94cebea18 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/UInt64Spec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/WatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/WatcherSpec.scala index 8551ed172..3b6110c89 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/WatcherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/WatcherSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala index eb70afe3b..575e9e512 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreWalletSpec.scala @@ -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)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala index c1222f83e..67a171abb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoindService.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala new file mode 100644 index 000000000..d730b2bf1 --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/ExtendedBitcoinClientSpec.scala @@ -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] + } +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientPoolSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientPoolSpec.scala index 7e75e0780..7b3ba4169 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientPoolSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientPoolSpec.scala @@ -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() diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala index 006b13d02..6763d77b6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumClientSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletBasicSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletBasicSpec.scala index 28d6920e0..074117a10 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletBasicSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletBasicSpec.scala @@ -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") } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala index 193fa863b..cb919acf7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSimulatedClientSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala index d123ff772..02b286256 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWalletSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWatcherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWatcherSpec.scala index 24c725973..e9dc3a340 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWatcherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumWatcherSpec.scala @@ -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] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumxService.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumxService.scala index 6a3339222..3df6908f6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumxService.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/electrum/ElectrumxService.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala index 3d4681dcf..493a835e7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitcoinCoreFeeProviderSpec.scala @@ -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)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala index 1bf7bb05d..f4368275e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/BitgoFeeProviderSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala index 3afb0fcd7..a0c79750e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/EarnDotComFeeProviderSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala index a527307b4..f61c16857 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/FallbackFeeProviderSpec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala index 1435011b4..882bd3da8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/fee/SmoothFeeProviderSpec.scala @@ -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( diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala index 09451818f..a3619e086 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/FuzzySpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 51cd6bc85..bf8c011f7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala index 22cce2950..52f963b98 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala index 5a89800a2..36b4d1a42 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedInternalStateSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 684be75b8..8d6832ce3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index f772deba9..aa9f46a7b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala index 48c31cfb0..eeffb8942 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingConfirmedStateSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala index 605e0da34..f6d9c853f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/c/WaitForFundingLockedStateSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 7b0598f68..626e8b9e7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -32,1986 +32,1975 @@ import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.transactions.{IN, OUT} import fr.acinq.eclair.wire.{AnnouncementSignatures, ChannelUpdate, ClosingSigned, CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} import fr.acinq.eclair.{Globals, TestConstants, TestkitBaseClass} -import org.junit.runner.RunWith -import org.scalatest.Tag -import org.scalatest.junit.JUnitRunner +import fr.acinq.eclair.transactions.Transactions.{htlcSuccessWeight, htlcTimeoutWeight, weight2fee} +import org.scalatest.{Outcome, Tag} import scala.concurrent.duration._ /** * Created by PM on 05/07/2016. */ -@RunWith(classOf[JUnitRunner]) + class NormalStateSpec 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, test.tags) awaitCond(alice.stateName == NORMAL) awaitCond(bob.stateName == NORMAL) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer))) } - test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)) } - test("recv CMD_ADD_HTLC (empty origin)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val sender = TestProbe() - val h = BinaryData("42" * 32) + test("recv CMD_ADD_HTLC (empty origin)") { f => + import f._ + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val sender = TestProbe() + val h = BinaryData("42" * 32) + sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[UpdateAddHtlc] + assert(htlc.id == 0 && htlc.paymentHash == h) + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Local(Some(sender.ref))) + ))) + } + + test("recv CMD_ADD_HTLC (incrementing ids)") { f => + import f._ + val sender = TestProbe() + val h = BinaryData("42" * 32) + for (i <- 0 until 10) { sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) sender.expectMsg("ok") val htlc = alice2bob.expectMsgType[UpdateAddHtlc] - assert(htlc.id == 0 && htlc.paymentHash == h) - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Local(Some(sender.ref))) - ))) + assert(htlc.id == i && htlc.paymentHash == h) } } - test("recv CMD_ADD_HTLC (incrementing ids)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val h = BinaryData("42" * 32) - for (i <- 0 until 10) { - sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) - sender.expectMsg("ok") - val htlc = alice2bob.expectMsgType[UpdateAddHtlc] - assert(htlc.id == i && htlc.paymentHash == h) - } - } + test("recv CMD_ADD_HTLC (relayed htlc)") { f => + import f._ + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val sender = TestProbe() + val h = BinaryData("42" * 32) + val originHtlc = UpdateAddHtlc(channelId = "42" * 32, id = 5656, amountMsat = 50000000, expiry = 400144, paymentHash = h, onionRoutingPacket = "00" * 1254) + val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000, h, originHtlc.expiry - 7, upstream_opt = Some(originHtlc)) + sender.send(alice, cmd) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[UpdateAddHtlc] + assert(htlc.id == 0 && htlc.paymentHash == h) + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localNextHtlcId = 1, + localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), + originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) + ))) } - test("recv CMD_ADD_HTLC (relayed htlc)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val sender = TestProbe() - val h = BinaryData("42" * 32) - val originHtlc = UpdateAddHtlc(channelId = "42" * 32, id = 5656, amountMsat = 50000000, expiry = 400144, paymentHash = h, onionRoutingPacket = "00" * 1254) - val cmd = CMD_ADD_HTLC(originHtlc.amountMsat - 10000, h, originHtlc.expiry - 7, upstream_opt = Some(originHtlc)) - sender.send(alice, cmd) - sender.expectMsg("ok") - val htlc = alice2bob.expectMsgType[UpdateAddHtlc] - assert(htlc.id == 0 && htlc.paymentHash == h) - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localNextHtlcId = 1, - localChanges = initialState.commitments.localChanges.copy(proposed = htlc :: Nil), - originChannels = Map(0L -> Relayed(originHtlc.channelId, originHtlc.id, originHtlc.amountMsat, htlc.amountMsat)) - ))) - } + test("recv CMD_ADD_HTLC (invalid payment hash)") { f => + import f._ + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val sender = TestProbe() + val add = CMD_ADD_HTLC(500000000, "11" * 42, expiry = 400144) + sender.send(alice, add) + val error = InvalidPaymentHash(channelId(alice)) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (invalid payment hash)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val sender = TestProbe() - val add = CMD_ADD_HTLC(500000000, "11" * 42, expiry = 400144) - sender.send(alice, add) - val error = InvalidPaymentHash(channelId(alice)) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (expiry too small)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val currentBlockCount = Globals.blockCount.get + val expiryTooSmall = currentBlockCount + 3 + val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = expiryTooSmall) + sender.send(alice, add) + val error = ExpiryTooSmall(channelId(alice), currentBlockCount + Channel.MIN_CLTV_EXPIRY, expiryTooSmall, currentBlockCount) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (expiry too small)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val currentBlockCount = Globals.blockCount.get - val expiryTooSmall = currentBlockCount + 3 - val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = expiryTooSmall) - sender.send(alice, add) - val error = ExpiryTooSmall(channelId(alice), currentBlockCount + Channel.MIN_CLTV_EXPIRY, expiryTooSmall, currentBlockCount) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (expiry too big)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val currentBlockCount = Globals.blockCount.get + val expiryTooBig = currentBlockCount + Channel.MAX_CLTV_EXPIRY + 1 + val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = expiryTooBig) + sender.send(alice, add) + val error = ExpiryTooBig(channelId(alice), maximum = currentBlockCount + Channel.MAX_CLTV_EXPIRY, actual = expiryTooBig, blockCount = currentBlockCount) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (expiry too big)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val currentBlockCount = Globals.blockCount.get - val expiryTooBig = currentBlockCount + Channel.MAX_CLTV_EXPIRY + 1 - val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = expiryTooBig) - sender.send(alice, add) - val error = ExpiryTooBig(channelId(alice), maximum = currentBlockCount + Channel.MAX_CLTV_EXPIRY, actual = expiryTooBig, blockCount = currentBlockCount) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (value too small)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val add = CMD_ADD_HTLC(50, "11" * 32, 400144) + sender.send(alice, add) + val error = HtlcValueTooSmall(channelId(alice), 1000, 50) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (value too small)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(50, "11" * 32, 400144) - sender.send(alice, add) - val error = HtlcValueTooSmall(channelId(alice), 1000, 50) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (insufficient funds)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val add = CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 400144) + sender.send(alice, add) + val error = InsufficientFunds(channelId(alice), amountMsat = Int.MaxValue, missingSatoshis = 1376443, reserveSatoshis = 20000, feesSatoshis = 8960) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(Int.MaxValue, "11" * 32, 400144) - sender.send(alice, add) - val error = InsufficientFunds(channelId(alice), amountMsat = Int.MaxValue, missingSatoshis = 1376443, reserveSatoshis = 20000, feesSatoshis = 8960) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_ADD_HTLC(500000000, "11" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + sender.send(alice, CMD_ADD_HTLC(200000000, "22" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + sender.send(alice, CMD_ADD_HTLC(67600000, "33" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + val add = CMD_ADD_HTLC(1000000, "44" * 32, 400144) + sender.send(alice, add) + val error = InsufficientFunds(channelId(alice), amountMsat = 1000000, missingSatoshis = 1000, reserveSatoshis = 20000, feesSatoshis = 12400) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_ADD_HTLC(500000000, "11" * 32, 400144)) + test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + sender.send(alice, CMD_ADD_HTLC(300000000, "22" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + val add = CMD_ADD_HTLC(500000000, "33" * 32, 400144) + sender.send(alice, add) + val error = InsufficientFunds(channelId(alice), amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) + } + + test("recv CMD_ADD_HTLC (over max inflight htlc value)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + val add = CMD_ADD_HTLC(151000000, "11" * 32, 400144) + sender.send(bob, add) + val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000) + sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + bob2alice.expectNoMsg(200 millis) + } + + test("recv CMD_ADD_HTLC (over max accepted htlcs)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + // Bob accepts a maximum of 30 htlcs + for (i <- 0 until 30) { + sender.send(alice, CMD_ADD_HTLC(10000000, "11" * 32, 400144)) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(200000000, "22" * 32, 400144)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(67600000, "33" * 32, 400144)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(1000000, "44" * 32, 400144) - sender.send(alice, add) - val error = InsufficientFunds(channelId(alice), amountMsat = 1000000, missingSatoshis = 1000, reserveSatoshis = 20000, feesSatoshis = 12400) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) } + val add = CMD_ADD_HTLC(10000000, "33" * 32, 400144) + sender.send(alice, add) + val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_ADD_HTLC(300000000, "11" * 32, 400144)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_ADD_HTLC(300000000, "22" * 32, 400144)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - val add = CMD_ADD_HTLC(500000000, "33" * 32, 400144) - sender.send(alice, add) - val error = InsufficientFunds(channelId(alice), amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (over capacity)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "11" * 32, 400144) + sender.send(alice, add1) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + // this is over channel-capacity + val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "22" * 32, 400144) + sender.send(alice, add2) + val error = InsufficientFunds(channelId(alice), add2.amountMsat, 564012, 20000, 10680) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (over max inflight htlc value)") { case (_, bob, _, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - val add = CMD_ADD_HTLC(151000000, "11" * 32, 400144) - sender.send(bob, add) - val error = HtlcValueTooHighInFlight(channelId(bob), maximum = 150000000, actual = 151000000) - sender.expectMsg(Failure(AddHtlcFailed(channelId(bob), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - bob2alice.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (after having sent Shutdown)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg("ok") + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && !alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isDefined) + + // actual test starts here + val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144) + sender.send(alice, add) + val error = NoMoreHtlcsClosingInProgress(channelId(alice)) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) + alice2bob.expectNoMsg(200 millis) } - test("recv CMD_ADD_HTLC (over max accepted htlcs)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - // Bob accepts a maximum of 30 htlcs - for (i <- 0 until 30) { - sender.send(alice, CMD_ADD_HTLC(10000000, "11" * 32, 400144)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - } - val add = CMD_ADD_HTLC(10000000, "33" * 32, 400144) - sender.send(alice, add) - val error = TooManyAcceptedHtlcs(channelId(alice), maximum = 30) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv CMD_ADD_HTLC (after having received Shutdown)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + // let's make alice send an htlc + val add1 = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144) + sender.send(alice, add1) + sender.expectMsg("ok") + // at the same time bob initiates a closing + sender.send(bob, CMD_CLOSE(None)) + sender.expectMsg("ok") + // this command will be received by alice right after having received the shutdown + val add2 = CMD_ADD_HTLC(100000000, "22" * 32, expiry = 300000) + // messages cross + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + bob2alice.expectMsgType[Shutdown] + bob2alice.forward(alice) + sender.send(alice, add2) + val error = NoMoreHtlcsClosingInProgress(channelId(alice)) + sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) } - test("recv CMD_ADD_HTLC (over capacity)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val add1 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "11" * 32, 400144) - sender.send(alice, add1) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - // this is over channel-capacity - val add2 = CMD_ADD_HTLC(TestConstants.fundingSatoshis * 2 / 3 * 1000, "22" * 32, 400144) - sender.send(alice, add2) - val error = InsufficientFunds(channelId(alice), add2.amountMsat, 564012, 20000, 10680) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv UpdateAddHtlc") { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) + bob ! htlc + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) } - test("recv CMD_ADD_HTLC (after having sent Shutdown)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg("ok") - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined && !alice.stateData.asInstanceOf[DATA_NORMAL].remoteShutdown.isDefined) - - // actual test starts here - val add = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144) - sender.send(alice, add) - val error = NoMoreHtlcsClosingInProgress(channelId(alice)) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add)))) - alice2bob.expectNoMsg(200 millis) - } + test("recv UpdateAddHtlc (unexpected id)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val htlc = UpdateAddHtlc("00" * 32, 42, 150000, BinaryData("42" * 32), 400144, defaultOnion) + bob ! htlc.copy(id = 0) + bob ! htlc.copy(id = 1) + bob ! htlc.copy(id = 2) + bob ! htlc.copy(id = 3) + bob ! htlc.copy(id = 42) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === UnexpectedHtlcId(channelId(bob), expected = 4, actual = 42).getMessage) + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv CMD_ADD_HTLC (after having received Shutdown)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - // let's make alice send an htlc - val add1 = CMD_ADD_HTLC(500000000, "11" * 32, expiry = 400144) - sender.send(alice, add1) - sender.expectMsg("ok") - // at the same time bob initiates a closing - sender.send(bob, CMD_CLOSE(None)) - sender.expectMsg("ok") - // this command will be received by alice right after having received the shutdown - val add2 = CMD_ADD_HTLC(100000000, "22" * 32, expiry = 300000) - // messages cross - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - bob2alice.expectMsgType[Shutdown] - bob2alice.forward(alice) - sender.send(alice, add2) - val error = NoMoreHtlcsClosingInProgress(channelId(alice)) - sender.expectMsg(Failure(AddHtlcFailed(channelId(alice), add2.paymentHash, error, Local(Some(sender.ref)), Some(initialState.channelUpdate), Some(add2)))) - } + test("recv UpdateAddHtlc (invalid payment hash)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val htlc = UpdateAddHtlc("00" * 32, 0, 150000, "11" * 42, 400144, defaultOnion) + alice2bob.forward(bob, htlc) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === InvalidPaymentHash(channelId(bob)).getMessage) + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - val htlc = UpdateAddHtlc("00" * 32, 0, 150000, BinaryData("42" * 32), 400144, defaultOnion) - bob ! htlc - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ htlc), remoteNextHtlcId = 1))) - } + test("recv UpdateAddHtlc (value too small)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val htlc = UpdateAddHtlc("00" * 32, 0, 150, BinaryData("42" * 32), expiry = 400144, defaultOnion) + alice2bob.forward(bob, htlc) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === HtlcValueTooSmall(channelId(bob), minimum = 1000, actual = 150).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (unexpected id)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc("00" * 32, 42, 150000, BinaryData("42" * 32), 400144, defaultOnion) - bob ! htlc.copy(id = 0) - bob ! htlc.copy(id = 1) - bob ! htlc.copy(id = 2) - bob ! htlc.copy(id = 3) - bob ! htlc.copy(id = 42) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === UnexpectedHtlcId(channelId(bob), expected = 4, actual = 42).getMessage) - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateAddHtlc (insufficient funds)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val htlc = UpdateAddHtlc("00" * 32, 0, Long.MaxValue, BinaryData("42" * 32), 400144, defaultOnion) + alice2bob.forward(bob, htlc) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = Long.MaxValue, missingSatoshis = 9223372036083735L, reserveSatoshis = 20000, feesSatoshis = 8960).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (invalid payment hash)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc("00" * 32, 0, 150000, "11" * 42, 400144, defaultOnion) - alice2bob.forward(bob, htlc) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === InvalidPaymentHash(channelId(bob)).getMessage) - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 0, 400000000, "11" * 32, 400144, defaultOnion)) + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 1, 200000000, "22" * 32, 400144, defaultOnion)) + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 167600000, "33" * 32, 400144, defaultOnion)) + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 3, 10000000, "44" * 32, 400144, defaultOnion)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = 10000000, missingSatoshis = 11720, reserveSatoshis = 20000, feesSatoshis = 14120).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (value too small)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc("00" * 32, 0, 150, BinaryData("42" * 32), expiry = 400144, defaultOnion) - alice2bob.forward(bob, htlc) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === HtlcValueTooSmall(channelId(bob), minimum = 1000, actual = 150).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 0, 300000000, "11" * 32, 400144, defaultOnion)) + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 1, 300000000, "22" * 32, 400144, defaultOnion)) + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 500000000, "33" * 32, 400144, defaultOnion)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (insufficient funds)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val htlc = UpdateAddHtlc("00" * 32, 0, Long.MaxValue, BinaryData("42" * 32), 400144, defaultOnion) - alice2bob.forward(bob, htlc) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = Long.MaxValue, missingSatoshis = 9223372036083735L, reserveSatoshis = 20000, feesSatoshis = 8960).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateAddHtlc (over max inflight htlc value)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + alice2bob.forward(alice, UpdateAddHtlc("00" * 32, 0, 151000000, "11" * 32, 400144, defaultOnion)) + val error = alice2bob.expectMsgType[Error] + assert(new String(error.data) === HtlcValueTooHighInFlight(channelId(alice), maximum = 150000000, actual = 151000000).getMessage) + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 1/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 0, 400000000, "11" * 32, 400144, defaultOnion)) - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 1, 200000000, "22" * 32, 400144, defaultOnion)) - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 167600000, "33" * 32, 400144, defaultOnion)) - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 3, 10000000, "44" * 32, 400144, defaultOnion)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = 10000000, missingSatoshis = 11720, reserveSatoshis = 20000, feesSatoshis = 14120).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] + test("recv UpdateAddHtlc (over max accepted htlcs)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + // Bob accepts a maximum of 30 htlcs + for (i <- 0 until 30) { + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, i, 1000000, "11" * 32, 400144, defaultOnion)) } + alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 30, 1000000, "11" * 32, 400144, defaultOnion)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === TooManyAcceptedHtlcs(channelId(bob), maximum = 30).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateAddHtlc (insufficient funds w/ pending htlcs 2/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 0, 300000000, "11" * 32, 400144, defaultOnion)) - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 1, 300000000, "22" * 32, 400144, defaultOnion)) - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 2, 500000000, "33" * 32, 400144, defaultOnion)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === InsufficientFunds(channelId(bob), amountMsat = 500000000, missingSatoshis = 332400, reserveSatoshis = 20000, feesSatoshis = 12400).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv CMD_SIGN") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + val commitSig = alice2bob.expectMsgType[CommitSig] + assert(commitSig.htlcSignatures.size == 1) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) } - test("recv UpdateAddHtlc (over max inflight htlc value)") { case (alice, _, alice2bob, _, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice2bob.forward(alice, UpdateAddHtlc("00" * 32, 0, 151000000, "11" * 32, 400144, defaultOnion)) - val error = alice2bob.expectMsgType[Error] - assert(new String(error.data) === HtlcValueTooHighInFlight(channelId(alice), maximum = 150000000, actual = 151000000).getMessage) - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv CMD_SIGN (two identical htlcs in each direction)") { f => + import f._ + val sender = TestProbe() + val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) + sender.send(alice, add) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + sender.send(alice, add) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + + crossSign(alice, bob, alice2bob, bob2alice) + + sender.send(bob, add) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateAddHtlc] + bob2alice.forward(alice) + sender.send(bob, add) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateAddHtlc] + bob2alice.forward(alice) + + // actual test starts here + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + val commitSig = bob2alice.expectMsgType[CommitSig] + assert(commitSig.htlcSignatures.toSet.size == 4) } - test("recv UpdateAddHtlc (over max accepted htlcs)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - // Bob accepts a maximum of 30 htlcs - for (i <- 0 until 30) { - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, i, 1000000, "11" * 32, 400144, defaultOnion)) - } - alice2bob.forward(bob, UpdateAddHtlc("00" * 32, 30, 1000000, "11" * 32, 400144, defaultOnion)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === TooManyAcceptedHtlcs(channelId(bob), maximum = 30).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } + ignore("recv CMD_SIGN (check htlc info are persisted)") { f => + import f._ + val sender = TestProbe() + // for the test to be really useful we have constraint on parameters + assert(Alice.nodeParams.dustLimitSatoshis > Bob.nodeParams.dustLimitSatoshis) + // we're gonna exchange two htlcs in each direction, the goal is to have bob's commitment have 4 htlcs, and alice's + // commitment only have 3. We will then check that alice indeed persisted 4 htlcs, and bob only 3. + val aliceMinReceive = Alice.nodeParams.dustLimitSatoshis + weight2fee(TestConstants.feeratePerKw, htlcSuccessWeight).toLong + val aliceMinOffer = Alice.nodeParams.dustLimitSatoshis + weight2fee(TestConstants.feeratePerKw, htlcTimeoutWeight).toLong + val bobMinReceive = Bob.nodeParams.dustLimitSatoshis + weight2fee(TestConstants.feeratePerKw, htlcSuccessWeight).toLong + val bobMinOffer = Bob.nodeParams.dustLimitSatoshis + weight2fee(TestConstants.feeratePerKw, htlcTimeoutWeight).toLong + val a2b_1 = bobMinReceive + 10 // will be in alice and bob tx + val a2b_2 = bobMinReceive + 20 // will be in alice and bob tx + val b2a_1 = aliceMinReceive + 10 // will be in alice and bob tx + val b2a_2 = bobMinOffer + 10 // will be only be in bob tx + assert(a2b_1 > aliceMinOffer && a2b_1 > bobMinReceive) + assert(a2b_2 > aliceMinOffer && a2b_2 > bobMinReceive) + assert(b2a_1 > aliceMinReceive && b2a_1 > bobMinOffer) + assert(b2a_2 < aliceMinReceive && b2a_2 > bobMinOffer) + sender.send(alice, CMD_ADD_HTLC(a2b_1 * 1000, "11" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + sender.send(alice, CMD_ADD_HTLC(a2b_2 * 1000, "22" * 32, 400144)) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + sender.send(bob, CMD_ADD_HTLC(b2a_1 * 1000, "33" * 32, 400144)) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateAddHtlc] + bob2alice.forward(alice) + sender.send(bob, CMD_ADD_HTLC(b2a_2 * 1000, "44" * 32, 400144)) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateAddHtlc] + bob2alice.forward(alice) + + // actual test starts here + crossSign(alice, bob, alice2bob, bob2alice) + // depending on who starts signing first, there will be one or two commitments because both sides have changes + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.index === 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.index === 2) + assert(alice.underlyingActor.nodeParams.channelsDb.listHtlcInfos(alice.stateData.asInstanceOf[DATA_NORMAL].channelId, 0).size == 0) + assert(alice.underlyingActor.nodeParams.channelsDb.listHtlcInfos(alice.stateData.asInstanceOf[DATA_NORMAL].channelId, 1).size == 2) + assert(alice.underlyingActor.nodeParams.channelsDb.listHtlcInfos(alice.stateData.asInstanceOf[DATA_NORMAL].channelId, 2).size == 4) + assert(bob.underlyingActor.nodeParams.channelsDb.listHtlcInfos(bob.stateData.asInstanceOf[DATA_NORMAL].channelId, 0).size == 0) + assert(bob.underlyingActor.nodeParams.channelsDb.listHtlcInfos(bob.stateData.asInstanceOf[DATA_NORMAL].channelId, 1).size == 3) } - test("recv CMD_SIGN") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - val commitSig = alice2bob.expectMsgType[CommitSig] - assert(commitSig.htlcSignatures.size == 1) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) - } - } - - test("recv CMD_SIGN (two identical htlcs in each direction)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) - sender.send(alice, add) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - sender.send(alice, add) + test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { f => + import f._ + val sender = TestProbe() + val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) + val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose + val htlcCount = epsilons.size + for (i <- epsilons) { + sender.send(alice, add.copy(amountMsat = add.amountMsat + i * 1000)) sender.expectMsg("ok") alice2bob.expectMsgType[UpdateAddHtlc] alice2bob.forward(bob) + } + // actual test starts here + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + val commitSig = alice2bob.expectMsgType[CommitSig] + assert(commitSig.htlcSignatures.toSet.size == htlcCount) + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == htlcCount) + val htlcTxs = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs + val amounts = htlcTxs.map(_.txinfo.tx.txOut.head.amount.toLong) + assert(amounts === amounts.sorted) + } + test("recv CMD_SIGN (no changes)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, CMD_SIGN) + sender.expectNoMsg(1 second) // just ignored + //sender.expectMsg("cannot sign when there are no changes") + } + + test("recv CMD_SIGN (while waiting for RevokeAndAck (no pending changes)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) + val waitForRevocation = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get + assert(waitForRevocation.reSignAsap === false) + + // actual test starts here + sender.send(alice, CMD_SIGN) + sender.expectNoMsg(300 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo === Left(waitForRevocation)) + } + + test("recv CMD_SIGN (while waiting for RevokeAndAck (with pending changes)") { f => + import f._ + val sender = TestProbe() + val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) + val waitForRevocation = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get + assert(waitForRevocation.reSignAsap === false) + + // actual test starts here + val (r2, htlc2) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectNoMsg(300 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo === Left(waitForRevocation.copy(reSignAsap = true))) + } + + test("recv CommitSig (one htlc received)") { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + + // actual test begins + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + + bob2alice.expectMsgType[RevokeAndAck] + // bob replies immediately with a signature + bob2alice.expectMsgType[CommitSig] + + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN)) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.signed.size == 1) + } + + test("recv CommitSig (one htlc sent)") { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + + // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT)) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) + } + + test("recv CommitSig (multiple htlcs in both directions)") { f => + import f._ + val sender = TestProbe() + + val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r2, htlc2) = addHtlc(8000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r3, htlc3) = addHtlc(300000, bob, alice, bob2alice, alice2bob) // b->a (dust) + + val (r4, htlc4) = addHtlc(1000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r5, htlc5) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) // b->a (regular) + + val (r6, htlc6) = addHtlc(500000, alice, bob, alice2bob, bob2alice) // a->b (dust) + + val (r7, htlc7) = addHtlc(4000000, bob, alice, bob2alice, alice2bob) // b->a (regular) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + + // actual test begins + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.index == 1) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 3) + } + + test("recv CommitSig (only fee update)") { f => + import f._ + val sender = TestProbe() + + sender.send(alice, CMD_UPDATE_FEE(TestConstants.feeratePerKw + 1000, commit = false)) + sender.expectMsg("ok") + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + + // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) + alice2bob.expectMsgType[UpdateFee] + alice2bob.forward(bob) + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + } + + test("recv CommitSig (two htlcs received with same r)") { f => + import f._ + val sender = TestProbe() + val r = BinaryData("42" * 32) + val h: BinaryData = Crypto.sha256(r) + + sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) + sender.expectMsg("ok") + val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + + sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) + sender.expectMsg("ok") + val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc1 :: htlc2 :: Nil) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + crossSign(alice, bob, alice2bob, bob2alice) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc1.id && h.direction == IN)) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 2) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx.txOut.count(_.amount == Satoshi(50000)) == 2) + } + + test("recv CommitSig (no changes)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + // signature is invalid but it doesn't matter + sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) + bob2alice.expectMsgType[Error] + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CommitSig (invalid signature)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + + // actual test begins + sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data).startsWith("invalid commitment signature")) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CommitSig (bad htlc sig count)") { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + val commitSig = alice2bob.expectMsgType[CommitSig] + + // actual test begins + val badCommitSig = commitSig.copy(htlcSignatures = commitSig.htlcSignatures ::: commitSig.htlcSignatures) + sender.send(bob, badCommitSig) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === HtlcSigCountMismatch(channelId(bob), expected = 1, actual = 2).getMessage) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CommitSig (invalid htlc sig)") { f => + import f._ + val sender = TestProbe() + + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + val commitSig = alice2bob.expectMsgType[CommitSig] + + // actual test begins + val badCommitSig = commitSig.copy(htlcSignatures = commitSig.signature :: Nil) + sender.send(bob, badCommitSig) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data).startsWith("invalid htlc signature")) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + + test("recv RevokeAndAck (one htlc sent)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + + // actual test begins + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localChanges.acked.size == 1) + } + + test("recv RevokeAndAck (one htlc received)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + + // actual test begins + alice2bob.expectMsgType[RevokeAndAck] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + } + + test("recv RevokeAndAck (multiple htlcs in both directions)") { f => + import f._ + val sender = TestProbe() + val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r2, htlc2) = addHtlc(8000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r3, htlc3) = addHtlc(300000, bob, alice, bob2alice, alice2bob) // b->a (dust) + + val (r4, htlc4) = addHtlc(1000000, alice, bob, alice2bob, bob2alice) // a->b (regular) + + val (r5, htlc5) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) // b->a (regular) + + val (r6, htlc6) = addHtlc(500000, alice, bob, alice2bob, bob2alice) // a->b (dust) + + val (r7, htlc7) = addHtlc(4000000, bob, alice, bob2alice, alice2bob) // b->a (regular) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + + // actual test begins + alice2bob.expectMsgType[RevokeAndAck] + alice2bob.forward(bob) + + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.index == 1) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.spec.htlcs.size == 7) + } + + test("recv RevokeAndAck (with reSignAsap=true)") { f => + import f._ + val sender = TestProbe() + val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + val (r2, htlc2) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectNoMsg(300 millis) + assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get.reSignAsap === true) + + // actual test starts here + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + alice2bob.expectMsgType[CommitSig] + } + + test("recv RevokeAndAck (invalid preimage)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + + // actual test begins + bob2alice.expectMsgType[RevokeAndAck] + sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv RevokeAndAck (unexpectedly)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) + sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CMD_FULFILL_HTLC") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r)) + sender.expectMsg("ok") + val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) + } + + test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val r: BinaryData = "11" * 32 + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(bob, CMD_FULFILL_HTLC(42, r)) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) + } + + test("recv CMD_FULFILL_HTLC (invalid preimage)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FULFILL_HTLC(htlc.id, "00" * 32)) + sender.expectMsg(Failure(InvalidHtlcPreimage(channelId(bob), 0))) + assert(initialState == bob.stateData) + } + + test("recv UpdateFulfillHtlc") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r)) + sender.expectMsg("ok") + val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] + + // actual test begins + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + bob2alice.forward(alice) + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)))) + } + + test("recv UpdateFulfillHtlc (sender has not signed htlc)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + + // actual test begins + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + sender.send(alice, UpdateFulfillHtlc("00" * 32, htlc.id, r)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFulfillHtlc (unknown htlc id)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFulfillHtlc("00" * 32, 42, "00" * 32)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFulfillHtlc (invalid preimage)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + + // actual test begins + sender.send(alice, UpdateFulfillHtlc("00" * 32, htlc.id, "00" * 32)) + relayer.expectMsgType[ForwardAdd] + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CMD_FAIL_HTLC") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure))) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + } + + test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val r: BinaryData = "11" * 32 + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure))) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) + } + + test("recv CMD_FAIL_MALFORMED_HTLC") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Crypto.sha256(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION)) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) + } + + test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, FailureMessageCodecs.BADONION)) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) + } + + test("recv CMD_FAIL_HTLC (invalid failure_code)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, 42)) + sender.expectMsg(Failure(InvalidFailureCode(channelId(bob)))) + assert(initialState == bob.stateData) + } + + test("recv UpdateFailHtlc") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure))) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailHtlc] + + // actual test begins + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + bob2alice.forward(alice) + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) + } + + test("recv UpdateFailMalformedHtlc") { f => + import f._ + val sender = TestProbe() + + // Alice sends an HTLC to Bob, which they both sign + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // Bob fails the HTLC because he cannot parse it + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Crypto.sha256(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION)) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] + bob2alice.forward(alice) + + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) + + sender.send(bob, CMD_SIGN) + val sig = bob2alice.expectMsgType[CommitSig] + // Bob should not have the htlc in its remote commit anymore + assert(sig.htlcSignatures.isEmpty) + + // and Alice should accept this signature + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] + } + + test("recv UpdateFailMalformedHtlc (invalid failure_code)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val fail = UpdateFailMalformedHtlc("00" * 32, htlc.id, Crypto.sha256(htlc.onionRoutingPacket), 42) + sender.send(alice, fail) + val error = alice2bob.expectMsgType[Error] + assert(new String(error.data) === InvalidFailureCode("00" * 32).getMessage) + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFailHtlc (sender has not signed htlc)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + + // actual test begins + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + sender.send(alice, UpdateFailHtlc("00" * 32, htlc.id, "00" * 152)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFailHtlc (unknown htlc id)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFailHtlc("00" * 32, 42, "00" * 152)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv CMD_UPDATE_FEE") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_UPDATE_FEE(20000)) + sender.expectMsg("ok") + val fee = alice2bob.expectMsgType[UpdateFee] + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) + } + + test("recv CMD_UPDATE_FEE (two in a row)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CMD_UPDATE_FEE(20000)) + sender.expectMsg("ok") + val fee1 = alice2bob.expectMsgType[UpdateFee] + sender.send(alice, CMD_UPDATE_FEE(30000)) + sender.expectMsg("ok") + val fee2 = alice2bob.expectMsgType[UpdateFee] + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee2)))) + } + + test("recv CMD_UPDATE_FEE (when fundee)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_UPDATE_FEE(20000)) + sender.expectMsg(Failure(FundeeCannotSendUpdateFee(channelId(bob)))) + assert(initialState == bob.stateData) + } + + test("recv UpdateFee") { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + val fee1 = UpdateFee("00" * 32, 12000) + bob ! fee1 + val fee2 = UpdateFee("00" * 32, 14000) + bob ! fee2 + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee2), remoteNextHtlcId = 0))) + } + + test("recv UpdateFee (two in a row)") { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + val fee = UpdateFee("00" * 32, 12000) + bob ! fee + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0))) + } + + test("recv UpdateFee (when sender is not funder)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFee("00" * 32, 12000)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) + alice2blockchain.expectMsg(PublishAsap(tx)) + alice2blockchain.expectMsgType[PublishAsap] + alice2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFee (sender can't afford it)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + val fee = UpdateFee("00" * 32, 100000000) + // we first update the global variable so that we don't trigger a 'fee too different' error + Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) + sender.send(bob, fee) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + //bob2blockchain.expectMsgType[PublishAsap] // main delayed (removed because of the high fees) + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFee (local/remote feerates are too different)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, UpdateFee("00" * 32, 85000)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=85000 localFeeratePerKw=10000") + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + test("recv UpdateFee (remote feerate is too small)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, UpdateFee("00" * 32, 252)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === "remote fee rate is too small: remoteFeeratePerKw=252") + awaitCond(bob.stateName == CLOSING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) + bob2blockchain.expectMsg(PublishAsap(tx)) + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + } + + ignore("recv CMD_UPDATE_RELAY_FEE ") { f => + import f._ + val sender = TestProbe() + val newFeeBaseMsat = TestConstants.Alice.nodeParams.feeBaseMsat * 2 + val newFeeProportionalMillionth = TestConstants.Alice.nodeParams.feeProportionalMillionth * 2 + sender.send(alice, CMD_UPDATE_RELAY_FEE(newFeeBaseMsat, newFeeProportionalMillionth)) + sender.expectMsg("ok") + + val localUpdate = relayer.expectMsgType[LocalChannelUpdate] + assert(localUpdate.channelUpdate.feeBaseMsat == newFeeBaseMsat) + assert(localUpdate.channelUpdate.feeProportionalMillionths == newFeeProportionalMillionth) + relayer.expectNoMsg(1 seconds) + } + + test("recv CMD_CLOSE (no pending htlcs)") { f => + import f._ + val sender = TestProbe() + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty) + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg("ok") + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == NORMAL) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) + } + + test("recv CMD_CLOSE (with unacked sent htlcs)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg(Failure(CannotCloseWithUnsignedOutgoingHtlcs(channelId(bob)))) + } + + test("recv CMD_CLOSE (with invalid final script)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, CMD_CLOSE(Some(BinaryData("00112233445566778899")))) + sender.expectMsg(Failure(InvalidFinalScript(channelId(alice)))) + } + + test("recv CMD_CLOSE (with signed sent htlcs)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg("ok") + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == NORMAL) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) + } + + test("recv CMD_CLOSE (two in a row)") { f => + import f._ + val sender = TestProbe() + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty) + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg("ok") + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == NORMAL) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice)))) + } + + test("recv CMD_CLOSE (while waiting for a RevokeAndAck)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + // actual test begins + sender.send(alice, CMD_CLOSE(None)) + sender.expectMsg("ok") + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == NORMAL) + } + + test("recv Shutdown (no pending htlcs)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, Shutdown("00" * 32, Bob.channelParams.defaultFinalScriptPubKey)) + alice2bob.expectMsgType[Shutdown] + alice2bob.expectMsgType[ClosingSigned] + awaitCond(alice.stateName == NEGOTIATING) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_NEGOTIATING].channelId) + } + + test("recv Shutdown (with unacked sent htlcs)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(bob, CMD_CLOSE(None)) + bob2alice.expectMsgType[Shutdown] + // actual test begins + bob2alice.forward(alice) + // alice sends a new sig + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + // bob replies with a revocation + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + // as soon as alice as received the revocation, she will send her shutdown message + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == SHUTDOWN) + // channel should be advertised as down + assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_SHUTDOWN].channelId) + } + + test("recv Shutdown (with unacked received htlcs)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + // actual test begins + sender.send(bob, Shutdown("00" * 32, TestConstants.Alice.channelParams.defaultFinalScriptPubKey)) + bob2alice.expectMsgType[Error] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + awaitCond(bob.stateName == CLOSING) + } + + test("recv Shutdown (with invalid final script)") { f => + import f._ + val sender = TestProbe() + sender.send(bob, Shutdown("00" * 32, BinaryData("00112233445566778899"))) + bob2alice.expectMsgType[Error] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + awaitCond(bob.stateName == CLOSING) + } + + test("recv Shutdown (with invalid final script and signed htlcs, in response to a Shutdown)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + sender.send(bob, CMD_CLOSE(None)) + bob2alice.expectMsgType[Shutdown] + // actual test begins + sender.send(bob, Shutdown("00" * 32, BinaryData("00112233445566778899"))) + bob2alice.expectMsgType[Error] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[PublishAsap] + bob2blockchain.expectMsgType[WatchConfirmed] + awaitCond(bob.stateName == CLOSING) + } + + test("recv Shutdown (with signed htlcs)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + sender.send(bob, Shutdown("00" * 32, TestConstants.Alice.channelParams.defaultFinalScriptPubKey)) + bob2alice.expectMsgType[Shutdown] + awaitCond(bob.stateName == SHUTDOWN) + } + + test("recv Shutdown (while waiting for a RevokeAndAck)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + sender.send(bob, CMD_CLOSE(None)) + bob2alice.expectMsgType[Shutdown] + // actual test begins + bob2alice.forward(alice) + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == SHUTDOWN) + } + + test("recv Shutdown (while waiting for a RevokeAndAck with pending outgoing htlc)") { f => + import f._ + val sender = TestProbe() + // let's make bob send a Shutdown message + sender.send(bob, CMD_CLOSE(None)) + bob2alice.expectMsgType[Shutdown] + // this is just so we have something to sign + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + // now we can sign + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + // adding an outgoing pending htlc + val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + // actual test begins + // alice eventually gets bob's shutdown + bob2alice.forward(alice) + // alice can't do anything for now other than waiting for bob to send the revocation + alice2bob.expectNoMsg() + // bob sends the revocation + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + // bob will also sign back + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + // then alice can sign the 2nd htlc + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + // and reply to bob's first signature + alice2bob.expectMsgType[RevokeAndAck] + alice2bob.forward(bob) + // bob replies with the 2nd revocation + bob2alice.expectMsgType[RevokeAndAck] + bob2alice.forward(alice) + // then alice can send her shutdown + alice2bob.expectMsgType[Shutdown] + awaitCond(alice.stateName == SHUTDOWN) + // note: bob will sign back a second time, but that is out of our scope + } + + test("recv CurrentBlockCount (no htlc timed out)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + sender.send(alice, CurrentBlockCount(400143)) + awaitCond(alice.stateData == initialState) + } + + test("recv CurrentBlockCount (an htlc timed out)") { f => + import f._ + val sender = TestProbe() + val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) + crossSign(alice, bob, alice2bob, bob2alice) + + // actual test begins + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val aliceCommitTx = initialState.commitments.localCommit.publishableTxs.commitTx.tx + sender.send(alice, CurrentBlockCount(400145)) + alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) + + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed + val watch = alice2blockchain.expectMsgType[WatchConfirmed] + assert(watch.event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) + } + + test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val event = CurrentFeerates(FeeratesPerKw.single(20000)) + sender.send(alice, event) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) + } + + test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(10010)) + sender.send(alice, event) + alice2bob.expectNoMsg(500 millis) + } + + test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(11000)) + sender.send(bob, event) + bob2alice.expectNoMsg(500 millis) + } + + test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(100)) + sender.send(bob, event) + bob2alice.expectMsgType[Error] + bob2blockchain.expectMsgType[PublishAsap] // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] + awaitCond(bob.stateName == CLOSING) + } + + test("recv BITCOIN_FUNDING_SPENT (their commit w/ htlc)") { f => + import f._ + val sender = TestProbe() + + val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) + val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) + val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) + val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) + val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) + crossSign(alice, bob, alice2bob, bob2alice) + fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) + fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) + + // at this point here is the situation from alice pov and what she should do when bob publishes his commit tx: + // balances : + // alice's balance : 449 999 990 => nothing to do + // bob's balance : 95 000 000 => nothing to do + // htlcs : + // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend + // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend + // alice -> bob : 10 (dust) => won't appear in the commitment tx + // bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage + // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout + + // bob publishes his current commit tx + val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) + + // in response to that, alice publishes its claim txes + val claimTxes = for (i <- 0 until 4) yield alice2blockchain.expectMsgType[PublishAsap].tx + // in addition to its main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage + val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { + assert(claimHtlcTx.txIn.size == 1) + assert(claimHtlcTx.txOut.size == 1) + Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + claimHtlcTx.txOut(0).amount + }).sum + // at best we have a little less than 450 000 + 250 000 + 100 000 + 50 000 = 850 000 (because fees) + assert(amountClaimed == Satoshi(814840)) + + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) // claim-main + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) + + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 1) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) + } + + test("recv BITCOIN_FUNDING_SPENT (their *next* commit w/ htlc)") { f => + import f._ + val sender = TestProbe() + + val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) + val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) + val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) + val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) + val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) + crossSign(alice, bob, alice2bob, bob2alice) + fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) + fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) + // alice sign but we intercept bob's revocation + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + + // as far as alice knows, bob currently has two valid unrevoked commitment transactions + + // at this point here is the situation from bob's pov with the latest sig received from alice, + // and what alice should do when bob publishes his commit tx: + // balances : + // alice's balance : 499 999 990 => nothing to do + // bob's balance : 95 000 000 => nothing to do + // htlcs : + // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend + // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend + // alice -> bob : 10 (dust) => won't appear in the commitment tx + // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout + + // bob publishes his current commit tx + val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + assert(bobCommitTx.txOut.size == 5) // two main outputs and 3 pending htlcs + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) + + // in response to that, alice publishes its claim txes + val claimTxes = for (i <- 0 until 3) yield alice2blockchain.expectMsgType[PublishAsap].tx + // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage + val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { + assert(claimHtlcTx.txIn.size == 1) + assert(claimHtlcTx.txOut.size == 1) + Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + claimHtlcTx.txOut(0).amount + }).sum + // at best we have a little less than 500 000 + 250 000 + 100 000 = 850 000 (because fees) + assert(amountClaimed == Satoshi(822280)) + + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) // claim-main + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) + + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.isDefined) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) + } + + test("recv BITCOIN_FUNDING_SPENT (revoked commit)") { f => + import f._ + val sender = TestProbe() + + // initially we have : + // alice = 800 000 + // bob = 200 000 + def send(): Transaction = { + // alice sends 8 000 sat + val (r, htlc) = addHtlc(10000000, alice, bob, alice2bob, bob2alice) crossSign(alice, bob, alice2bob, bob2alice) - sender.send(bob, add) - sender.expectMsg("ok") - bob2alice.expectMsgType[UpdateAddHtlc] - bob2alice.forward(alice) - sender.send(bob, add) - sender.expectMsg("ok") - bob2alice.expectMsgType[UpdateAddHtlc] - bob2alice.forward(alice) - - // actual test starts here - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - val commitSig = bob2alice.expectMsgType[CommitSig] - assert(commitSig.htlcSignatures.toSet.size == 4) - } - } - - test("recv CMD_SIGN (htlcs with same pubkeyScript but different amounts)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) - val epsilons = List(3, 1, 5, 7, 6) // unordered on purpose - val htlcCount = epsilons.size - for (i <- epsilons) { - sender.send(alice, add.copy(amountMsat = add.amountMsat + i * 1000)) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - } - // actual test starts here - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - val commitSig = alice2bob.expectMsgType[CommitSig] - assert(commitSig.htlcSignatures.toSet.size == htlcCount) - alice2bob.forward(bob) - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == htlcCount) - val htlcTxs = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs - val amounts = htlcTxs.map(_.txinfo.tx.txOut.head.amount.toLong) - assert(amounts === amounts.sorted) - } - } - - test("recv CMD_SIGN (no changes)") { case (alice, _, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, CMD_SIGN) - sender.expectNoMsg(1 second) // just ignored - //sender.expectMsg("cannot sign when there are no changes") - } - } - - test("recv CMD_SIGN (while waiting for RevokeAndAck (no pending changes)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) - val waitForRevocation = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get - assert(waitForRevocation.reSignAsap === false) - - // actual test starts here - sender.send(alice, CMD_SIGN) - sender.expectNoMsg(300 millis) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo === Left(waitForRevocation)) - } - } - - test("recv CMD_SIGN (while waiting for RevokeAndAck (with pending changes)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) - val waitForRevocation = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get - assert(waitForRevocation.reSignAsap === false) - - // actual test starts here - val (r2, htlc2) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectNoMsg(300 millis) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo === Left(waitForRevocation.copy(reSignAsap = true))) - } - } - - test("recv CommitSig (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - - // actual test begins - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - - bob2alice.expectMsgType[RevokeAndAck] - // bob replies immediately with a signature - bob2alice.expectMsgType[CommitSig] - - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == IN)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.acked.size == 0) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.signed.size == 1) - } - } - - test("recv CommitSig (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - - // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc.id && h.direction == OUT)) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 1) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) - } - } - - test("recv CommitSig (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - - val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r2, htlc2) = addHtlc(8000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r3, htlc3) = addHtlc(300000, bob, alice, bob2alice, alice2bob) // b->a (dust) - - val (r4, htlc4) = addHtlc(1000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r5, htlc5) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) // b->a (regular) - - val (r6, htlc6) = addHtlc(500000, alice, bob, alice2bob, bob2alice) // a->b (dust) - - val (r7, htlc7) = addHtlc(4000000, bob, alice, bob2alice, alice2bob) // b->a (regular) - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - - // actual test begins - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.index == 1) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 3) - } - } - - test("recv CommitSig (only fee update)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - - sender.send(alice, CMD_UPDATE_FEE(TestConstants.feeratePerKw + 1000, commit = false)) - sender.expectMsg("ok") - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - - // actual test begins (note that channel sends a CMD_SIGN to itself when it receives RevokeAndAck and there are changes) - alice2bob.expectMsgType[UpdateFee] - alice2bob.forward(bob) - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - } - } - - test("recv CommitSig (two htlcs received with same r)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val r = BinaryData("42" * 32) - val h: BinaryData = Crypto.sha256(r) - - sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) - sender.expectMsg("ok") - val htlc1 = alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - - sender.send(alice, CMD_ADD_HTLC(50000000, h, 400144)) - sender.expectMsg("ok") - val htlc2 = alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteChanges.proposed == htlc1 :: htlc2 :: Nil) - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - - crossSign(alice, bob, alice2bob, bob2alice) - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.htlcs.exists(h => h.add.id == htlc1.id && h.direction == IN)) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.htlcTxsAndSigs.size == 2) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.spec.toLocalMsat == initialState.commitments.localCommit.spec.toLocalMsat) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx.txOut.count(_.amount == Satoshi(50000)) == 2) - } - } - - test("recv CommitSig (no changes)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - // signature is invalid but it doesn't matter - sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) - bob2alice.expectMsgType[Error] - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CommitSig (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - - // actual test begins - sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data).startsWith("invalid commitment signature")) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CommitSig (bad htlc sig count)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - val commitSig = alice2bob.expectMsgType[CommitSig] - - // actual test begins - val badCommitSig = commitSig.copy(htlcSignatures = commitSig.htlcSignatures ::: commitSig.htlcSignatures) - sender.send(bob, badCommitSig) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === HtlcSigCountMismatch(channelId(bob), expected = 1, actual = 2).getMessage) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CommitSig (invalid htlc sig)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - val commitSig = alice2bob.expectMsgType[CommitSig] - - // actual test begins - val badCommitSig = commitSig.copy(htlcSignatures = commitSig.signature :: Nil) - sender.send(bob, badCommitSig) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data).startsWith("invalid htlc signature")) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - - test("recv RevokeAndAck (one htlc sent)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - - // actual test begins - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isLeft) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localChanges.acked.size == 1) - } - } - - test("recv RevokeAndAck (one htlc received)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - - // actual test begins - alice2bob.expectMsgType[RevokeAndAck] - alice2bob.forward(bob) - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - } - } - - test("recv RevokeAndAck (multiple htlcs in both directions)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r2, htlc2) = addHtlc(8000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r3, htlc3) = addHtlc(300000, bob, alice, bob2alice, alice2bob) // b->a (dust) - - val (r4, htlc4) = addHtlc(1000000, alice, bob, alice2bob, bob2alice) // a->b (regular) - - val (r5, htlc5) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) // b->a (regular) - - val (r6, htlc6) = addHtlc(500000, alice, bob, alice2bob, bob2alice) // a->b (dust) - - val (r7, htlc7) = addHtlc(4000000, bob, alice, bob2alice, alice2bob) // b->a (regular) - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - - // actual test begins - alice2bob.expectMsgType[RevokeAndAck] - alice2bob.forward(bob) - - awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.index == 1) - assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteCommit.spec.htlcs.size == 7) - } - } - - test("recv RevokeAndAck (with reSignAsap=true)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - val (r2, htlc2) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectNoMsg(300 millis) - assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.left.toOption.get.reSignAsap === true) - - // actual test starts here - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - alice2bob.expectMsgType[CommitSig] - } - } - - test("recv RevokeAndAck (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - - // actual test begins - bob2alice.expectMsgType[RevokeAndAck] - sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv RevokeAndAck (unexpectedly)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight) - sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CMD_FULFILL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r)) - sender.expectMsg("ok") - val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) - } - } - - test("recv CMD_FULFILL_HTLC (unknown htlc id)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val r: BinaryData = "11" * 32 - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - - sender.send(bob, CMD_FULFILL_HTLC(42, r)) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } - } - - test("recv CMD_FULFILL_HTLC (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FULFILL_HTLC(htlc.id, "00" * 32)) - sender.expectMsg(Failure(InvalidHtlcPreimage(channelId(bob), 0))) - assert(initialState == bob.stateData) - } - } - - test("recv UpdateFulfillHtlc") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r)) - sender.expectMsg("ok") - val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] - - // actual test begins - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - bob2alice.forward(alice) - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill)))) - } - } - - test("recv UpdateFulfillHtlc (sender has not signed htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - - // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - sender.send(alice, UpdateFulfillHtlc("00" * 32, htlc.id, r)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFulfillHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFulfillHtlc("00" * 32, 42, "00" * 32)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFulfillHtlc (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - - // actual test begins - sender.send(alice, UpdateFulfillHtlc("00" * 32, htlc.id, "00" * 32)) - relayer.expectMsgType[ForwardAdd] - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CMD_FAIL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure))) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) - } - } - - test("recv CMD_FAIL_HTLC (unknown htlc id)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val r: BinaryData = "11" * 32 - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - - sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure))) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } - } - - test("recv CMD_FAIL_MALFORMED_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Crypto.sha256(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION)) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) - } - } - - test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, FailureMessageCodecs.BADONION)) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } - } - - test("recv CMD_FAIL_HTLC (invalid failure_code)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, 42)) - sender.expectMsg(Failure(InvalidFailureCode(channelId(bob)))) - assert(initialState == bob.stateData) - } - } - - test("recv UpdateFailHtlc") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - sender.send(bob, CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure))) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailHtlc] - - // actual test begins - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - bob2alice.forward(alice) - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) - } - } - - test("recv UpdateFailMalformedHtlc") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - - // Alice sends an HTLC to Bob, which they both sign - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // Bob fails the HTLC because he cannot parse it - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(htlc.id, Crypto.sha256(htlc.onionRoutingPacket), FailureMessageCodecs.BADONION)) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] - bob2alice.forward(alice) - - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail)))) - - sender.send(bob, CMD_SIGN) - val sig = bob2alice.expectMsgType[CommitSig] - // Bob should not have the htlc in its remote commit anymore - assert(sig.htlcSignatures.isEmpty) - - // and Alice should accept this signature - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - } - } - - test("recv UpdateFailMalformedHtlc (invalid failure_code)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val fail = UpdateFailMalformedHtlc("00" * 32, htlc.id, Crypto.sha256(htlc.onionRoutingPacket), 42) - sender.send(alice, fail) - val error = alice2bob.expectMsgType[Error] - assert(new String(error.data) === InvalidFailureCode("00" * 32).getMessage) - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFailHtlc (sender has not signed htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - - // actual test begins - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - sender.send(alice, UpdateFailHtlc("00" * 32, htlc.id, "00" * 152)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFailHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFailHtlc("00" * 32, 42, "00" * 152)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv CMD_UPDATE_FEE") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CMD_UPDATE_FEE(20000)) - sender.expectMsg("ok") - val fee = alice2bob.expectMsgType[UpdateFee] - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) - } - } - - test("recv CMD_UPDATE_FEE (when fundee)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] - sender.send(bob, CMD_UPDATE_FEE(20000)) - sender.expectMsg(Failure(FundeeCannotSendUpdateFee(channelId(bob)))) - assert(initialState == bob.stateData) - } - } - - test("recv UpdateFee") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] - val fee = UpdateFee("00" * 32, 12000) - bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee), remoteNextHtlcId = 0))) - } - } - - test("recv UpdateFee (when sender is not funder)") { case (alice, _, alice2bob, _, alice2blockchain, _, relayer) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFee("00" * 32, 12000)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId) - alice2blockchain.expectMsg(PublishAsap(tx)) - alice2blockchain.expectMsgType[PublishAsap] - alice2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFee (sender can't afford it)") { case (_, bob, _, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - val fee = UpdateFee("00" * 32, 100000000) - // we first update the global variable so that we don't trigger a 'fee too different' error - Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) - sender.send(bob, fee) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === CannotAffordFees(channelId(bob), missingSatoshis = 71620000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - //bob2blockchain.expectMsgType[PublishAsap] // main delayed (removed because of the high fees) - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFee (local/remote feerates are too different)") { case (_, bob, _, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, UpdateFee("00" * 32, 85000)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=85000 localFeeratePerKw=10000") - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - test("recv UpdateFee (remote feerate is too small)") { case (_, bob, _, bob2alice, _, bob2blockchain, relayer) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, UpdateFee("00" * 32, 252)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === "remote fee rate is too small: remoteFeeratePerKw=252") - awaitCond(bob.stateName == CLOSING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId) - bob2blockchain.expectMsg(PublishAsap(tx)) - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - } - } - - ignore("recv CMD_UPDATE_RELAY_FEE ") { case (alice, bob, alice2bob, bob2alice, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - val newFeeBaseMsat = TestConstants.Alice.nodeParams.feeBaseMsat * 2 - val newFeeProportionalMillionth = TestConstants.Alice.nodeParams.feeProportionalMillionth * 2 - sender.send(alice, CMD_UPDATE_RELAY_FEE(newFeeBaseMsat, newFeeProportionalMillionth)) - sender.expectMsg("ok") - - val localUpdate = relayer.expectMsgType[LocalChannelUpdate] - assert(localUpdate.channelUpdate.feeBaseMsat == newFeeBaseMsat) - assert(localUpdate.channelUpdate.feeProportionalMillionths == newFeeProportionalMillionth) - relayer.expectNoMsg(1 seconds) - } - } - - test("recv CMD_CLOSE (no pending htlcs)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty) - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg("ok") - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == NORMAL) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) - } - } - - test("recv CMD_CLOSE (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg(Failure(CannotCloseWithUnsignedOutgoingHtlcs(channelId(bob)))) - } - } - - test("recv CMD_CLOSE (with invalid final script)") { case (alice, _, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, CMD_CLOSE(Some(BinaryData("00112233445566778899")))) - sender.expectMsg(Failure(InvalidFinalScript(channelId(alice)))) - } - } - - test("recv CMD_CLOSE (with signed sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg("ok") - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == NORMAL) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) - } - } - - test("recv CMD_CLOSE (two in a row)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isEmpty) - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg("ok") - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == NORMAL) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].localShutdown.isDefined) - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg(Failure(ClosingAlreadyInProgress(channelId(alice)))) - } - } - - test("recv CMD_CLOSE (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - // actual test begins - sender.send(alice, CMD_CLOSE(None)) - sender.expectMsg("ok") - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == NORMAL) - } - } - - test("recv Shutdown (no pending htlcs)") { case (alice, _, alice2bob, _, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, Shutdown("00" * 32, Bob.channelParams.defaultFinalScriptPubKey)) - alice2bob.expectMsgType[Shutdown] - alice2bob.expectMsgType[ClosingSigned] - awaitCond(alice.stateName == NEGOTIATING) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_NEGOTIATING].channelId) - } - } - - test("recv Shutdown (with unacked sent htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(bob, CMD_CLOSE(None)) - bob2alice.expectMsgType[Shutdown] - // actual test begins - bob2alice.forward(alice) - // alice sends a new sig - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - // bob replies with a revocation - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - // as soon as alice as received the revocation, she will send her shutdown message - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == SHUTDOWN) - // channel should be advertised as down - assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_SHUTDOWN].channelId) - } - } - - test("recv Shutdown (with unacked received htlcs)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - // actual test begins - sender.send(bob, Shutdown("00" * 32, TestConstants.Alice.channelParams.defaultFinalScriptPubKey)) - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - awaitCond(bob.stateName == CLOSING) - } - } - - test("recv Shutdown (with invalid final script)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(bob, Shutdown("00" * 32, BinaryData("00112233445566778899"))) - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - awaitCond(bob.stateName == CLOSING) - } - } - - test("recv Shutdown (with invalid final script and signed htlcs, in response to a Shutdown)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - sender.send(bob, CMD_CLOSE(None)) - bob2alice.expectMsgType[Shutdown] - // actual test begins - sender.send(bob, Shutdown("00" * 32, BinaryData("00112233445566778899"))) - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[PublishAsap] - bob2blockchain.expectMsgType[WatchConfirmed] - awaitCond(bob.stateName == CLOSING) - } - } - - test("recv Shutdown (with signed htlcs)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - sender.send(bob, Shutdown("00" * 32, TestConstants.Alice.channelParams.defaultFinalScriptPubKey)) - bob2alice.expectMsgType[Shutdown] - awaitCond(bob.stateName == SHUTDOWN) - } - } - - test("recv Shutdown (while waiting for a RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - sender.send(bob, CMD_CLOSE(None)) - bob2alice.expectMsgType[Shutdown] - // actual test begins - bob2alice.forward(alice) - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == SHUTDOWN) - } - } - - test("recv Shutdown (while waiting for a RevokeAndAck with pending outgoing htlc)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - // let's make bob send a Shutdown message - sender.send(bob, CMD_CLOSE(None)) - bob2alice.expectMsgType[Shutdown] - // this is just so we have something to sign - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - // now we can sign - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - // adding an outgoing pending htlc - val (r1, htlc1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - // actual test begins - // alice eventually gets bob's shutdown - bob2alice.forward(alice) - // alice can't do anything for now other than waiting for bob to send the revocation - alice2bob.expectNoMsg() - // bob sends the revocation - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - // bob will also sign back - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - // then alice can sign the 2nd htlc - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - // and reply to bob's first signature - alice2bob.expectMsgType[RevokeAndAck] - alice2bob.forward(bob) - // bob replies with the 2nd revocation - bob2alice.expectMsgType[RevokeAndAck] - bob2alice.forward(alice) - // then alice can send her shutdown - alice2bob.expectMsgType[Shutdown] - awaitCond(alice.stateName == SHUTDOWN) - // note: bob will sign back a second time, but that is out of our scope - } - } - - test("recv CurrentBlockCount (no htlc timed out)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - sender.send(alice, CurrentBlockCount(400143)) - awaitCond(alice.stateData == initialState) - } - } - - test("recv CurrentBlockCount (an htlc timed out)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - // actual test begins - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val aliceCommitTx = initialState.commitments.localCommit.publishableTxs.commitTx.tx - sender.send(alice, CurrentBlockCount(400145)) - alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) - - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed - val watch = alice2blockchain.expectMsgType[WatchConfirmed] - assert(watch.event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) - } - } - - test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val event = CurrentFeerates(FeeratesPerKw.single(20000)) - sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) - } - } - - test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(10010)) - sender.send(alice, event) - alice2bob.expectNoMsg(500 millis) - } - } - - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { case (_, bob, _, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(11000)) - sender.send(bob, event) - bob2alice.expectNoMsg(500 millis) - } - } - - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(100)) - sender.send(bob, event) - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishAsap] // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - awaitCond(bob.stateName == CLOSING) - } - } - - test("recv BITCOIN_FUNDING_SPENT (their commit w/ htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - - val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) - val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) - val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) - val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) - val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) - crossSign(alice, bob, alice2bob, bob2alice) - fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) - fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) - - // at this point here is the situation from alice pov and what she should do when bob publishes his commit tx: - // balances : - // alice's balance : 449 999 990 => nothing to do - // bob's balance : 95 000 000 => nothing to do - // htlcs : - // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend - // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend - // alice -> bob : 10 (dust) => won't appear in the commitment tx - // bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage - // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout - - // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) - - // in response to that, alice publishes its claim txes - val claimTxes = for (i <- 0 until 4) yield alice2blockchain.expectMsgType[PublishAsap].tx - // in addition to its main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage - val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { - assert(claimHtlcTx.txIn.size == 1) - assert(claimHtlcTx.txOut.size == 1) - Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - claimHtlcTx.txOut(0).amount - }).sum - // at best we have a little less than 450 000 + 250 000 + 100 000 + 50 000 = 850 000 (because fees) - assert(amountClaimed == Satoshi(814840)) - - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) // claim-main - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) - - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 1) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) - } - } - - test("recv BITCOIN_FUNDING_SPENT (their *next* commit w/ htlc)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - - val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) - val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) - val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) - val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) - val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) - crossSign(alice, bob, alice2bob, bob2alice) - fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) - fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) - // alice sign but we intercept bob's revocation - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - - // as far as alice knows, bob currently has two valid unrevoked commitment transactions - - // at this point here is the situation from bob's pov with the latest sig received from alice, - // and what alice should do when bob publishes his commit tx: - // balances : - // alice's balance : 499 999 990 => nothing to do - // bob's balance : 95 000 000 => nothing to do - // htlcs : - // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend - // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend - // alice -> bob : 10 (dust) => won't appear in the commitment tx - // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout - - // bob publishes his current commit tx - val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - assert(bobCommitTx.txOut.size == 5) // two main outputs and 3 pending htlcs - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) - - // in response to that, alice publishes its claim txes - val claimTxes = for (i <- 0 until 3) yield alice2blockchain.expectMsgType[PublishAsap].tx - // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage - val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { - assert(claimHtlcTx.txIn.size == 1) - assert(claimHtlcTx.txOut.size == 1) - Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - claimHtlcTx.txOut(0).amount - }).sum - // at best we have a little less than 500 000 + 250 000 + 100 000 = 850 000 (because fees) - assert(amountClaimed == Satoshi(822280)) - - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) // claim-main - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) - - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.isDefined) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) - } - } - - test("recv BITCOIN_FUNDING_SPENT (revoked commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - - // initially we have : - // alice = 800 000 - // bob = 200 000 - def send(): Transaction = { - // alice sends 8 000 sat - val (r, htlc) = addHtlc(10000000, alice, bob, alice2bob, bob2alice) - crossSign(alice, bob, alice2bob, bob2alice) - - bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - } - - val txs = for (i <- 0 until 10) yield send() - // bob now has 10 spendable tx, 9 of them being revoked - - // let's say that bob published this tx - val revokedTx = txs(3) - // channel state for this revoked tx is as follows: - // alice = 760 000 - // bob = 200 000 - // a->b = 10 000 - // a->b = 10 000 - // a->b = 10 000 - // a->b = 10 000 - // two main outputs + 4 htlc - assert(revokedTx.txOut.size == 6) - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) - alice2bob.expectMsgType[Error] - - val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx - val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx -// val htlcPenaltyTxs = for (i <- 0 until 4) yield alice2blockchain.expectMsgType[PublishAsap].tx - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty - // let's make sure that htlc-penalty txs each spend a different output -// assert(htlcPenaltyTxs.map(_.txIn.head.outPoint.index).toSet.size === htlcPenaltyTxs.size) -// htlcPenaltyTxs.foreach(htlcPenaltyTx => assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)) - alice2blockchain.expectNoMsg(1 second) - - Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) -// htlcPenaltyTxs.foreach(htlcPenaltyTx => Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) - - // two main outputs are 760 000 and 200 000 - assert(mainTx.txOut(0).amount == Satoshi(741490)) - assert(mainPenaltyTx.txOut(0).amount == Satoshi(195150)) -// assert(htlcPenaltyTxs(0).txOut(0).amount == Satoshi(4530)) -// assert(htlcPenaltyTxs(1).txOut(0).amount == Satoshi(4530)) -// assert(htlcPenaltyTxs(2).txOut(0).amount == Satoshi(4530)) -// assert(htlcPenaltyTxs(3).txOut(0).amount == Satoshi(4530)) - - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) - - } - } - - ignore("recv BITCOIN_FUNDING_SPENT (revoked commit with identical htlcs)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - - // initially we have : - // alice = 800 000 - // bob = 200 000 - - val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) - sender.send(alice, add) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - sender.send(alice, add) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - - crossSign(alice, bob, alice2bob, bob2alice) - // bob will publish this tx after it is revoked - val revokedTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - - sender.send(alice, add) - sender.expectMsg("ok") - alice2bob.expectMsgType[UpdateAddHtlc] - alice2bob.forward(bob) - - crossSign(alice, bob, alice2bob, bob2alice) - - // channel state for this revoked tx is as follows: - // alice = 780 000 - // bob = 200 000 - // a->b = 10 000 - // a->b = 10 000 - assert(revokedTx.txOut.size == 4) - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) - alice2bob.expectMsgType[Error] - - val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx - val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx - val htlcPenaltyTxs = for (i <- 0 until 2) yield alice2blockchain.expectMsgType[PublishAsap].tx - // let's make sure that htlc-penalty txs each spend a different output - assert(htlcPenaltyTxs.map(_.txIn.head.outPoint.index).toSet.size === htlcPenaltyTxs.size) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty - htlcPenaltyTxs.foreach(htlcPenaltyTx => assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)) - alice2blockchain.expectNoMsg(1 second) - - Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - htlcPenaltyTxs.foreach(htlcPenaltyTx => Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) - } - } - - test("recv Error") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) - val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) - val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) - val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) - val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) - crossSign(alice, bob, alice2bob, bob2alice) - fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) - fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) - - // at this point here is the situation from alice pov and what she should do when she publishes his commit tx: - // balances : - // alice's balance : 449 999 990 => nothing to do - // bob's balance : 95 000 000 => nothing to do - // htlcs : - // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend using 2nd stage htlc-timeout - // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend using 2nd stage htlc-timeout - // alice -> bob : 10 (dust) => won't appear in the commitment tx - // bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage using htlc-success - // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout - - // an error occurs and alice publishes her commit tx - val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx - alice ! Error("00" * 32, "oops".getBytes()) - alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) - assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs - - // alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the htlc - // so we expect 7 transactions: - // - 1 tx to claim the main delayed output - // - 3 txes for each htlc - // - 3 txes for each delayed output of the claimed htlc - val claimTxs = for (i <- 0 until 7) yield alice2blockchain.expectMsgType[PublishAsap].tx - - // the main delayed output spends the commitment transaction - Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - - // 2nd stage transactions spend the commitment transaction - Transaction.correctlySpends(claimTxs(1), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(2), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(3), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - - // 3rd stage transactions spend their respective HTLC-Success/HTLC-Timeout transactions - Transaction.correctlySpends(claimTxs(4), claimTxs(1) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(5), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(6), claimTxs(3) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(0))) // main-delayed - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(4))) // htlc-delayed - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(5))) // htlc-delayed - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(6))) // htlc-delayed - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) - - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) - val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get - assert(localCommitPublished.commitTx == aliceCommitTx) - assert(localCommitPublished.htlcSuccessTxs.size == 1) - assert(localCommitPublished.htlcTimeoutTxs.size == 2) - assert(localCommitPublished.claimHtlcDelayedTxs.size == 3) - } - } - - test("recv BITCOIN_FUNDING_DEEPLYBURIED", Tag("channels_public")) { case (alice, _, alice2bob, _, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] - // public channel: we don't send the channel_update directly to the peer - alice2bob.expectNoMsg(1 second) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) - // we don't re-publish the same channel_update if there was no change - relayer.expectNoMsg(1 second) - } - } - - test("recv BITCOIN_FUNDING_DEEPLYBURIED (short channel id changed)", Tag("channels_public")) { case (alice, _, alice2bob, _, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400001, 22)) - val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] - // public channel: we don't send the channel_update directly to the peer - alice2bob.expectNoMsg(1 second) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) - assert(relayer.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) - relayer.expectNoMsg(1 second) - } - } - - test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel)") { case (alice, _, alice2bob, _, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - // private channel: we send the channel_update directly to the peer - val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) - // we don't re-publish the same channel_update if there was no change - relayer.expectNoMsg(1 second) - } - } - - test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel, short channel id changed)") { case (alice, _, alice2bob, _, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400001, 22)) - // private channel: we send the channel_update directly to the peer - val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) - // LocalChannelUpdate should not be published - assert(relayer.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) - relayer.expectNoMsg(1 second) - } - } - - test("recv AnnouncementSignatures", Tag("channels_public")) { case (alice, bob, alice2bob, bob2alice, _, _, relayer) => - within(30 seconds) { - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] - sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] - import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) - // actual test starts here - bob2alice.forward(alice) - awaitCond({ - val normal = alice.stateData.asInstanceOf[DATA_NORMAL] - normal.shortChannelId == annSigsA.shortChannelId && normal.buried && normal.channelAnnouncement == Some(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId - }) - assert(relayer.expectMsgType[LocalChannelUpdate].channelAnnouncement_opt === Some(channelAnn)) - } - } - - test("recv AnnouncementSignatures (re-send)", Tag("channels_public")) { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) - val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] - sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) - val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] - import initialState.commitments.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) - bob2alice.forward(alice) - awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn)) - - // actual test starts here - // simulate bob re-sending its sigs - bob2alice.send(alice, annSigsA) - // alice re-sends her sigs - alice2bob.expectMsg(annSigsA) - } - } - - ignore("recv TickRefreshChannelUpdate", Tag("channels_public")) { case (alice, bob, alice2bob, bob2alice, _, _, relayer) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) - bob2alice.expectMsgType[AnnouncementSignatures] - bob2alice.forward(alice) - val update1 = relayer.expectMsgType[LocalChannelUpdate] - - // actual test starts here - Thread.sleep(1000) - sender.send(alice, TickRefreshChannelUpdate) - val update2 = relayer.expectMsgType[LocalChannelUpdate] - assert(update1.channelUpdate.timestamp < update2.channelUpdate.timestamp) - } + bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + } + + val txs = for (i <- 0 until 10) yield send() + // bob now has 10 spendable tx, 9 of them being revoked + + // let's say that bob published this tx + val revokedTx = txs(3) + // channel state for this revoked tx is as follows: + // alice = 760 000 + // bob = 200 000 + // a->b = 10 000 + // a->b = 10 000 + // a->b = 10 000 + // a->b = 10 000 + // two main outputs + 4 htlc + assert(revokedTx.txOut.size == 6) + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) + alice2bob.expectMsgType[Error] + + val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx + val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx +// val htlcPenaltyTxs = for (i <- 0 until 4) yield alice2blockchain.expectMsgType[PublishAsap].tx + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty + // let's make sure that htlc-penalty txs each spend a different output +// assert(htlcPenaltyTxs.map(_.txIn.head.outPoint.index).toSet.size === htlcPenaltyTxs.size) +// htlcPenaltyTxs.foreach(htlcPenaltyTx => assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)) + alice2blockchain.expectNoMsg(1 second) + + Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) +// htlcPenaltyTxs.foreach(htlcPenaltyTx => Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + + // two main outputs are 760 000 and 200 000 + assert(mainTx.txOut(0).amount == Satoshi(741490)) + assert(mainPenaltyTx.txOut(0).amount == Satoshi(195150)) +// assert(htlcPenaltyTxs(0).txOut(0).amount == Satoshi(4530)) +// assert(htlcPenaltyTxs(1).txOut(0).amount == Satoshi(4530)) +// assert(htlcPenaltyTxs(2).txOut(0).amount == Satoshi(4530)) +// assert(htlcPenaltyTxs(3).txOut(0).amount == Satoshi(4530)) + + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) + + } + + ignore("recv BITCOIN_FUNDING_SPENT (revoked commit with identical htlcs)") { f => + import f._ + val sender = TestProbe() + + // initially we have : + // alice = 800 000 + // bob = 200 000 + + val add = CMD_ADD_HTLC(10000000, "11" * 32, 400144) + sender.send(alice, add) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + sender.send(alice, add) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + + crossSign(alice, bob, alice2bob, bob2alice) + // bob will publish this tx after it is revoked + val revokedTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + + sender.send(alice, add) + sender.expectMsg("ok") + alice2bob.expectMsgType[UpdateAddHtlc] + alice2bob.forward(bob) + + crossSign(alice, bob, alice2bob, bob2alice) + + // channel state for this revoked tx is as follows: + // alice = 780 000 + // bob = 200 000 + // a->b = 10 000 + // a->b = 10 000 + assert(revokedTx.txOut.size == 4) + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) + alice2bob.expectMsgType[Error] + + val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx + val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx + val htlcPenaltyTxs = for (i <- 0 until 2) yield alice2blockchain.expectMsgType[PublishAsap].tx + // let's make sure that htlc-penalty txs each spend a different output + assert(htlcPenaltyTxs.map(_.txIn.head.outPoint.index).toSet.size === htlcPenaltyTxs.size) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty + htlcPenaltyTxs.foreach(htlcPenaltyTx => assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT)) + alice2blockchain.expectNoMsg(1 second) + + Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + htlcPenaltyTxs.foreach(htlcPenaltyTx => Transaction.correctlySpends(htlcPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + } + + test("recv Error") { f => + import f._ + val (ra1, htlca1) = addHtlc(250000000, alice, bob, alice2bob, bob2alice) + val (ra2, htlca2) = addHtlc(100000000, alice, bob, alice2bob, bob2alice) + val (ra3, htlca3) = addHtlc(10000, alice, bob, alice2bob, bob2alice) + val (rb1, htlcb1) = addHtlc(50000000, bob, alice, bob2alice, alice2bob) + val (rb2, htlcb2) = addHtlc(55000000, bob, alice, bob2alice, alice2bob) + crossSign(alice, bob, alice2bob, bob2alice) + fulfillHtlc(1, ra2, bob, alice, bob2alice, alice2bob) + fulfillHtlc(0, rb1, alice, bob, alice2bob, bob2alice) + + // at this point here is the situation from alice pov and what she should do when she publishes his commit tx: + // balances : + // alice's balance : 449 999 990 => nothing to do + // bob's balance : 95 000 000 => nothing to do + // htlcs : + // alice -> bob : 250 000 000 (bob does not have the preimage) => wait for the timeout and spend using 2nd stage htlc-timeout + // alice -> bob : 100 000 000 (bob has the preimage) => if bob does not use the preimage, wait for the timeout and spend using 2nd stage htlc-timeout + // alice -> bob : 10 (dust) => won't appear in the commitment tx + // bob -> alice : 50 000 000 (alice has the preimage) => spend immediately using the preimage using htlc-success + // bob -> alice : 55 000 000 (alice does not have the preimage) => nothing to do, bob will get his money back after the timeout + + // an error occurs and alice publishes her commit tx + val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx + alice ! Error("00" * 32, "oops".getBytes()) + alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) + assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs + + // alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the htlc + // so we expect 7 transactions: + // - 1 tx to claim the main delayed output + // - 3 txes for each htlc + // - 3 txes for each delayed output of the claimed htlc + val claimTxs = for (i <- 0 until 7) yield alice2blockchain.expectMsgType[PublishAsap].tx + + // the main delayed output spends the commitment transaction + Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + + // 2nd stage transactions spend the commitment transaction + Transaction.correctlySpends(claimTxs(1), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(2), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(3), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + + // 3rd stage transactions spend their respective HTLC-Success/HTLC-Timeout transactions + Transaction.correctlySpends(claimTxs(4), claimTxs(1) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(5), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(6), claimTxs(3) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(0))) // main-delayed + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(4))) // htlc-delayed + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(5))) // htlc-delayed + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(6))) // htlc-delayed + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) + + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) + val localCommitPublished = alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.get + assert(localCommitPublished.commitTx == aliceCommitTx) + assert(localCommitPublished.htlcSuccessTxs.size == 1) + assert(localCommitPublished.htlcTimeoutTxs.size == 2) + assert(localCommitPublished.claimHtlcDelayedTxs.size == 3) + } + + test("recv BITCOIN_FUNDING_DEEPLYBURIED", Tag("channels_public")) { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] + // public channel: we don't send the channel_update directly to the peer + alice2bob.expectNoMsg(1 second) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) + // we don't re-publish the same channel_update if there was no change + relayer.expectNoMsg(1 second) + } + + test("recv BITCOIN_FUNDING_DEEPLYBURIED (short channel id changed)", Tag("channels_public")) { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400001, 22)) + val annSigs = alice2bob.expectMsgType[AnnouncementSignatures] + // public channel: we don't send the channel_update directly to the peer + alice2bob.expectNoMsg(1 second) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == annSigs.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) + assert(relayer.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) + relayer.expectNoMsg(1 second) + } + + test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + // private channel: we send the channel_update directly to the peer + val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) + // we don't re-publish the same channel_update if there was no change + relayer.expectNoMsg(1 second) + } + + test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel, short channel id changed)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400001, 22)) + // private channel: we send the channel_update directly to the peer + val channelUpdate = alice2bob.expectMsgType[ChannelUpdate] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId == channelUpdate.shortChannelId && alice.stateData.asInstanceOf[DATA_NORMAL].buried == true) + // LocalChannelUpdate should not be published + assert(relayer.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId) + relayer.expectNoMsg(1 second) + } + + test("recv AnnouncementSignatures", Tag("channels_public")) { f => + import f._ + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] + sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] + import initialState.commitments.{localParams, remoteParams} + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + // actual test starts here + bob2alice.forward(alice) + awaitCond({ + val normal = alice.stateData.asInstanceOf[DATA_NORMAL] + normal.shortChannelId == annSigsA.shortChannelId && normal.buried && normal.channelAnnouncement == Some(channelAnn) && normal.channelUpdate.shortChannelId == annSigsA.shortChannelId + }) + assert(relayer.expectMsgType[LocalChannelUpdate].channelAnnouncement_opt === Some(channelAnn)) + } + + test("recv AnnouncementSignatures (re-send)", Tag("channels_public")) { f => + import f._ + val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) + val annSigsA = alice2bob.expectMsgType[AnnouncementSignatures] + sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 42, 10)) + val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] + import initialState.commitments.{localParams, remoteParams} + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.keyManager.fundingPublicKey(localParams.channelKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + bob2alice.forward(alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement === Some(channelAnn)) + + // actual test starts here + // simulate bob re-sending its sigs + bob2alice.send(alice, annSigsA) + // alice re-sends her sigs + alice2bob.expectMsg(annSigsA) + } + + ignore("recv TickRefreshChannelUpdate", Tag("channels_public")) { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + bob2alice.expectMsgType[AnnouncementSignatures] + bob2alice.forward(alice) + val update1 = relayer.expectMsgType[LocalChannelUpdate] + + // actual test starts here + Thread.sleep(1100) + sender.send(alice, TickRefreshChannelUpdate) + val update2 = relayer.expectMsgType[LocalChannelUpdate] + assert(update1.channelUpdate.timestamp < update2.channelUpdate.timestamp) + } + + test("recv INPUT_DISCONNECTED", Tag("channels_public")) { f => + import f._ + val sender = TestProbe() + sender.send(alice, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42)) + bob2alice.expectMsgType[AnnouncementSignatures] + bob2alice.forward(alice) + val update1 = relayer.expectMsgType[LocalChannelUpdate] + assert(Announcements.isEnabled(update1.channelUpdate.channelFlags) == true) + + // actual test starts here + Thread.sleep(1100) + sender.send(alice, INPUT_DISCONNECTED) + val update2 = relayer.expectMsgType[LocalChannelUpdate] + assert(update1.channelUpdate.timestamp < update2.channelUpdate.timestamp) + assert(Announcements.isEnabled(update2.channelUpdate.channelFlags) == false) + awaitCond(alice.stateName == OFFLINE) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 85dc898d7..0226ceab4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -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) + } + } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala index f43e530b4..d1a7d7680 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/f/ShutdownStateSpec.scala @@ -28,20 +28,19 @@ import fr.acinq.eclair.payment.{ForwardAdd, Local, PaymentLifecycle} import fr.acinq.eclair.router.Hop import fr.acinq.eclair.wire.{CommitSig, Error, FailureMessageCodecs, PermanentChannelFailure, RevokeAndAck, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc} import fr.acinq.eclair.{Globals, 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 ShutdownStateSpec 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) { @@ -91,711 +90,666 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods { bob2alice.forward(alice) awaitCond(alice.stateName == SHUTDOWN) awaitCond(bob.stateName == SHUTDOWN) - test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)) + withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer))) } } - test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - 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._ + 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") { case (_, bob, _, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) - sender.expectMsg("ok") - val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) - } + test("recv CMD_FULFILL_HTLC") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) + sender.expectMsg("ok") + val fulfill = bob2alice.expectMsgType[UpdateFulfillHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fulfill)))) } - test("recv CMD_FULFILL_HTLC (unknown htlc id)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FULFILL_HTLC(42, "12" * 32)) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } + test("recv CMD_FULFILL_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FULFILL_HTLC(42, "12" * 32)) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) } - test("recv CMD_FULFILL_HTLC (invalid preimage)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FULFILL_HTLC(1, "00" * 32)) - sender.expectMsg(Failure(InvalidHtlcPreimage(channelId(bob), 1))) - assert(initialState == bob.stateData) - } + test("recv CMD_FULFILL_HTLC (invalid preimage)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FULFILL_HTLC(1, "00" * 32)) + sender.expectMsg(Failure(InvalidHtlcPreimage(channelId(bob), 1))) + assert(initialState == bob.stateData) } - test("recv UpdateFulfillHtlc") { case (alice, _, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val fulfill = UpdateFulfillHtlc("00" * 32, 0, "11" * 32) - sender.send(alice, fulfill) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))) - } + test("recv UpdateFulfillHtlc") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + val fulfill = UpdateFulfillHtlc("00" * 32, 0, "11" * 32) + sender.send(alice, fulfill) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))) } - test("recv UpdateFulfillHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - val fulfill = UpdateFulfillHtlc("00" * 32, 42, "00" * 32) - sender.send(alice, fulfill) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFulfillHtlc (unknown htlc id)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + val fulfill = UpdateFulfillHtlc("00" * 32, 42, "00" * 32) + sender.send(alice, fulfill) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateFulfillHtlc (invalid preimage)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFulfillHtlc("00" * 32, 42, "00" * 32)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFulfillHtlc (invalid preimage)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFulfillHtlc("00" * 32, 42, "00" * 32)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv CMD_FAIL_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FAIL_HTLC(1, Right(PermanentChannelFailure))) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) - } + test("recv CMD_FAIL_HTLC") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FAIL_HTLC(1, Right(PermanentChannelFailure))) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } - test("recv CMD_FAIL_HTLC (unknown htlc id)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure))) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } + test("recv CMD_FAIL_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FAIL_HTLC(42, Right(PermanentChannelFailure))) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) } - test("recv CMD_FAIL_MALFORMED_HTLC") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION)) - sender.expectMsg("ok") - val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] - awaitCond(bob.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) - } + test("recv CMD_FAIL_MALFORMED_HTLC") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION)) + sender.expectMsg("ok") + val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc] + awaitCond(bob.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fail)))) } - test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, FailureMessageCodecs.BADONION)) - sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) - assert(initialState == bob.stateData) - } + test("recv CMD_FAIL_MALFORMED_HTLC (unknown htlc id)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, FailureMessageCodecs.BADONION)) + sender.expectMsg(Failure(UnknownHtlcId(channelId(bob), 42))) + assert(initialState == bob.stateData) } - test("recv CMD_FAIL_HTLC (invalid failure_code)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, 42)) - sender.expectMsg(Failure(InvalidFailureCode(channelId(bob)))) - assert(initialState == bob.stateData) - } + test("recv CMD_FAIL_HTLC (invalid failure_code)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_FAIL_MALFORMED_HTLC(42, "00" * 32, 42)) + sender.expectMsg(Failure(InvalidFailureCode(channelId(bob)))) + assert(initialState == bob.stateData) } - test("recv UpdateFailHtlc") { case (alice, _, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val fail = UpdateFailHtlc("00" * 32, 1, "00" * 152) - sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) - } + test("recv UpdateFailHtlc") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + val fail = UpdateFailHtlc("00" * 32, 1, "00" * 152) + sender.send(alice, fail) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) } - test("recv UpdateFailHtlc (unknown htlc id)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFailHtlc("00" * 32, 42, "00" * 152)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFailHtlc (unknown htlc id)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFailHtlc("00" * 32, 42, "00" * 152)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateFailMalformedHtlc") { case (alice, _, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION) - sender.send(alice, fail) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) - } + test("recv UpdateFailMalformedHtlc") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), FailureMessageCodecs.BADONION) + sender.send(alice, fail) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments == initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))) } - test("recv UpdateFailMalformedHtlc (invalid failure_code)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), 42) - sender.send(alice, fail) - val error = alice2bob.expectMsgType[Error] - assert(new String(error.data) === InvalidFailureCode("00" * 32).getMessage) - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFailMalformedHtlc (invalid failure_code)") { f => + import f._ + val sender = TestProbe() + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val fail = UpdateFailMalformedHtlc("00" * 32, 1, Crypto.sha256(BinaryData.empty), 42) + sender.send(alice, fail) + val error = alice2bob.expectMsgType[Error] + assert(new String(error.data) === InvalidFailureCode("00" * 32).getMessage) + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv CMD_SIGN") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - // we need to have something to sign so we first send a fulfill and acknowledge (=sign) it - sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) - bob2alice.expectMsgType[UpdateFulfillHtlc] - bob2alice.forward(alice) - sender.send(bob, CMD_SIGN) - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - alice2bob.forward(bob) - sender.send(alice, CMD_SIGN) - sender.expectMsg("ok") - alice2bob.expectMsgType[CommitSig] - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) - } + test("recv CMD_SIGN") { f => + import f._ + val sender = TestProbe() + // we need to have something to sign so we first send a fulfill and acknowledge (=sign) it + sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) + bob2alice.expectMsgType[UpdateFulfillHtlc] + bob2alice.forward(alice) + sender.send(bob, CMD_SIGN) + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] + alice2bob.forward(bob) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[CommitSig] + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) } - test("recv CMD_SIGN (no changes)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(alice, CMD_SIGN) - sender.expectNoMsg(1 second) // just ignored - //sender.expectMsg("cannot sign when there are no changes") - } + test("recv CMD_SIGN (no changes)") { f => + import f._ + val sender = TestProbe() + sender.send(alice, CMD_SIGN) + sender.expectNoMsg(1 second) // just ignored + //sender.expectMsg("cannot sign when there are no changes") } - test("recv CMD_SIGN (while waiting for RevokeAndAck)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) - sender.expectMsg("ok") - bob2alice.expectMsgType[UpdateFulfillHtlc] - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - bob2alice.expectMsgType[CommitSig] - awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) - val waitForRevocation = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.left.toOption.get - assert(waitForRevocation.reSignAsap === false) + test("recv CMD_SIGN (while waiting for RevokeAndAck)") { f => + import f._ + val sender = TestProbe() + sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateFulfillHtlc] + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + bob2alice.expectMsgType[CommitSig] + awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) + val waitForRevocation = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.left.toOption.get + assert(waitForRevocation.reSignAsap === false) - // actual test starts here - sender.send(bob, CMD_SIGN) - sender.expectNoMsg(300 millis) - assert(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo === Left(waitForRevocation)) - } + // actual test starts here + sender.send(bob, CMD_SIGN) + sender.expectNoMsg(300 millis) + assert(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo === Left(waitForRevocation)) } - test("recv CommitSig") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) - sender.expectMsg("ok") - bob2alice.expectMsgType[UpdateFulfillHtlc] - bob2alice.forward(alice) - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - } + test("recv CommitSig") { f => + import f._ + val sender = TestProbe() + sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateFulfillHtlc] + bob2alice.forward(alice) + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] } - test("recv CommitSig (no changes)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - // signature is invalid but it doesn't matter - sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) - bob2alice.expectMsgType[Error] - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv CommitSig (no changes)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + // signature is invalid but it doesn't matter + sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) + bob2alice.expectMsgType[Error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv CommitSig (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) - bob2alice.expectMsgType[Error] - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv CommitSig (invalid signature)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, CommitSig("00" * 32, "00" * 64, Nil)) + bob2alice.expectMsgType[Error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv RevokeAndAck (with remaining htlcs on both sides)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) - // this will cause alice and bob to receive RevokeAndAcks - crossSign(bob, alice, bob2alice, alice2bob) - // actual test starts here - assert(alice.stateName == SHUTDOWN) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.size == 1) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 1) - } + test("recv RevokeAndAck (with remaining htlcs on both sides)") { f => + import f._ + fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) + // this will cause alice and bob to receive RevokeAndAcks + crossSign(bob, alice, bob2alice, alice2bob) + // actual test starts here + assert(alice.stateName == SHUTDOWN) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.size == 1) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 1) } - test("recv RevokeAndAck (with remaining htlcs on one side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) - fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) - val sender = TestProbe() - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - // actual test starts here - bob2alice.forward(bob) - assert(alice.stateName == SHUTDOWN) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.isEmpty) - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 2) - } + test("recv RevokeAndAck (with remaining htlcs on one side)") { f => + import f._ + fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) + fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) + val sender = TestProbe() + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] + // actual test starts here + bob2alice.forward(bob) + assert(alice.stateName == SHUTDOWN) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.spec.htlcs.isEmpty) + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteCommit.spec.htlcs.size == 2) } - test("recv RevokeAndAck (no more htlcs on either side)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) - fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) - crossSign(bob, alice, bob2alice, alice2bob) - // actual test starts here - awaitCond(alice.stateName == NEGOTIATING) - } + test("recv RevokeAndAck (no more htlcs on either side)") { f => + import f._ + fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) + fulfillHtlc(1, "22" * 32, bob, alice, bob2alice, alice2bob) + crossSign(bob, alice, bob2alice, alice2bob) + // actual test starts here + awaitCond(alice.stateName == NEGOTIATING) } - test("recv RevokeAndAck (invalid preimage)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) - sender.expectMsg("ok") - bob2alice.expectMsgType[UpdateFulfillHtlc] - bob2alice.forward(alice) - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) - sender.send(bob, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) - bob2alice.expectMsgType[Error] - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[PublishAsap] // htlc success - bob2blockchain.expectMsgType[PublishAsap] // htlc delayed - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv RevokeAndAck (invalid preimage)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, CMD_FULFILL_HTLC(0, "11" * 32)) + sender.expectMsg("ok") + bob2alice.expectMsgType[UpdateFulfillHtlc] + bob2alice.forward(alice) + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] + awaitCond(bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isLeft) + sender.send(bob, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) + bob2alice.expectMsgType[Error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[PublishAsap] // htlc success + bob2blockchain.expectMsgType[PublishAsap] // htlc delayed + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv RevokeAndAck (unexpectedly)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isRight) - sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv RevokeAndAck (unexpectedly)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + awaitCond(alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.remoteNextCommitInfo.isRight) + sender.send(alice, RevokeAndAck("00" * 32, Scalar("11" * 32), Scalar("22" * 32).toPoint)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv CMD_UPDATE_FEE") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(alice, CMD_UPDATE_FEE(20000)) - sender.expectMsg("ok") - val fee = alice2bob.expectMsgType[UpdateFee] - awaitCond(alice.stateData == initialState.copy( - commitments = initialState.commitments.copy( - localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) - } + test("recv CMD_UPDATE_FEE") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(alice, CMD_UPDATE_FEE(20000)) + sender.expectMsg("ok") + val fee = alice2bob.expectMsgType[UpdateFee] + awaitCond(alice.stateData == initialState.copy( + commitments = initialState.commitments.copy( + localChanges = initialState.commitments.localChanges.copy(initialState.commitments.localChanges.proposed :+ fee)))) } - test("recv CMD_UPDATE_FEE (when fundee)") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(bob, CMD_UPDATE_FEE(20000)) - sender.expectMsg(Failure(FundeeCannotSendUpdateFee(channelId(bob)))) - assert(initialState == bob.stateData) - } + test("recv CMD_UPDATE_FEE (when fundee)") { f => + import f._ + val sender = TestProbe() + val initialState = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(bob, CMD_UPDATE_FEE(20000)) + sender.expectMsg(Failure(FundeeCannotSendUpdateFee(channelId(bob)))) + assert(initialState == bob.stateData) } - test("recv UpdateFee") { case (_, bob, _, _, _, _, _) => - within(30 seconds) { - val initialData = bob.stateData.asInstanceOf[DATA_SHUTDOWN] - val fee = UpdateFee("00" * 32, 12000) - bob ! fee - awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)))) - } + test("recv UpdateFee") { f => + import f._ + val initialData = bob.stateData.asInstanceOf[DATA_SHUTDOWN] + val fee = UpdateFee("00" * 32, 12000) + bob ! fee + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(remoteChanges = initialData.commitments.remoteChanges.copy(proposed = initialData.commitments.remoteChanges.proposed :+ fee)))) } - test("recv UpdateFee (when sender is not funder)") { case (alice, _, alice2bob, _, alice2blockchain, _, _) => - within(30 seconds) { - val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(alice, UpdateFee("00" * 32, 12000)) - alice2bob.expectMsgType[Error] - awaitCond(alice.stateName == CLOSING) - alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - alice2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFee (when sender is not funder)") { f => + import f._ + val tx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(alice, UpdateFee("00" * 32, 12000)) + alice2bob.expectMsgType[Error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(PublishAsap(tx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + alice2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateFee (sender can't afford it)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - val fee = UpdateFee("00" * 32, 100000000) - // we first update the global variable so that we don't trigger a 'fee too different' error - Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) - sender.send(bob, fee) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - //bob2blockchain.expectMsgType[PublishAsap] // main delayed (removed because of the high fees) - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFee (sender can't afford it)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + val fee = UpdateFee("00" * 32, 100000000) + // we first update the global variable so that we don't trigger a 'fee too different' error + Globals.feeratesPerKw.set(FeeratesPerKw.single(fee.feeratePerKw)) + sender.send(bob, fee) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === CannotAffordFees(channelId(bob), missingSatoshis = 72120000L, reserveSatoshis = 20000L, feesSatoshis = 72400000L).getMessage) + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + //bob2blockchain.expectMsgType[PublishAsap] // main delayed (removed because of the high fees) + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateFee (local/remote feerates are too different)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, UpdateFee("00" * 32, 65000)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=10000") - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFee (local/remote feerates are too different)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, UpdateFee("00" * 32, 65000)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === "local/remote feerates are too different: remoteFeeratePerKw=65000 localFeeratePerKw=10000") + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv UpdateFee (remote feerate is too small)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - val sender = TestProbe() - sender.send(bob, UpdateFee("00" * 32, 252)) - val error = bob2alice.expectMsgType[Error] - assert(new String(error.data) === "remote fee rate is too small: remoteFeeratePerKw=252") - awaitCond(bob.stateName == CLOSING) - bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - } + test("recv UpdateFee (remote feerate is too small)") { f => + import f._ + val tx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + val sender = TestProbe() + sender.send(bob, UpdateFee("00" * 32, 252)) + val error = bob2alice.expectMsgType[Error] + assert(new String(error.data) === "remote fee rate is too small: remoteFeeratePerKw=252") + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(PublishAsap(tx)) // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] } - test("recv CurrentBlockCount (no htlc timed out)") { case (alice, bob, alice2bob, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - sender.send(alice, CurrentBlockCount(400143)) - awaitCond(alice.stateData == initialState) - } + test("recv CurrentBlockCount (no htlc timed out)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + sender.send(alice, CurrentBlockCount(400143)) + awaitCond(alice.stateData == initialState) } - test("recv CurrentBlockCount (an htlc timed out)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val aliceCommitTx = initialState.commitments.localCommit.publishableTxs.commitTx.tx - sender.send(alice, CurrentBlockCount(400145)) - alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx - alice2blockchain.expectMsgType[PublishAsap] // main delayed - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 - alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 - val watch = alice2blockchain.expectMsgType[WatchConfirmed] - assert(watch.event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) - } + test("recv CurrentBlockCount (an htlc timed out)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + val aliceCommitTx = initialState.commitments.localCommit.publishableTxs.commitTx.tx + sender.send(alice, CurrentBlockCount(400145)) + alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) // commit tx + alice2blockchain.expectMsgType[PublishAsap] // main delayed + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc timeout 2 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 1 + alice2blockchain.expectMsgType[PublishAsap] // htlc delayed 2 + val watch = alice2blockchain.expectMsgType[WatchConfirmed] + assert(watch.event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) } - test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] - val event = CurrentFeerates(FeeratesPerKw.single(20000)) - sender.send(alice, event) - alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) - } + test("recv CurrentFeerate (when funder, triggers an UpdateFee)") { f => + import f._ + val sender = TestProbe() + val initialState = alice.stateData.asInstanceOf[DATA_SHUTDOWN] + val event = CurrentFeerates(FeeratesPerKw.single(20000)) + sender.send(alice, event) + alice2bob.expectMsg(UpdateFee(initialState.commitments.channelId, event.feeratesPerKw.blocks_2)) } - test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { case (alice, _, alice2bob, _, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(10010)) - sender.send(alice, event) - alice2bob.expectNoMsg(500 millis) - } + test("recv CurrentFeerate (when funder, doesn't trigger an UpdateFee)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(10010)) + sender.send(alice, event) + alice2bob.expectNoMsg(500 millis) } - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { case (_, bob, _, bob2alice, _, _, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(11000)) - sender.send(bob, event) - bob2alice.expectNoMsg(500 millis) - } + test("recv CurrentFeerate (when fundee, commit-fee/network-fee are close)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(11000)) + sender.send(bob, event) + bob2alice.expectNoMsg(500 millis) } - test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { case (_, bob, _, bob2alice, _, bob2blockchain, _) => - within(30 seconds) { - val sender = TestProbe() - val event = CurrentFeerates(FeeratesPerKw.single(1000)) - sender.send(bob, event) - bob2alice.expectMsgType[Error] - bob2blockchain.expectMsgType[PublishAsap] // commit tx - bob2blockchain.expectMsgType[PublishAsap] // main delayed - bob2blockchain.expectMsgType[WatchConfirmed] - awaitCond(bob.stateName == CLOSING) - } + test("recv CurrentFeerate (when fundee, commit-fee/network-fee are very different)") { f => + import f._ + val sender = TestProbe() + val event = CurrentFeerates(FeeratesPerKw.single(1000)) + sender.send(bob, event) + bob2alice.expectMsgType[Error] + bob2blockchain.expectMsgType[PublishAsap] // commit tx + bob2blockchain.expectMsgType[PublishAsap] // main delayed + bob2blockchain.expectMsgType[WatchConfirmed] + awaitCond(bob.stateName == CLOSING) } - test("recv BITCOIN_FUNDING_SPENT (their commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - // bob publishes his current commit tx, which contains two pending htlcs alice->bob - val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - assert(bobCommitTx.txOut.size == 4) // two main outputs and 2 pending htlcs - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) + test("recv BITCOIN_FUNDING_SPENT (their commit)") { f => + import f._ + // bob publishes his current commit tx, which contains two pending htlcs alice->bob + val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + assert(bobCommitTx.txOut.size == 4) // two main outputs and 2 pending htlcs + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) - // in response to that, alice publishes its claim txes - val claimTxes = for (i <- 0 until 3) yield alice2blockchain.expectMsgType[PublishAsap].tx - // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage - val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { - assert(claimHtlcTx.txIn.size == 1) - assert(claimHtlcTx.txOut.size == 1) - Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - claimHtlcTx.txOut(0).amount - }).sum - // htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat = 1000000 - 200000 = 800000 (because fees) - assert(amountClaimed == Satoshi(774010)) + // in response to that, alice publishes its claim txes + val claimTxes = for (i <- 0 until 3) yield alice2blockchain.expectMsgType[PublishAsap].tx + // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage + val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { + assert(claimHtlcTx.txIn.size == 1) + assert(claimHtlcTx.txOut.size == 1) + Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + claimHtlcTx.txOut(0).amount + }).sum + // htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat = 1000000 - 200000 = 800000 (because fees) + assert(amountClaimed == Satoshi(774010)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) - } + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.isDefined) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].remoteCommitPublished.get.claimHtlcTimeoutTxs.size == 2) } - test("recv BITCOIN_FUNDING_SPENT (their next commit)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - // bob fulfills the first htlc - fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) - // then signs - val sender = TestProbe() - sender.send(bob, CMD_SIGN) - sender.expectMsg("ok") - bob2alice.expectMsgType[CommitSig] - bob2alice.forward(alice) - alice2bob.expectMsgType[RevokeAndAck] - alice2bob.forward(bob) - alice2bob.expectMsgType[CommitSig] - alice2bob.forward(bob) - bob2alice.expectMsgType[RevokeAndAck] - // we intercept bob's revocation - // as far as alice knows, bob currently has two valid unrevoked commitment transactions + test("recv BITCOIN_FUNDING_SPENT (their next commit)") { f => + import f._ + // bob fulfills the first htlc + fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) + // then signs + val sender = TestProbe() + sender.send(bob, CMD_SIGN) + sender.expectMsg("ok") + bob2alice.expectMsgType[CommitSig] + bob2alice.forward(alice) + alice2bob.expectMsgType[RevokeAndAck] + alice2bob.forward(bob) + alice2bob.expectMsgType[CommitSig] + alice2bob.forward(bob) + bob2alice.expectMsgType[RevokeAndAck] + // we intercept bob's revocation + // as far as alice knows, bob currently has two valid unrevoked commitment transactions - // bob publishes his current commit tx, which contains one pending htlc alice->bob - val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - assert(bobCommitTx.txOut.size == 3) // two main outputs and 1 pending htlc - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) + // bob publishes his current commit tx, which contains one pending htlc alice->bob + val bobCommitTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + assert(bobCommitTx.txOut.size == 3) // two main outputs and 1 pending htlc + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx) - // in response to that, alice publishes its claim txes - val claimTxes = for (i <- 0 until 2) yield alice2blockchain.expectMsgType[PublishAsap].tx - // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage - val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { - assert(claimHtlcTx.txIn.size == 1) - assert(claimHtlcTx.txOut.size == 1) - Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - claimHtlcTx.txOut(0).amount - }).sum - // htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat - htlc1 = 1000000 - 200000 - 300 000 = 500000 (because fees) - assert(amountClaimed == Satoshi(481190)) + // in response to that, alice publishes its claim txes + val claimTxes = for (i <- 0 until 2) yield alice2blockchain.expectMsgType[PublishAsap].tx + // in addition to its main output, alice can only claim 2 out of 3 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage + val amountClaimed = (for (claimHtlcTx <- claimTxes) yield { + assert(claimHtlcTx.txIn.size == 1) + assert(claimHtlcTx.txOut.size == 1) + Transaction.correctlySpends(claimHtlcTx, bobCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + claimHtlcTx.txOut(0).amount + }).sum + // htlc will timeout and be eventually refunded so we have a little less than fundingSatoshis - pushMsat - htlc1 = 1000000 - 200000 - 300 000 = 500000 (because fees) + assert(amountClaimed == Satoshi(481190)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(bobCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxes(0))) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.isDefined) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcTimeoutTxs.size == 1) - } + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.isDefined) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcSuccessTxs.size == 0) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].nextRemoteCommitPublished.get.claimHtlcTimeoutTxs.size == 1) } - test("recv BITCOIN_FUNDING_SPENT (revoked tx)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _, _) => - within(30 seconds) { - val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - // two main outputs + 2 htlc - assert(revokedTx.txOut.size == 4) + test("recv BITCOIN_FUNDING_SPENT (revoked tx)") { f => + import f._ + val revokedTx = bob.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + // two main outputs + 2 htlc + assert(revokedTx.txOut.size == 4) - // bob fulfills one of the pending htlc (just so that he can have a new sig) - fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) - // bob and alice sign - crossSign(bob, alice, bob2alice, alice2bob) - // bob now has a new commitment tx + // bob fulfills one of the pending htlc (just so that he can have a new sig) + fulfillHtlc(0, "11" * 32, bob, alice, bob2alice, alice2bob) + // bob and alice sign + crossSign(bob, alice, bob2alice, alice2bob) + // bob now has a new commitment tx - // bob published the revoked tx - alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) - alice2bob.expectMsgType[Error] + // bob published the revoked tx + alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx) + alice2bob.expectMsgType[Error] - val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx - val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx -// val htlc1PenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx -// val htlc2PenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty -// assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // htlc1-penalty -// assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // htlc2-penalty - alice2blockchain.expectNoMsg(1 second) + val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx + val mainPenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx +// val htlc1PenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx +// val htlc2PenaltyTx = alice2blockchain.expectMsgType[PublishAsap].tx + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(revokedTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event == BITCOIN_TX_CONFIRMED(mainTx)) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // main-penalty +// assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // htlc1-penalty +// assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) // htlc2-penalty + alice2blockchain.expectNoMsg(1 second) - Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) -// Transaction.correctlySpends(htlc1PenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) -// Transaction.correctlySpends(htlc2PenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(mainTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(mainPenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) +// Transaction.correctlySpends(htlc1PenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) +// Transaction.correctlySpends(htlc2PenaltyTx, Seq(revokedTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - // two main outputs are 300 000 and 200 000, htlcs are 300 000 and 200 000 - assert(mainTx.txOut(0).amount == Satoshi(284930)) - assert(mainPenaltyTx.txOut(0).amount == Satoshi(195150)) -// assert(htlc1PenaltyTx.txOut(0).amount == Satoshi(194530)) -// assert(htlc2PenaltyTx.txOut(0).amount == Satoshi(294530)) + // two main outputs are 300 000 and 200 000, htlcs are 300 000 and 200 000 + assert(mainTx.txOut(0).amount == Satoshi(284930)) + assert(mainPenaltyTx.txOut(0).amount == Satoshi(195150)) +// assert(htlc1PenaltyTx.txOut(0).amount == Satoshi(194530)) +// assert(htlc2PenaltyTx.txOut(0).amount == Satoshi(294530)) - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) - } + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].revokedCommitPublished.size == 1) } - 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 aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx - alice ! Error("00" * 32, "oops".getBytes) - alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) - assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs + test("recv Error") { f => + import f._ + val aliceCommitTx = alice.stateData.asInstanceOf[DATA_SHUTDOWN].commitments.localCommit.publishableTxs.commitTx.tx + alice ! Error("00" * 32, "oops".getBytes) + alice2blockchain.expectMsg(PublishAsap(aliceCommitTx)) + assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs - // alice can claim both htlc after a timeout - // so we expect 5 transactions: - // - 1 tx to claim the main delayed output - // - 2 txes for each htlc - // - 2 txes for each delayed output of the claimed htlc - val claimTxs = for (i <- 0 until 5) yield alice2blockchain.expectMsgType[PublishAsap].tx + // alice can claim both htlc after a timeout + // so we expect 5 transactions: + // - 1 tx to claim the main delayed output + // - 2 txes for each htlc + // - 2 txes for each delayed output of the claimed htlc + val claimTxs = for (i <- 0 until 5) yield alice2blockchain.expectMsgType[PublishAsap].tx - // the main delayed output spends the commitment transaction - Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + // the main delayed output spends the commitment transaction + Transaction.correctlySpends(claimTxs(0), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - // 2nd stage transactions spend the commitment transaction - Transaction.correctlySpends(claimTxs(1), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(2), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + // 2nd stage transactions spend the commitment transaction + Transaction.correctlySpends(claimTxs(1), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(2), aliceCommitTx :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - // 3rd stage transactions spend their respective HTLC-Timeout transactions - Transaction.correctlySpends(claimTxs(3), claimTxs(1) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - Transaction.correctlySpends(claimTxs(4), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + // 3rd stage transactions spend their respective HTLC-Timeout transactions + Transaction.correctlySpends(claimTxs(3), claimTxs(1) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) + Transaction.correctlySpends(claimTxs(4), claimTxs(2) :: Nil, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(0))) // main-delayed - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(3))) // htlc-delayed - assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(4))) // htlc-delayed - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) - alice2blockchain.expectNoMsg(1 second) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(aliceCommitTx)) + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(0))) // main-delayed + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(3))) // htlc-delayed + assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_TX_CONFIRMED(claimTxs(4))) // htlc-delayed + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + assert(alice2blockchain.expectMsgType[WatchSpent].event === BITCOIN_OUTPUT_SPENT) + alice2blockchain.expectNoMsg(1 second) - awaitCond(alice.stateName == CLOSING) - assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) - } + awaitCond(alice.stateName == CLOSING) + assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala index 82bfd400d..7de370327 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala index 48976c53c..c1707edab 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/h/ClosingStateSpec.scala @@ -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)))) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/BitStreamSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/BitStreamSpec.scala index 645397122..403643bba 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/BitStreamSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/BitStreamSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305Spec.scala index cd4a8a2bc..151a8439c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305Spec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/GeneratorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/GeneratorsSpec.scala index 43fc0ed63..9fca57a80 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/GeneratorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/GeneratorsSpec.scala @@ -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") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala index cb7078161..1fcde60be 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/LocalKeyManagerSpec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseDemo.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseDemo.scala index 8abbb9ad7..b59c1f916 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseDemo.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseDemo.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala index 15e68a5e8..d68862417 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ShaChainSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ShaChainSpec.scala index e292a23b8..a9f7c83aa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ShaChainSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/ShaChainSpec.scala @@ -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", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala index cf8c56b80..b5c21c40c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/TransportHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/TransportHandlerSpec.scala index 569eb9da5..f6144b7c9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/TransportHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/TransportHandlerSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala index b4852ce2a..a169779a4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelStateSpec.scala @@ -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) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala index 99692dfea..4e220ce32 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteAuditDbSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala index 37da86819..9e9b511ac 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteChannelsDbSpec.scala @@ -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) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala index 8ee6d99dc..bd76ef09b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala index 52211208e..aaa4ea61b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePaymentsDbSpec.scala @@ -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:") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePeersDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePeersDbSpec.scala index cc81fa625..09461f6a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePeersDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePeersDbSpec.scala @@ -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:") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePendingRelayDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePendingRelayDbSpec.scala index cb4d975dc..8653dcd16 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePendingRelayDbSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqlitePendingRelayDbSpec.scala @@ -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:") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index bbaf501c5..a0e76e7bc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -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) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala index ddee3188b..5a3ea6c0d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/interop/rustytests/RustyTestsSpec.scala @@ -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)}*/ } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala index 7c2520e79..8b02a58e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/NodeURISpec.scala @@ -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" diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala index b56d3d416..347d86a4b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala @@ -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")) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala index 0b6fdfb6a..fed98ada0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/HtlcGenerationSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala index 991517cbc..1ca28af3c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentHandlerSpec.scala @@ -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") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala index 05d279d43..a5165fc86 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentLifecycleSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala index 58a08b185..de00748fd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentRequestSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala index a71cbcbbe..7535d6163 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/RelayerSpec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala index d4ec6d5a2..9e96a810d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsBatchValidationSpec.scala @@ -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) } \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala index a82c2a13b..d74f1aa4c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/AnnouncementsSpec.scala @@ -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)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index d74fe59a5..73e1acf56 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -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))) } } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesExSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesExSpec.scala index cf440caf6..4cd6345fd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesExSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesExSpec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala index 78fe9a72d..4e8845072 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/ChannelRangeQueriesSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/PruningSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/PruningSpec.scala index a332ae774..40d71ac06 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/PruningSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/PruningSpec.scala @@ -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._ diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala index d12c75388..0cf6f43db 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouteCalculationSpec.scala @@ -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]) = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala index 7368a6085..58a7ddcc7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RouterSpec.scala @@ -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)) + } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncExSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncExSpec.scala index d9fc5d0f9..a017241d7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncExSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncExSpec.scala @@ -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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala index 7c4fdb05f..ff5c861dc 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/RoutingSyncSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimReceivedHtlcSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimReceivedHtlcSpec.scala index 0f4365488..2040980a6 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimReceivedHtlcSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimReceivedHtlcSpec.scala @@ -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 { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimSentHtlcSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimSentHtlcSpec.scala index 3203d3b86..e0dff133d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimSentHtlcSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/ClaimSentHtlcSpec.scala @@ -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 { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala index d3bfc6ad4..02dcf93a8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/CommitmentSpecSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index e1458713c..1bf7e63e5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -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) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index 1b6056cc1..e8f2d537e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -25,9 +25,8 @@ import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.transactions.Scripts.{htlcOffered, htlcReceived, toLocalDelayed} import fr.acinq.eclair.transactions.Transactions.{addSigs, _} import fr.acinq.eclair.wire.UpdateAddHtlc -import org.junit.runner.RunWith +import grizzled.slf4j.Logging import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner import scala.io.Source import scala.util.{Failure, Random, Success, Try} @@ -35,8 +34,8 @@ import scala.util.{Failure, Random, Success, Try} /** * Created by PM on 16/12/2016. */ -@RunWith(classOf[JUnitRunner]) -class TransactionsSpec extends FunSuite { + +class TransactionsSpec extends FunSuite with Logging { test("encode/decode sequence and locktime (one example)") { @@ -360,7 +359,7 @@ class TransactionsSpec extends FunSuite { assert(tests.size === 15, "there were 15 tests at ec99f893f320e8c88f564c1c8566f3454f0f1f5f") tests.foreach(test => { - println(s"running BOLT 2 test: '${test.name}'") + logger.info(s"running BOLT 2 test: '${test.name}'") val fee = commitTxFee(test.dustLimit, test.spec) assert(fee === test.expectedFee) }) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala index a4628e800..40d328c3e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/ChannelCodecsSpec.scala @@ -24,16 +24,14 @@ import fr.acinq.eclair.payment.{Local, Relayed} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.ChannelCodecs._ import fr.acinq.eclair.{UInt64, randomKey} -import org.junit.runner.RunWith import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner import scala.util.Random /** * Created by PM on 31/05/2016. */ -@RunWith(classOf[JUnitRunner]) + class ChannelCodecsSpec extends FunSuite { def randomBytes(size: Int): BinaryData = { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/CommandCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/CommandCodecsSpec.scala index dcf4356ea..e3d617d79 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/CommandCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/CommandCodecsSpec.scala @@ -18,14 +18,12 @@ package fr.acinq.eclair.wire import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FAIL_MALFORMED_HTLC, CMD_FULFILL_HTLC, Command} import fr.acinq.eclair.randomBytes -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 CommandCodecsSpec extends FunSuite { test("encode/decode all channel messages") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala index a76c2c409..ecc4c0ba8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/FailureMessageCodecsSpec.scala @@ -18,9 +18,7 @@ package fr.acinq.eclair.wire import fr.acinq.bitcoin.{BinaryData, Block} import fr.acinq.eclair.ShortChannelId -import org.junit.runner.RunWith import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner import scodec.bits.BitVector import scala.util.Random @@ -28,7 +26,7 @@ import scala.util.Random /** * Created by PM on 31/05/2016. */ -@RunWith(classOf[JUnitRunner]) + class FailureMessageCodecsSpec extends FunSuite { val channelUpdate = ChannelUpdate( signature = BinaryData("3045022100c451cd65c88f55b1767941a247e849e12f5f4d4a93a07316659e22f5267d2088022009042a595c6bc8942cd9d729317b82b306edc259fb6b3a3cecb3dd1bd446e90601"), @@ -36,10 +34,12 @@ class FailureMessageCodecsSpec extends FunSuite { shortChannelId = ShortChannelId(12345), timestamp = 1234567L, cltvExpiryDelta = 100, - flags = BinaryData("0001"), + messageFlags = 0, + channelFlags = 1, htlcMinimumMsat = 1000, feeBaseMsat = 12, - feeProportionalMillionths = 76) + feeProportionalMillionths = 76, + htlcMaximumMsat = None) def randomBytes(size: Int): BinaryData = { val bin = new Array[Byte](size) @@ -53,7 +53,7 @@ class FailureMessageCodecsSpec extends FunSuite { InvalidOnionVersion(randomBytes(32)) :: InvalidOnionHmac(randomBytes(32)) :: InvalidOnionKey(randomBytes(32)) :: TemporaryChannelFailure(channelUpdate) :: PermanentChannelFailure :: RequiredChannelFeatureMissing :: UnknownNextPeer :: AmountBelowMinimum(123456, channelUpdate) :: FeeInsufficient(546463, channelUpdate) :: IncorrectCltvExpiry(1211, channelUpdate) :: ExpiryTooSoon(channelUpdate) :: - UnknownPaymentHash :: IncorrectPaymentAmount :: FinalExpiryTooSoon :: FinalIncorrectCltvExpiry(1234) :: ChannelDisabled(BinaryData("0101"), channelUpdate) :: ExpiryTooFar :: Nil + UnknownPaymentHash :: IncorrectPaymentAmount :: FinalExpiryTooSoon :: FinalIncorrectCltvExpiry(1234) :: ChannelDisabled(0, 1, channelUpdate) :: ExpiryTooFar :: Nil msgs.foreach { case msg => { @@ -67,7 +67,7 @@ class FailureMessageCodecsSpec extends FunSuite { test("support encoding of channel_update with/without type in failure messages") { val tmp_channel_failure_notype = BinaryData("10070080cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0003000e00000000000003e80000000100000001") val tmp_channel_failure_withtype = BinaryData("100700820102cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b08e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f45782196fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000008260500041300005b91b52f0003000e00000000000003e80000000100000001") - val ref = TemporaryChannelFailure(ChannelUpdate(BinaryData("3045022100cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b022008e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f457821901"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759, BinaryData("0003"), 14, 1000, 1, 1)) + val ref = TemporaryChannelFailure(ChannelUpdate(BinaryData("3045022100cc3e80149073ed487c76e48e9622bf980f78267b8a34a3f61921f2d8fce6063b022008e74f34a073a13f2097337e4915bb4c001f3b5c4d81e9524ed575e1f457821901"), Block.LivenetGenesisBlock.hash, ShortChannelId(0x826050004130000L), 1536275759, 0, 3, 14, 1000, 1, 1, None)) val u = FailureMessageCodecs.failureMessageCodec.decode(BitVector.apply(tmp_channel_failure_notype.data)).require.value assert(u === ref) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index 3be0e8f9a..27999f1fa 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -19,20 +19,19 @@ package fr.acinq.eclair.wire import java.net.{Inet4Address, Inet6Address, InetAddress} import com.google.common.net.InetAddresses -import fr.acinq.bitcoin.Crypto.{PrivateKey, Scalar} +import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, Scalar} import fr.acinq.bitcoin.{BinaryData, Block, Crypto} import fr.acinq.eclair.crypto.Sphinx +import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.wire.LightningMessageCodecs._ import fr.acinq.eclair.{ShortChannelId, UInt64, randomBytes, randomKey} -import org.junit.runner.RunWith import org.scalatest.FunSuite -import org.scalatest.junit.JUnitRunner import scodec.bits.{BitVector, ByteVector, HexStringSyntax} /** * Created by PM on 31/05/2016. */ -@RunWith(classOf[JUnitRunner]) + class LightningMessageCodecsSpec extends FunSuite { import LightningMessageCodecsSpec._ @@ -193,7 +192,7 @@ class LightningMessageCodecsSpec extends FunSuite { //BinaryData("a483677744b63d892a85fb7460fd6cb0504f802600956eb18cfaad05fbbe775328e4a7060476d2c0f3b7a6d505bb4de9377a55b27d1477baf14c367287c3de7900005abb440002dc523b9db431de52d7adb79cf81dd3d780002f4ce952706053edc9da30d9b9f702dc5256495247494e41574f4c460000000000000000000000000000000000000000000016031bb5481aa82769f4446e1002260701584473f82607"), //BinaryData("3ecfd85bcb3bafb5bad14ab7f6323a2df33e161c37c2897e576762fa90ffe46078d231ebbf7dce3eff4b440d091a10ea9d092e698a321bb9c6b30869e2782c9900005abbebe202dc523b9db431de52d7adb79cf81dd3d780002f4ce952706053edc9da30d9b9f702dc5256495247494e41574f4c460000000000000000000000000000000000000000000016031bb5481aa82769f4446e1002260701584473f82607"), //BinaryData("ad40baf5c7151777cc8896bc70ad2d0fd2afff47f4befb3883a78911b781a829441382d82625b77a47b9c2c71d201aab7187a6dc80e7d2d036dcb1186bac273c00005abffc330341f5ff2992997613aff5675d6796232a63ab7f30136219774da8aba431df37c80341f563377a6763723364776d777a7a3261652e6f6e696f6e00000000000000000000000f0317f2614763b32d9ce804fc002607") - ) + ) anns.foreach { ann => val bin = ByteVector(ann.data.toArray).toBitVector @@ -221,7 +220,7 @@ class LightningMessageCodecsSpec extends FunSuite { val revoke_and_ack = RevokeAndAck(randomBytes(32), scalar(0), point(1)) val channel_announcement = ChannelAnnouncement(randomSignature, randomSignature, randomSignature, randomSignature, bin(7, 9), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey) val node_announcement = NodeAnnouncement(randomSignature, bin(0, 0), 1, randomKey.publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil) - val channel_update = ChannelUpdate(randomSignature, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, bin(2, 2), 3, 4, 5, 6) + val channel_update = ChannelUpdate(randomSignature, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, 42, 0, 3, 4, 5, 6, None) val announcement_signatures = AnnouncementSignatures(randomBytes(32), ShortChannelId(42), randomSignature, randomSignature) val gossip_timestamp_filter = GossipTimestampFilter(Block.RegtestGenesisBlock.blockId, 100000, 1500) val query_short_channel_id = QueryShortChannelIds(Block.RegtestGenesisBlock.blockId, randomBytes(7515)) @@ -267,12 +266,13 @@ class LightningMessageCodecsSpec extends FunSuite { val revoke_and_ack = RevokeAndAck(randomBytes(32), scalar(0), point(1)) val channel_announcement = ChannelAnnouncement(randomSignature, randomSignature, randomSignature, randomSignature, bin(7, 9), Block.RegtestGenesisBlock.hash, ShortChannelId(1), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey) val node_announcement = NodeAnnouncement(randomSignature, bin(0, 0), 1, randomKey.publicKey, Color(100.toByte, 200.toByte, 300.toByte), "node-alias", IPv4(InetAddress.getByAddress(Array[Byte](192.toByte, 168.toByte, 1.toByte, 42.toByte)).asInstanceOf[Inet4Address], 42000) :: Nil) - val channel_update = ChannelUpdate(randomSignature, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, bin(2, 2), 3, 4, 5, 6) + val channel_update1 = ChannelUpdate(randomSignature, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, 1, 0, 3, 4, 5, 6, Some(50000000L)) + val channel_update2 = ChannelUpdate(randomSignature, Block.RegtestGenesisBlock.hash, ShortChannelId(1), 2, 0, 0, 3, 4, 5, 6, None) val announcement_signatures = AnnouncementSignatures(randomBytes(32), ShortChannelId(42), randomSignature, randomSignature) val ping = Ping(100, BinaryData("01" * 10)) val pong = Pong(BinaryData("01" * 10)) - val cached = channel_announcement :: node_announcement :: channel_update :: Nil + val cached = channel_announcement :: node_announcement :: channel_update1 :: channel_update2 :: Nil val nonCached = commit_sig :: revoke_and_ack :: announcement_signatures :: ping :: pong :: Nil val msgs: List[LightningMessage] = cached ::: nonCached @@ -291,6 +291,17 @@ class LightningMessageCodecsSpec extends FunSuite { } + test("decode channel_update with htlc_maximum_msat") { + // this was generated by c-lightning + val bin = BinaryData("010258fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf1792306226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0005a100000200005bc75919010100060000000000000001000000010000000a000000003a699d00") + val update = LightningMessageCodecs.lightningMessageCodec.decode(BitVector(bin.toArray)).require.value.asInstanceOf[ChannelUpdate] + assert(update === ChannelUpdate("3044022058fff7d0e987e2cdd560e3bb5a046b4efe7b26c969c2f51da1dceec7bcb8ae1b0220634790503d5290c1a6c51d681cf8f4211d27ed33a257dcc1102862571bf1792301", "06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f", ShortChannelId(0x5a10000020000L), 1539791129, 1, 1, 6, 1, 1, 10, Some(980000000L))) + val nodeId = PublicKey("03370c9bac836e557eb4f017fe8f9cc047f44db39c1c4e410ff0f7be142b817ae4") + assert(Announcements.checkSig(update, nodeId)) + val bin2 = BinaryData(LightningMessageCodecs.lightningMessageCodec.encode(update).require.toByteArray) + assert(bin === bin2) + } + } object LightningMessageCodecsSpec { diff --git a/eclair-node/pom.xml b/eclair-node/pom.xml index 5fdde316e..5f4b7e336 100644 --- a/eclair-node/pom.xml +++ b/eclair-node/pom.xml @@ -21,7 +21,7 @@ fr.acinq.eclair eclair_2.11 - 0.2-android-beta12 + 0.2-android-SNAPSHOT eclair-node_2.11 diff --git a/eclair-node/src/main/resources/logback.xml b/eclair-node/src/main/resources/logback.xml index 9d669c208..59f3871af 100644 --- a/eclair-node/src/main/resources/logback.xml +++ b/eclair-node/src/main/resources/logback.xml @@ -25,9 +25,15 @@ - + ${eclair.datadir:-${user.home}/.eclair}/eclair.log - true + + + ${eclair.datadir:-${user.home}/.eclair}/eclair.%d{yyyy-MM-dd}.log + + 90 + 5GB + %d %-5level %logger{24} %X{nodeId}%X{channelId} - %msg%ex{24}%n @@ -47,7 +53,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 78aba6a99..8037b6665 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ fr.acinq.eclair eclair_2.11 - 0.2-android-beta12 + 0.2-android-SNAPSHOT pom @@ -113,7 +113,7 @@ net.alchim31.maven scala-maven-plugin - 3.3.1 + 3.4.2 -deprecation @@ -192,26 +192,35 @@ + org.apache.maven.plugins maven-surefire-plugin 2.20 - -Xmx1024m - false - false - - - - **/*Spec.* - **/*Test.* - **/*Suite.* - - - ${project.build.directory} - + true + + + org.scalatest + scalatest-maven-plugin + 2.0.0 + + false + + ${project.build.directory} + + + + + test + + test + + + + @@ -228,16 +237,10 @@ scala-library ${scala.version} - - junit - junit - 4.12 - test - org.scalatest scalatest_${scala.version.short} - 2.2.6 + 3.0.5 test