diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Channel.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Channel.scala index f6cdf18aa..4e5d7b0e0 100644 --- a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Channel.scala +++ b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Channel.scala @@ -12,6 +12,7 @@ import lightning.open_channel.anchor_offer.{WILL_CREATE_ANCHOR, WONT_CREATE_ANCH import scala.util.{Failure, Success, Try} import scala.concurrent.duration._ +import scala.util.control.NonFatal /** * Created by PM on 20/08/2015. @@ -45,7 +46,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann 888 888 Y8888 888 888 8888888 888 Y888 8888888 888 */ - when(OPEN_WAIT_FOR_OPEN_NOANCHOR) { + when(OPEN_WAIT_FOR_OPEN_NOANCHOR)(handleExceptions { case Event(open_channel(delay, theirRevocationHash, theirNextRevocationHash, commitKey, finalKey, WILL_CREATE_ANCHOR, minDepth, initialFeeRate), DATA_OPEN_WAIT_FOR_OPEN(ourParams)) => val theirParams = TheirChannelParams(delay, commitKey, finalKey, minDepth, initialFeeRate) goto(OPEN_WAIT_FOR_ANCHOR) using DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash) @@ -55,9 +56,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAIT_FOR_OPEN_WITHANCHOR) { + when(OPEN_WAIT_FOR_OPEN_WITHANCHOR)(handleExceptions { case Event(open_channel(delay, theirRevocationHash, theirNextRevocationHash, commitKey, finalKey, WONT_CREATE_ANCHOR, minDepth, initialFeeRate), DATA_OPEN_WAIT_FOR_OPEN(ourParams)) => val theirParams = TheirChannelParams(delay, commitKey, finalKey, minDepth, initialFeeRate) log.debug(s"their params: $theirParams") @@ -76,9 +77,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAIT_FOR_ANCHOR) { + when(OPEN_WAIT_FOR_ANCHOR)(handleExceptions { case Event(open_anchor(anchorTxHash, anchorOutputIndex, anchorAmount), DATA_OPEN_WAIT_FOR_ANCHOR(ourParams, theirParams, theirRevocationHash, theirNextRevocationHash)) => val anchorTxid = anchorTxHash.reverse //see https://github.com/ElementsProject/lightning/issues/17 @@ -109,9 +110,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAIT_FOR_COMMIT_SIG) { + when(OPEN_WAIT_FOR_COMMIT_SIG)(handleExceptions { case Event(open_commit_sig(theirSig), DATA_OPEN_WAIT_FOR_COMMIT_SIG(ourParams, theirParams, anchorTx, anchorOutputIndex, theirCommitment, theirNextRevocationHash)) => val anchorAmount = anchorTx.txOut(anchorOutputIndex).amount val theirSpec = theirCommitment.spec @@ -145,9 +146,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAITING_THEIRANCHOR) { + when(OPEN_WAITING_THEIRANCHOR)(handleExceptions { case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) => log.info(s"received their open_complete, deferring message") stay using d.copy(deferred = Some(msg)) @@ -174,9 +175,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAITING_OURANCHOR) { + when(OPEN_WAITING_OURANCHOR)(handleExceptions { case Event(msg@open_complete(blockId_opt), d: DATA_OPEN_WAITING) => log.info(s"received their open_complete, deferring message") stay using d.copy(deferred = Some(msg)) @@ -209,10 +210,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann blockchain ! Publish(d.commitments.ourCommit.publishableTx) blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) + }) - } - - when(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR) { + when(OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR)(handleExceptions { case Event(open_complete(blockid_opt), d: DATA_NORMAL) => Register.create_alias(theirNodeId, d.commitments.anchorId) goto(NORMAL) @@ -228,9 +228,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann goto(CLOSED) case Event(CMD_CLOSE(_), _) => goto(CLOSED) - } + }) - when(OPEN_WAIT_FOR_COMPLETE_OURANCHOR) { + when(OPEN_WAIT_FOR_COMPLETE_OURANCHOR)(handleExceptions { case Event(open_complete(blockid_opt), d: DATA_NORMAL) => Register.create_alias(theirNodeId, d.commitments.anchorId) goto(NORMAL) @@ -252,7 +252,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann blockchain ! Publish(d.commitments.ourCommit.publishableTx) blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) - } + }) /* @@ -267,25 +267,45 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann */ when(NORMAL) { - case Event(CMD_ADD_HTLC(amount, rHash, expiry, nodeIds, origin, id_opt), d@DATA_NORMAL(commitments, htlcIdx, _)) => - // TODO: should we take pending htlcs into account? - // TODO: assert(commitment.state.commit_changes(staged).us.pay_msat >= amount, "insufficient funds!") - // TODO: nodeIds are ignored - val id: Long = id_opt.getOrElse(htlcIdx + 1) - val htlc = update_add_htlc(id, amount, rHash, expiry, routing(ByteString.EMPTY)) - them ! htlc - stay using d.copy(htlcIdx = htlc.id, commitments = commitments.addOurProposal(htlc)) + // TODO : this should probably be done in Commitments.scala + // our available funds as seen by them, including all pending changes + val reduced = reduce(commitments.theirCommit.spec, commitments.theirChanges.acked, commitments.ourChanges.acked ++ commitments.ourChanges.signed ++ commitments.ourChanges.proposed) + // the pending htlcs that we sent to them (seen as IN from their pov) have already been deduced from our balance + val available = reduced.amount_them_msat + reduced.htlcs.filter(_.direction == OUT).map(-_.amountMsat).sum + if (amount > available) { + sender ! s"insufficient funds (available=$available msat)" + stay + } else { + // TODO: nodeIds are ignored + val id: Long = id_opt.getOrElse(htlcIdx + 1) + val htlc = update_add_htlc(id, amount, rHash, expiry, routing(ByteString.EMPTY)) + them ! htlc + sender ! "ok" + stay using d.copy(htlcIdx = htlc.id, commitments = commitments.addOurProposal(htlc)) + } case Event(htlc@update_add_htlc(htlcId, amount, rHash, expiry, nodeIds), d@DATA_NORMAL(commitments, _, _)) => - // TODO: should we take pending htlcs into account? - // assert(commitment.state.commit_changes(staged).them.pay_msat >= amount, "insufficient funds!") // TODO : we should fail the channel - // TODO: nodeIds are ignored - stay using d.copy(commitments = commitments.addTheirProposal(htlc)) + // TODO : this should probably be done in Commitments.scala + // their available funds as seen by us, including all pending changes + val reduced = reduce(commitments.ourCommit.spec, commitments.ourChanges.acked, commitments.theirChanges.acked ++ commitments.theirChanges.proposed) + // the pending htlcs that they sent to us (seen as IN from our pov) have already been deduced from their balance + val available = reduced.amount_them_msat + reduced.htlcs.filter(_.direction == OUT).map(-_.amountMsat).sum + if (amount > available) { + log.error("they sent an htlc but had insufficient funds") + them ! error(Some("Insufficient funds")) + blockchain ! Publish(d.commitments.ourCommit.publishableTx) + blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) + goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) + } else { + // TODO: nodeIds are ignored + stay using d.copy(commitments = commitments.addTheirProposal(htlc)) + } case Event(CMD_FULFILL_HTLC(id, r), d: DATA_NORMAL) => val (commitments1, fullfill) = Commitments.sendFulfill(d.commitments, CMD_FULFILL_HTLC(id, r)) them ! fullfill + sender ! "ok" stay using d.copy(commitments = commitments1) case Event(fulfill@update_fulfill_htlc(id, r), d: DATA_NORMAL) => @@ -300,9 +320,27 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann stay using d.copy(commitments = Commitments.receiveFail(d.commitments, fail)) case Event(CMD_SIGN, d: DATA_NORMAL) => - val (commitments1, commit) = Commitments.sendCommit(d.commitments) - them ! commit - stay using d.copy(commitments = commitments1) + if (d.commitments.theirNextCommitInfo.isLeft) { + sender ! "cannot sign until next revocation hash is received" + stay + } /*else if (d.commitments.ourChanges.proposed.isEmpty) { + //TODO : check this + sender ! "cannot sign when there are no changes" + stay + }*/ else { + Try(Commitments.sendCommit(d.commitments)) match { + case Success((commitments1, commit)) => + them ! commit + sender ! "ok" + stay using d.copy(commitments = commitments1) + case Failure(cause) => + log.error(cause, "") + them ! error(Some("Bad signature")) + blockchain ! Publish(d.commitments.ourCommit.publishableTx) + blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) + goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) + } + } case Event(msg@update_commit(theirSig), d: DATA_NORMAL) => Try(Commitments.receiveCommit(d.commitments, msg)) match { @@ -310,7 +348,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann them ! revocation stay using d.copy(commitments = commitments1) case Failure(cause) => - log.error(cause, "received a bad signature") + log.error(cause, "") them ! error(Some("Bad signature")) blockchain ! Publish(d.commitments.ourCommit.publishableTx) blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) @@ -320,8 +358,16 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann case Event(msg@update_revocation(revocationPreimage, nextRevocationHash), d: DATA_NORMAL) => // we received a revocation because we sent a signature // => all our changes have been acked - // TODO: check preimage - stay using d.copy(commitments = Commitments.receiveRevocation(d.commitments, msg)) + Try(Commitments.receiveRevocation(d.commitments, msg)) match { + case Success(commitments1) => + stay using d.copy(commitments = commitments1) + case Failure(cause) => + log.error(cause, "") + them ! error(Some("Bad revocation")) + blockchain ! Publish(d.commitments.ourCommit.publishableTx) + blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) + goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) + } case Event(theirClearing@close_clearing(theirScriptPubKey), d@DATA_NORMAL(commitments, _, ourClearingOpt)) => val ourClearing: close_clearing = ourClearingOpt.getOrElse { @@ -411,7 +457,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann stay using d.copy(commitments = commitments1) } case Failure(cause) => - log.error(cause, "received a bad signature") + log.error(cause, "") them ! error(Some("Bad signature")) blockchain ! Publish(d.commitments.ourCommit.publishableTx) blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) @@ -479,7 +525,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann stay() case Event(close_signature(theirCloseFee, theirSig), d: DATA_CLOSING) => - throw new RuntimeException(s"unexpected closing fee: $theirCloseFee ours is ${d.ourSignature.map(_.closeFee)}") + throw new RuntimeException(s"unexpected closing fee: $theirCloseFee ours is ${ + d.ourSignature.map(_.closeFee) + }") case Event(BITCOIN_CLOSE_DONE, _) => goto(CLOSED) @@ -528,6 +576,25 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, val params: OurChann } + /** + * This helper function runs the state's default event handlers, and react to exceptions by unilaterally closing the channel + */ + def handleExceptions(s: StateFunction): StateFunction = { + case event => + try { + s(event) + } catch { + case t: Throwable => event.stateData match { + case d: HasCommitments => + blockchain ! Publish(d.commitments.ourCommit.publishableTx) + blockchain ! WatchConfirmed(self, d.commitments.ourCommit.publishableTx.txid, d.commitments.ourParams.minDepth, BITCOIN_CLOSE_DONE) + goto(CLOSING) using DATA_CLOSING(d.commitments, ourCommitPublished = Some(d.commitments.ourCommit.publishableTx)) + case _ => + goto(CLOSED) + } + } + } + } diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala index 2f488332b..98034f397 100644 --- a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala +++ b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala @@ -82,14 +82,14 @@ sealed trait Command /** * * - * @param amount + * @param amountMsat * @param rHash * @param expiry * @param nodeIds * @param originChannelId * @param id should only be provided in tests otherwise it will be assigned automatically */ -final case class CMD_ADD_HTLC(amount: Int, rHash: sha256_hash, expiry: locktime, nodeIds: Seq[String] = Seq.empty[String], originChannelId: Option[BinaryData] = None, id: Option[Long] = None) extends Command +final case class CMD_ADD_HTLC(amountMsat: Int, rHash: sha256_hash, expiry: locktime, nodeIds: Seq[String] = Seq.empty[String], originChannelId: Option[BinaryData] = None, id: Option[Long] = None) extends Command final case class CMD_FULFILL_HTLC(id: Long, r: sha256_hash) extends Command final case class CMD_FAIL_HTLC(id: Long, reason: String) extends Command case object CMD_SIGN extends Command @@ -143,23 +143,27 @@ case class TheirCommit(index: Long, spec: CommitmentSpec, theirRevocationHash: s final case class ClosingData(ourScriptPubKey: BinaryData, theirScriptPubKey: Option[BinaryData]) +trait HasCommitments { + def commitments: Commitments +} + final case class DATA_OPEN_WAIT_FOR_OPEN (ourParams: OurChannelParams) extends Data final case class DATA_OPEN_WITH_ANCHOR_WAIT_FOR_ANCHOR(ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: BinaryData, theirNextRevocationHash: sha256_hash) extends Data final case class DATA_OPEN_WAIT_FOR_ANCHOR (ourParams: OurChannelParams, theirParams: TheirChannelParams, theirRevocationHash: sha256_hash, theirNextRevocationHash: sha256_hash) extends Data final case class DATA_OPEN_WAIT_FOR_COMMIT_SIG (ourParams: OurChannelParams, theirParams: TheirChannelParams, anchorTx: Transaction, anchorOutputIndex: Int, initialCommitment: TheirCommit, theirNextRevocationHash: sha256_hash) extends Data -final case class DATA_OPEN_WAITING (commitments: Commitments, deferred: Option[open_complete]) extends Data +final case class DATA_OPEN_WAITING (commitments: Commitments, deferred: Option[open_complete]) extends Data with HasCommitments final case class DATA_NORMAL (commitments: Commitments, htlcIdx: Long, - ourClearing: Option[close_clearing]) extends Data + ourClearing: Option[close_clearing]) extends Data with HasCommitments final case class DATA_CLEARING (commitments: Commitments, htlcIdx: Long, - ourClearing: close_clearing, theirClearing: close_clearing) extends Data + ourClearing: close_clearing, theirClearing: close_clearing) extends Data with HasCommitments final case class DATA_NEGOCIATING (commitments: Commitments, htlcIdx: Long, - ourClearing: close_clearing, theirClearing: close_clearing, ourSignature: close_signature) extends Data + ourClearing: close_clearing, theirClearing: close_clearing, ourSignature: close_signature) extends Data with HasCommitments final case class DATA_CLOSING (commitments: Commitments, ourSignature: Option[close_signature] = None, mutualClosePublished: Option[Transaction] = None, ourCommitPublished: Option[Transaction] = None, theirCommitPublished: Option[Transaction] = None, - revokedPublished: Seq[Transaction] = Seq()) extends Data { + revokedPublished: Seq[Transaction] = Seq()) extends Data with HasCommitments { assert(mutualClosePublished.isDefined || ourCommitPublished.isDefined || theirCommitPublished.isDefined || revokedPublished.size > 0, "there should be at least one tx published in this state") } diff --git a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 92ca610ba..777feca5e 100644 --- a/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-demo/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -113,6 +113,10 @@ object Commitments { // we will reply to this sig with our old revocation hash preimage (at index) and our next revocation hash (at index + 1) // and will increment our index + // check that there have been changes + /*if (commitments.theirChanges.proposed.isEmpty) + throw new RuntimeException("cannot sign if there are no changes")*/ + // check that their signature is valid val spec = Helpers.reduce(ourCommit.spec, ourChanges.acked, theirChanges.acked ++ theirChanges.proposed) val ourNextRevocationHash = Helpers.revocationHash(ourParams.shaSeed, ourCommit.index + 1) @@ -121,7 +125,7 @@ object Commitments { val signedTx = Helpers.addSigs(ourParams, theirParams, anchorOutput.amount, ourTx, ourSig, commit.sig) Helpers.checksig(ourParams, theirParams, anchorOutput, signedTx).get - // we will send our revocation preimage+ our next revocation hash + // we will send our revocation preimage + our next revocation hash val ourRevocationPreimage = Helpers.revocationPreimage(ourParams.shaSeed, ourCommit.index) val ourNextRevocationHash1 = Helpers.revocationHash(ourParams.shaSeed, ourCommit.index + 2) val revocation = update_revocation(ourRevocationPreimage, ourNextRevocationHash1) @@ -138,8 +142,9 @@ object Commitments { import commitments._ // we receive a revocation because we just sent them a sig for their next commit tx theirNextCommitInfo match { + case Left(theirNextCommit) if BinaryData(Crypto.sha256(revocation.revocationPreimage)) != BinaryData(theirCommit.theirRevocationHash) => + throw new RuntimeException("invalid preimage") case Left(theirNextCommit) => - assert(BinaryData(Crypto.sha256(revocation.revocationPreimage)) == BinaryData(theirCommit.theirRevocationHash), "invalid preimage") commitments.copy( ourChanges = ourChanges.copy(signed = Nil, acked = ourChanges.acked ++ ourChanges.signed), theirCommit = theirNextCommit, diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenNoAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenNoAnchorStateSpec.scala similarity index 97% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenNoAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenNoAnchorStateSpec.scala index aea0ae179..31e57fc37 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenNoAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenNoAnchorStateSpec.scala @@ -1,4 +1,4 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.a import akka.actor.ActorSystem import akka.testkit.{TestFSMRef, TestKit, TestProbe} @@ -6,8 +6,8 @@ import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.channel._ import lightning.{error, open_channel} import org.junit.runner.RunWith -import org.scalatest.{BeforeAndAfterAll, fixture} import org.scalatest.junit.JUnitRunner +import org.scalatest.{BeforeAndAfterAll, fixture} import scala.concurrent.duration._ diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenWithAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenWithAnchorStateSpec.scala similarity index 98% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenWithAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenWithAnchorStateSpec.scala index 776654187..72b19d15c 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForOpenWithAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/a/OpenWaitForOpenWithAnchorStateSpec.scala @@ -1,4 +1,4 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.a import akka.actor.{ActorRef, ActorSystem} import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForAnchorStateSpec.scala similarity index 97% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForAnchorStateSpec.scala index 16aa45e92..806ffcd1f 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForAnchorStateSpec.scala @@ -1,4 +1,4 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.b import akka.actor.ActorSystem import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCommitSigStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForCommitSigStateSpec.scala similarity index 95% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCommitSigStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForCommitSigStateSpec.scala index e550d257a..95d2df52f 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCommitSigStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/b/OpenWaitForCommitSigStateSpec.scala @@ -1,11 +1,11 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.b import akka.actor.{ActorRef, ActorSystem} import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} import fr.acinq.eclair.TestBitcoinClient import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.channel.{OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_OPEN_WITHANCHOR, _} +import fr.acinq.eclair.channel.{OPEN_WAITING_OURANCHOR, _} import lightning._ import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingOurAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingOurAnchorStateSpec.scala similarity index 98% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingOurAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingOurAnchorStateSpec.scala index 7e986f8e0..7dcb03bd9 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingOurAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingOurAnchorStateSpec.scala @@ -1,4 +1,4 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.c import akka.actor.{ActorRef, ActorSystem} import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingTheirAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingTheirAnchorStateSpec.scala similarity index 98% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingTheirAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingTheirAnchorStateSpec.scala index aec6aac1a..fab8f7ff2 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitingTheirAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/c/OpenWaitingTheirAnchorStateSpec.scala @@ -1,4 +1,4 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.c import akka.actor.ActorSystem import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteOurAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteOurAnchorStateSpec.scala similarity index 96% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteOurAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteOurAnchorStateSpec.scala index db1c66cba..5a45e5977 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteOurAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteOurAnchorStateSpec.scala @@ -1,11 +1,11 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.d import akka.actor.{ActorRef, ActorSystem} import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} import fr.acinq.eclair.TestBitcoinClient import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain._ -import fr.acinq.eclair.channel.{OPEN_WAITING_OURANCHOR, OPEN_WAIT_FOR_COMPLETE_OURANCHOR, _} +import fr.acinq.eclair.channel.{OPEN_WAIT_FOR_COMPLETE_OURANCHOR, _} import lightning._ import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteTheirAnchorStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteTheirAnchorStateSpec.scala similarity index 94% rename from eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteTheirAnchorStateSpec.scala rename to eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteTheirAnchorStateSpec.scala index 1c6d36212..04b1ebb7a 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/OpenWaitForCompleteTheirAnchorStateSpec.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/d/OpenWaitForCompleteTheirAnchorStateSpec.scala @@ -1,11 +1,11 @@ -package fr.acinq.eclair.channel.simulator +package fr.acinq.eclair.channel.simulator.states.d import akka.actor.ActorSystem import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} import fr.acinq.eclair.TestBitcoinClient import fr.acinq.eclair.TestConstants.{Alice, Bob} import fr.acinq.eclair.blockchain.{PollingWatcher, WatchConfirmed, WatchLost, WatchSpent} -import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, OPEN_WAITING_THEIRANCHOR, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR, _} +import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, OPEN_WAIT_FOR_COMPLETE_THEIRANCHOR, _} import lightning._ import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner @@ -52,7 +52,7 @@ class OpenWaitForCompleteTheirAnchorStateSpec extends TestKit(ActorSystem("test" test("recv open_complete") { case (bob, alice2bob, bob2alice, bob2blockchain) => within(30 seconds) { - val msg = alice2bob.expectMsgType[open_complete] + alice2bob.expectMsgType[open_complete] alice2bob.forward(bob) awaitCond(bob.stateName == NORMAL) } diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/e/NormalStateSpec.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/e/NormalStateSpec.scala new file mode 100644 index 000000000..7ec80884c --- /dev/null +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/channel/simulator/states/e/NormalStateSpec.scala @@ -0,0 +1,345 @@ +package fr.acinq.eclair.channel.simulator.states.e + +import akka.actor.ActorSystem +import akka.testkit.{TestActorRef, TestFSMRef, TestKit, TestProbe} +import com.google.protobuf.ByteString +import fr.acinq.bitcoin.Crypto +import fr.acinq.eclair._ +import fr.acinq.eclair.TestBitcoinClient +import fr.acinq.eclair.TestConstants.{Alice, Bob} +import fr.acinq.eclair.blockchain._ +import fr.acinq.eclair.channel.{BITCOIN_ANCHOR_DEPTHOK, _} +import lightning._ +import lightning.locktime.Locktime.Blocks +import org.junit.runner.RunWith +import org.scalatest.junit.JUnitRunner +import org.scalatest.{BeforeAndAfterAll, fixture} + +import scala.concurrent.duration._ + +/** + * Created by PM on 05/07/2016. + */ +@RunWith(classOf[JUnitRunner]) +class NormalStateSpec extends TestKit(ActorSystem("test")) with fixture.FunSuiteLike with BeforeAndAfterAll { + + type FixtureParam = Tuple6[TestFSMRef[State, Data, Channel], TestFSMRef[State, Data, Channel], TestProbe, TestProbe, TestProbe, TestProbe] + + override def withFixture(test: OneArgTest) = { + val alice2bob = TestProbe() + val bob2alice = TestProbe() + val alice2blockchain = TestProbe() + val blockchainA = TestActorRef(new PollingWatcher(new TestBitcoinClient())) + val bob2blockchain = TestProbe() + val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(alice2bob.ref, alice2blockchain.ref, Alice.channelParams, "B")) + val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(bob2alice.ref, bob2blockchain.ref, Bob.channelParams, "A")) + alice2bob.expectMsgType[open_channel] + alice2bob.forward(bob) + bob2alice.expectMsgType[open_channel] + bob2alice.forward(alice) + alice2blockchain.expectMsgType[MakeAnchor] + alice2blockchain.forward(blockchainA) + alice2bob.expectMsgType[open_anchor] + alice2bob.forward(bob) + bob2alice.expectMsgType[open_commit_sig] + bob2alice.forward(alice) + alice2blockchain.expectMsgType[WatchConfirmed] + alice2blockchain.forward(blockchainA) + alice2blockchain.expectMsgType[WatchSpent] + alice2blockchain.forward(blockchainA) + alice2blockchain.expectMsgType[Publish] + alice2blockchain.forward(blockchainA) + bob2blockchain.expectMsgType[WatchConfirmed] + bob2blockchain.expectMsgType[WatchSpent] + bob ! BITCOIN_ANCHOR_DEPTHOK + bob2blockchain.expectMsgType[WatchLost] + bob2alice.expectMsgType[open_complete] + bob2alice.forward(alice) + alice2blockchain.expectMsgType[WatchLost] + alice2blockchain.forward(blockchainA) + alice2bob.expectMsgType[open_complete] + alice2bob.forward(bob) + awaitCond(alice.stateName == NORMAL) + awaitCond(bob.stateName == NORMAL) + // note : alice is funder and bob is fundee, so alice has all the money + test((alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)) + } + + override def afterAll { + TestKit.shutdownActorSystem(system) + } + + test("recv CMD_ADD_HTLC") { case (alice, _, alice2bob, _, _, _) => + within(30 seconds) { + val h = sha256_hash(1, 2, 3, 4) + alice ! CMD_ADD_HTLC(500000, h, locktime(Blocks(3))) + val htlc = alice2bob.expectMsgType[update_add_htlc] + assert(htlc.id == 1 && htlc.rHash == h) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].htlcIdx == 1 && alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourChanges.proposed == htlc :: Nil) + } + } + + test("recv CMD_ADD_HTLC (insufficient funds)") { case (alice, _, alice2bob, _, _, _) => + within(30 seconds) { + val sender = TestProbe() + sender.send(alice, CMD_ADD_HTLC(Int.MaxValue, sha256_hash(1, 1, 1, 1), locktime(Blocks(3)))) + sender.expectMsg("insufficient funds (available=1000000000 msat)") + } + } + + test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 1/2)") { case (alice, _, alice2bob, _, _, _) => + within(30 seconds) { + val sender = TestProbe() + sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(3)))) + sender.expectMsg("ok") + sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(3)))) + sender.expectMsg("ok") + sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(3)))) + sender.expectMsg("insufficient funds (available=0 msat)") + } + } + + test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs 2/2)") { case (alice, _, alice2bob, _, _, _) => + within(30 seconds) { + val sender = TestProbe() + sender.send(alice, CMD_ADD_HTLC(300000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(3)))) + sender.expectMsg("ok") + sender.send(alice, CMD_ADD_HTLC(300000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(3)))) + sender.expectMsg("ok") + sender.send(alice, CMD_ADD_HTLC(500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(3)))) + sender.expectMsg("insufficient funds (available=400000000 msat)") + } + } + + test("recv update_add_htlc") { case (_, bob, alice2bob, _, _,_) => + within(30 seconds) { + val initialData = bob.stateData.asInstanceOf[DATA_NORMAL] + val htlc = update_add_htlc(42, 150, sha256_hash(1, 2, 3, 4), locktime(Blocks(3)), routing(ByteString.EMPTY)) + bob ! htlc + awaitCond(bob.stateData == initialData.copy(commitments = initialData.commitments.copy(theirChanges = initialData.commitments.theirChanges.copy(proposed = initialData.commitments.theirChanges.proposed :+ htlc)))) + } + } + + test("recv update_add_htlc (insufficient funds)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + val htlc = update_add_htlc(42, Int.MaxValue, sha256_hash(1, 2, 3, 4), locktime(Blocks(3)), routing(ByteString.EMPTY)) + alice2bob.forward(bob, htlc) + bob2alice.expectMsgType[error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(Publish(tx)) + bob2blockchain.expectMsgType[WatchConfirmed] + } + } + + test("recv update_add_htlc (insufficient funds w/ pending htlcs 1/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + alice2bob.forward(bob, update_add_htlc(42, 500000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(3)), routing(ByteString.EMPTY))) + alice2bob.forward(bob, update_add_htlc(43, 500000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(3)), routing(ByteString.EMPTY))) + alice2bob.forward(bob, update_add_htlc(44, 500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(3)), routing(ByteString.EMPTY))) + bob2alice.expectMsgType[error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(Publish(tx)) + bob2blockchain.expectMsgType[WatchConfirmed] + } + } + + test("recv update_add_htlc (insufficient funds w/ pending htlcs 2/2)") { case (_, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + alice2bob.forward(bob, update_add_htlc(42, 300000000, sha256_hash(1, 1, 1, 1), locktime(Blocks(3)), routing(ByteString.EMPTY))) + alice2bob.forward(bob, update_add_htlc(43, 300000000, sha256_hash(2, 2, 2, 2), locktime(Blocks(3)), routing(ByteString.EMPTY))) + alice2bob.forward(bob, update_add_htlc(44, 500000000, sha256_hash(3, 3, 3, 3), locktime(Blocks(3)), routing(ByteString.EMPTY))) + bob2alice.expectMsgType[error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(Publish(tx)) + bob2blockchain.expectMsgType[WatchConfirmed] + } + } + + test("recv CMD_SIGN") { case (alice, bob, alice2bob, bob2alice, _, _) => + within(30 seconds) { + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_add_htlc] + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_commit] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.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.expectMsg("cannot sign when there are no changes") + } + }*/ + + test("recv CMD_SIGN (while waiting for update_revocation)") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) => + within(30 seconds) { + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_add_htlc] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight) + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_commit] + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isLeft) + sender.send(alice, CMD_SIGN) + sender.expectMsg("cannot sign until next revocation hash is received") + } + } + + test("recv update_commit") { case (alice, bob, alice2bob, bob2alice, _, _) => + within(30 seconds) { + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[update_add_htlc] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_commit] + alice2bob.forward(bob) + + bob2alice.expectMsgType[update_revocation] + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.htlcs.exists(h => h.id == htlc.id && h.direction == IN)) + assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.spec.amount_us_msat == initialState.commitments.ourCommit.spec.amount_us_msat) + } + } + + /*test("recv update_commit (no changes)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + val sender = TestProbe() + // signature is invalid but it doesn't matter + sender.send(bob, update_commit(signature(0, 0, 0, 0, 0, 0, 0, 0))) + bob2alice.expectMsgType[error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(Publish(tx)) + bob2blockchain.expectMsgType[WatchConfirmed] + } + }*/ + + test("recv update_commit (invalid signature)") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[update_add_htlc] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil) + val tx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + + sender.send(bob, update_commit(signature(0, 0, 0, 0, 0, 0, 0, 0))) + bob2alice.expectMsgType[error] + awaitCond(bob.stateName == CLOSING) + bob2blockchain.expectMsg(Publish(tx)) + bob2blockchain.expectMsgType[WatchConfirmed] + } + } + + test("recv update_revocation") { case (alice, bob, alice2bob, bob2alice, _, bob2blockchain) => + within(30 seconds) { + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[update_add_htlc] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_commit] + alice2bob.forward(bob) + + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isLeft) + bob2alice.expectMsgType[update_revocation] + bob2alice.forward(alice) + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight) + } + } + + test("recv update_revocation (invalid preimage") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) => + within(30 seconds) { + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[update_add_htlc] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil) + + sender.send(alice, CMD_SIGN) + sender.expectMsg("ok") + alice2bob.expectMsgType[update_commit] + alice2bob.forward(bob) + + bob2alice.expectMsgType[update_revocation] + sender.send(alice, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1))) + alice2bob.expectMsgType[error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(Publish(tx)) + alice2blockchain.expectMsgType[WatchConfirmed] + } + } + + test("recv update_revocation (unexpectedly") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, _) => + within(30 seconds) { + val tx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.ourCommit.publishableTx + val sender = TestProbe() + awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.theirNextCommitInfo.isRight) + sender.send(alice, update_revocation(sha256_hash(0, 0, 0, 0), sha256_hash(1, 1, 1, 1))) + alice2bob.expectMsgType[error] + awaitCond(alice.stateName == CLOSING) + alice2blockchain.expectMsg(Publish(tx)) + alice2blockchain.expectMsgType[WatchConfirmed] + } + } + + + /*test("recv CMD_FULFILL_HTLC") { case (alice, bob, alice2bob, bob2alice, _) => + within(30 seconds) { + val sender = TestProbe() + val r = sha256_hash(1, 2, 3, 4) + val h: sha256_hash = Crypto.sha256(r) + sender.send(alice, CMD_ADD_HTLC(500000, h, locktime(Blocks(3)))) + sender.expectMsg("ok") + val htlc = alice2bob.expectMsgType[update_add_htlc] + alice2bob.forward(bob) + awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.theirChanges.proposed == htlc :: Nil) + val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] + sender.send(bob, CMD_FULFILL_HTLC(htlc.id, r)) + sender.expectMsg("ok") + val fulfill = bob2alice.expectMsgType[update_fulfill_htlc] + awaitCond(bob.stateData == initialState.copy(commitments = initialState.commitments.copy(ourChanges = initialState.commitments.ourChanges.copy(initialState.commitments.ourChanges.proposed :+ fulfill)))) + } + }*/ + +} diff --git a/eclair-demo/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala b/eclair-demo/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala index d91510417..772f2f996 100644 --- a/eclair-demo/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala +++ b/eclair-demo/src/test/scala/fr/acinq/eclair/interop/rustytests/SynchronizationPipe.scala @@ -110,6 +110,7 @@ class SynchronizationPipe(latch: CountDownLatch) extends Actor with ActorLogging import scala.io.Source val script = Source.fromFile(file).getLines().filterNot(_.startsWith("#")).toList exec(script, a, b) + case "ok" => {} case msg if sender() == a => log.info(s"a -> b $msg") b forward msg @@ -120,11 +121,14 @@ class SynchronizationPipe(latch: CountDownLatch) extends Actor with ActorLogging } def wait(a: ActorRef, b: ActorRef, script: List[String]): Receive = { + case "ok" => {} case msg if sender() == a && script.head.startsWith("B:recv") => + log.info(s"a -> b $msg") b forward msg unstashAll() exec(script.drop(1), a, b) case msg if sender() == b && script.head.startsWith("A:recv") => + log.info(s"b -> a $msg") a forward msg unstashAll() exec(script.drop(1), a, b)