mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 22:25:26 +01:00
Relay fail message *after* it is cross-signed (#754)
We were previously handling `UpdateFailHtlc` and `UpdateFailMalformedHtlc` similarly to `UpdateFulfillHtlc`, but that is wrong: - a fulfill needs to be propagated as soon as possible, because it allows us to pull funds from upstream - a fail needs to be cross-signed downstream (=irrevocably confirmed) before forwarding it upstream, because it means that we won't be able to pull funds anymore. In other words we need to be absolutely sure that the htlc won't be fulfilled downstream if we fail it upstream, otherwise we risk losing money. Also added tests.
This commit is contained in:
parent
7f3b101426
commit
8887ac2043
9 changed files with 310 additions and 172 deletions
|
@ -546,7 +546,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(fulfill: UpdateFulfillHtlc, d: DATA_NORMAL) =>
|
||||
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// NB: fulfills must be forwarded to the upstream channel asap, because they allow us to get money
|
||||
// we forward preimages as soon as possible to the upstream channel because it allows us to pull funds
|
||||
relayer ! ForwardFulfill(fulfill, origin, htlc)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
|
@ -571,9 +571,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(fail: UpdateFailHtlc, d: DATA_NORMAL) =>
|
||||
Try(Commitments.receiveFail(d.commitments, fail)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// TODO NB: fails must not be forwarded to the upstream channel before they are signed, because they cancel the incoming payment
|
||||
relayer ! ForwardFail(fail, origin, htlc)
|
||||
case Success(Right((commitments1, _, _))) =>
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
|
@ -581,9 +579,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(fail: UpdateFailMalformedHtlc, d: DATA_NORMAL) =>
|
||||
Try(Commitments.receiveFailMalformed(d.commitments, fail)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// TODO NB: fails must not be forwarded to the upstream channel before they are signed, because they cancel the incoming payment
|
||||
relayer ! ForwardFailMalformed(fail, origin, htlc)
|
||||
case Success(Right((commitments1, _, _))) =>
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
|
@ -657,16 +653,13 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// we received a revocation because we sent a signature
|
||||
// => all our changes have been acked
|
||||
Try(Commitments.receiveRevocation(d.commitments, revocation)) match {
|
||||
case Success(commitments1) =>
|
||||
case Success((commitments1, forwards)) =>
|
||||
cancelTimer(RevocationTimeout.toString)
|
||||
// we forward HTLCs only when they have been committed by both sides
|
||||
// it always happen when we receive a revocation, because, we always sign our changes before they sign them
|
||||
d.commitments.remoteChanges.signed.collect {
|
||||
case htlc: UpdateAddHtlc =>
|
||||
log.debug(s"relaying $htlc")
|
||||
relayer ! ForwardAdd(htlc)
|
||||
}
|
||||
log.debug(s"received a new rev, spec:\n${Commitments.specs2String(commitments1)}")
|
||||
forwards.foreach { forward =>
|
||||
log.debug(s"forwarding {} to relayer", forward)
|
||||
relayer ! forward
|
||||
}
|
||||
if (Commitments.localHasChanges(commitments1) && d.commitments.remoteNextCommitInfo.left.map(_.reSignAsap) == Left(true)) {
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
|
@ -881,6 +874,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
case Event(fulfill: UpdateFulfillHtlc, d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFulfill(d.commitments, fulfill)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// we forward preimages as soon as possible to the upstream channel because it allows us to pull funds
|
||||
relayer ! ForwardFulfill(fulfill, origin, htlc)
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
|
@ -905,9 +899,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(fail: UpdateFailHtlc, d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFail(d.commitments, fail)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// TODO NB: fails must not be forwarded to the upstream channel before they are signed, because they cancel the incoming payment
|
||||
relayer ! ForwardFail(fail, origin, htlc)
|
||||
case Success(Right((commitments1, _, _))) =>
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
|
@ -915,9 +907,7 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
|
||||
case Event(fail: UpdateFailMalformedHtlc, d: DATA_SHUTDOWN) =>
|
||||
Try(Commitments.receiveFailMalformed(d.commitments, fail)) match {
|
||||
case Success(Right((commitments1, origin, htlc))) =>
|
||||
// TODO NB: fails must not be forwarded to the upstream channel before they are signed, because they cancel the incoming payment
|
||||
relayer ! ForwardFailMalformed(fail, origin, htlc)
|
||||
case Success(Right((commitments1, _, _))) =>
|
||||
stay using d.copy(commitments = commitments1)
|
||||
case Success(Left(_)) => stay
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(fail))
|
||||
|
@ -963,28 +953,27 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
}
|
||||
|
||||
case Event(commit: CommitSig, d@DATA_SHUTDOWN(_, localShutdown, remoteShutdown)) =>
|
||||
Try(Commitments.receiveCommit(d.commitments, commit, keyManager)) map {
|
||||
case (commitments1, revocation) =>
|
||||
Try(Commitments.receiveCommit(d.commitments, commit, keyManager)) match {
|
||||
case Success((commitments1, revocation)) =>
|
||||
// we always reply with a revocation
|
||||
log.debug(s"received a new sig:\n${Commitments.specs2String(commitments1)}")
|
||||
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments1))
|
||||
(commitments1, revocation)
|
||||
} match {
|
||||
case Success((commitments1, revocation)) if commitments1.hasNoPendingHtlcs =>
|
||||
if (d.commitments.localParams.isFunder) {
|
||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
||||
val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil
|
||||
if (commitments1.hasNoPendingHtlcs) {
|
||||
if (d.commitments.localParams.isFunder) {
|
||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
||||
val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending revocation :: closingSigned :: Nil
|
||||
} else {
|
||||
// we are fundee, will wait for their closing_signed
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None)) sending revocation
|
||||
}
|
||||
} else {
|
||||
// we are fundee, will wait for their closing_signed
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None)) sending revocation
|
||||
if (Commitments.localHasChanges(commitments1)) {
|
||||
// if we have newly acknowledged changes let's sign them
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
stay using store(d.copy(commitments = commitments1)) sending revocation
|
||||
}
|
||||
case Success((commitments1, revocation)) =>
|
||||
if (Commitments.localHasChanges(commitments1)) {
|
||||
// if we have newly acknowledged changes let's sign them
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
stay using store(d.copy(commitments = commitments1)) sending revocation
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(commit))
|
||||
}
|
||||
|
||||
|
@ -992,30 +981,34 @@ class Channel(val nodeParams: NodeParams, wallet: EclairWallet, remoteNodeId: Pu
|
|||
// we received a revocation because we sent a signature
|
||||
// => all our changes have been acked including the shutdown message
|
||||
Try(Commitments.receiveRevocation(commitments, revocation)) match {
|
||||
case Success(commitments1) if commitments1.hasNoPendingHtlcs =>
|
||||
case Success((commitments1, forwards)) =>
|
||||
cancelTimer(RevocationTimeout.toString)
|
||||
log.debug(s"received a new rev, switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
|
||||
if (d.commitments.localParams.isFunder) {
|
||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
||||
val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned
|
||||
} else {
|
||||
// we are fundee, will wait for their closing_signed
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None))
|
||||
}
|
||||
case Success(commitments1) =>
|
||||
cancelTimer(RevocationTimeout.toString)
|
||||
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
||||
d.commitments.remoteChanges.signed.collect {
|
||||
case htlc: UpdateAddHtlc =>
|
||||
log.debug(s"closing in progress: failing $htlc")
|
||||
self ! CMD_FAIL_HTLC(htlc.id, Right(PermanentChannelFailure), commit = true)
|
||||
}
|
||||
if (Commitments.localHasChanges(commitments1) && d.commitments.remoteNextCommitInfo.left.map(_.reSignAsap) == Left(true)) {
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
log.debug(s"received a new rev, spec:\n${Commitments.specs2String(commitments1)}")
|
||||
stay using store(d.copy(commitments = commitments1))
|
||||
forwards.foreach {
|
||||
case forwardAdd: ForwardAdd =>
|
||||
// BOLT 2: A sending node SHOULD fail to route any HTLC added after it sent shutdown.
|
||||
log.debug(s"closing in progress: failing ${forwardAdd.add}")
|
||||
self ! CMD_FAIL_HTLC(forwardAdd.add.id, Right(PermanentChannelFailure), commit = true)
|
||||
case forward =>
|
||||
log.debug(s"forwarding {} to relayer", forward)
|
||||
relayer ! forward
|
||||
}
|
||||
if (commitments1.hasNoPendingHtlcs) {
|
||||
log.debug(s"switching to NEGOTIATING spec:\n${Commitments.specs2String(commitments1)}")
|
||||
if (d.commitments.localParams.isFunder) {
|
||||
// we are funder, need to initiate the negotiation by sending the first closing_signed
|
||||
val (closingTx, closingSigned) = Closing.makeFirstClosingTx(keyManager, commitments1, localShutdown.scriptPubKey, remoteShutdown.scriptPubKey)
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, List(List(ClosingTxProposed(closingTx.tx, closingSigned))), bestUnpublishedClosingTx_opt = None)) sending closingSigned
|
||||
} else {
|
||||
// we are fundee, will wait for their closing_signed
|
||||
goto(NEGOTIATING) using store(DATA_NEGOTIATING(commitments1, localShutdown, remoteShutdown, closingTxProposed = List(List()), bestUnpublishedClosingTx_opt = None))
|
||||
}
|
||||
} else {
|
||||
if (Commitments.localHasChanges(commitments1) && d.commitments.remoteNextCommitInfo.left.map(_.reSignAsap) == Left(true)) {
|
||||
self ! CMD_SIGN
|
||||
}
|
||||
stay using store(d.copy(commitments = commitments1))
|
||||
}
|
||||
case Failure(cause) => handleLocalError(cause, d, Some(revocation))
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import akka.event.LoggingAdapter
|
|||
import fr.acinq.bitcoin.Crypto.{Point, PrivateKey, sha256}
|
||||
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.payment._
|
||||
import fr.acinq.eclair.transactions.Transactions._
|
||||
import fr.acinq.eclair.transactions._
|
||||
import fr.acinq.eclair.wire._
|
||||
|
@ -455,30 +455,47 @@ object Commitments {
|
|||
publishableTxs = PublishableTxs(signedCommitTx, htlcTxsAndSigs))
|
||||
val ourChanges1 = localChanges.copy(acked = Nil)
|
||||
val theirChanges1 = remoteChanges.copy(proposed = Nil, acked = remoteChanges.acked ++ remoteChanges.proposed)
|
||||
// the outgoing following htlcs have been completed (fulfilled or failed) when we received this sig
|
||||
val completedOutgoingHtlcs = commitments.localCommit.spec.htlcs.filter(_.direction == OUT).map(_.add.id) -- localCommit1.spec.htlcs.filter(_.direction == OUT).map(_.add.id)
|
||||
// we remove the newly completed htlcs from the origin map
|
||||
val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs
|
||||
val commitments1 = commitments.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1, originChannels = originChannels1)
|
||||
val commitments1 = commitments.copy(localCommit = localCommit1, localChanges = ourChanges1, remoteChanges = theirChanges1)
|
||||
|
||||
(commitments1, revocation)
|
||||
}
|
||||
|
||||
def receiveRevocation(commitments: Commitments, revocation: RevokeAndAck): Commitments = {
|
||||
def receiveRevocation(commitments: Commitments, revocation: RevokeAndAck): (Commitments, Seq[ForwardMessage]) = {
|
||||
import commitments._
|
||||
// we receive a revocation because we just sent them a sig for their next commit tx
|
||||
remoteNextCommitInfo match {
|
||||
case Left(_) if revocation.perCommitmentSecret.toPoint != remoteCommit.remotePerCommitmentPoint =>
|
||||
throw InvalidRevocation(commitments.channelId)
|
||||
case Left(WaitingForRevocation(theirNextCommit, _, _, _)) =>
|
||||
val forwards = commitments.remoteChanges.signed collect {
|
||||
// we forward adds downstream only when they have been committed by both sides
|
||||
// it always happen when we receive a revocation, because they send the add, then they sign it, then we sign it
|
||||
case add: UpdateAddHtlc => ForwardAdd(add)
|
||||
// same for fails: we need to make sure that they are in neither commitment before propagating the fail upstream
|
||||
case fail: UpdateFailHtlc =>
|
||||
val origin = commitments.originChannels(fail.id)
|
||||
val add = commitments.remoteCommit.spec.htlcs.find(p => p.direction == IN && p.add.id == fail.id).map(_.add).get
|
||||
ForwardFail(fail, origin, add)
|
||||
// same as above
|
||||
case fail: UpdateFailMalformedHtlc =>
|
||||
val origin = commitments.originChannels(fail.id)
|
||||
val add = commitments.remoteCommit.spec.htlcs.find(p => p.direction == IN && p.add.id == fail.id).map(_.add).get
|
||||
ForwardFailMalformed(fail, origin, add)
|
||||
}
|
||||
// the outgoing following htlcs have been completed (fulfilled or failed) when we received this revocation
|
||||
// they have been removed from both local and remote commitment
|
||||
// (since fulfill/fail are sent by remote, they are (1) signed by them, (2) revoked by us, (3) signed by us, (4) revoked by them
|
||||
val completedOutgoingHtlcs = commitments.remoteCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id) -- theirNextCommit.spec.htlcs.filter(_.direction == IN).map(_.add.id)
|
||||
// we remove the newly completed htlcs from the origin map
|
||||
val originChannels1 = commitments.originChannels -- completedOutgoingHtlcs
|
||||
val commitments1 = commitments.copy(
|
||||
localChanges = localChanges.copy(signed = Nil, acked = localChanges.acked ++ localChanges.signed),
|
||||
remoteChanges = remoteChanges.copy(signed = Nil),
|
||||
remoteCommit = theirNextCommit,
|
||||
remoteNextCommitInfo = Right(revocation.nextPerCommitmentPoint),
|
||||
remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index))
|
||||
|
||||
commitments1
|
||||
remotePerCommitmentSecrets = commitments.remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret, 0xFFFFFFFFFFFFL - commitments.remoteCommit.index),
|
||||
originChannels = originChannels1)
|
||||
(commitments1, forwards)
|
||||
case Right(_) =>
|
||||
throw UnexpectedRevocation(commitments.channelId)
|
||||
}
|
||||
|
|
|
@ -39,10 +39,11 @@ sealed trait Origin
|
|||
case class Local(sender: Option[ActorRef]) extends Origin // we don't persist reference to local actors
|
||||
case class Relayed(originChannelId: BinaryData, originHtlcId: Long, amountMsatIn: Long, amountMsatOut: Long) extends Origin
|
||||
|
||||
case class ForwardAdd(add: UpdateAddHtlc, canRedirect: Boolean = true)
|
||||
case class ForwardFulfill(fulfill: UpdateFulfillHtlc, to: Origin, htlc: UpdateAddHtlc)
|
||||
case class ForwardFail(fail: UpdateFailHtlc, to: Origin, htlc: UpdateAddHtlc)
|
||||
case class ForwardFailMalformed(fail: UpdateFailMalformedHtlc, to: Origin, htlc: UpdateAddHtlc)
|
||||
sealed trait ForwardMessage
|
||||
case class ForwardAdd(add: UpdateAddHtlc, canRedirect: Boolean = true) extends ForwardMessage
|
||||
case class ForwardFulfill(fulfill: UpdateFulfillHtlc, to: Origin, htlc: UpdateAddHtlc) extends ForwardMessage
|
||||
case class ForwardFail(fail: UpdateFailHtlc, to: Origin, htlc: UpdateAddHtlc) extends ForwardMessage
|
||||
case class ForwardFailMalformed(fail: UpdateFailMalformedHtlc, to: Origin, htlc: UpdateAddHtlc) extends ForwardMessage
|
||||
|
||||
// @formatter:on
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package fr.acinq.eclair.channel.states
|
||||
|
||||
import akka.actor.Actor
|
||||
import akka.testkit.{TestFSMRef, TestKitBase, TestProbe}
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
|
@ -37,43 +38,38 @@ trait StateTestsHelperMethods extends TestKitBase {
|
|||
|
||||
def defaultOnion: BinaryData = "00" * Sphinx.PacketLength
|
||||
|
||||
case class Setup(alice: TestFSMRef[State, Data, Channel],
|
||||
case class SetupFixture(alice: TestFSMRef[State, Data, Channel],
|
||||
bob: TestFSMRef[State, Data, Channel],
|
||||
alice2bob: TestProbe,
|
||||
bob2alice: TestProbe,
|
||||
alice2blockchain: TestProbe,
|
||||
bob2blockchain: TestProbe,
|
||||
router: TestProbe,
|
||||
relayer: TestProbe)
|
||||
relayerA: TestProbe,
|
||||
relayerB: TestProbe,
|
||||
channelUpdateListener: TestProbe)
|
||||
|
||||
def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams): Setup = {
|
||||
def init(nodeParamsA: NodeParams = TestConstants.Alice.nodeParams, nodeParamsB: NodeParams = TestConstants.Bob.nodeParams): SetupFixture = {
|
||||
Globals.feeratesPerKw.set(FeeratesPerKw.single(TestConstants.feeratePerKw))
|
||||
val alice2bob = TestProbe()
|
||||
val bob2alice = TestProbe()
|
||||
val alice2blockchain = TestProbe()
|
||||
val bob2blockchain = TestProbe()
|
||||
val relayer = TestProbe()
|
||||
system.eventStream.subscribe(relayer.ref, classOf[LocalChannelUpdate])
|
||||
system.eventStream.subscribe(relayer.ref, classOf[LocalChannelDown])
|
||||
// hacky... publish a fake update and wait till we've received it
|
||||
// val fakeUpdate = LocalChannelUpdate(probe.ref, BinaryData("00" * 32), 0, PrivateKey("01" * 32).publicKey, None, ChannelUpdate(LightningMessageCodecsSpec.randomSignature, Block.RegtestGenesisBlock.hash, 1, 2, BinaryData("0202"), 3, 4, 5, 6))
|
||||
// system.eventStream.publish(fakeUpdate)
|
||||
// probe.expectMsg(fakeUpdate)
|
||||
val relayerA = TestProbe()
|
||||
val relayerB = TestProbe()
|
||||
val channelUpdateListener = TestProbe()
|
||||
system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelUpdate])
|
||||
system.eventStream.subscribe(channelUpdateListener.ref, classOf[LocalChannelDown])
|
||||
val router = TestProbe()
|
||||
val wallet = new TestWallet
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayer.ref))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayer.ref))
|
||||
Setup(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayer)
|
||||
val alice: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsA, wallet, Bob.nodeParams.nodeId, alice2blockchain.ref, router.ref, relayerA.ref))
|
||||
val bob: TestFSMRef[State, Data, Channel] = TestFSMRef(new Channel(nodeParamsB, wallet, Alice.nodeParams.nodeId, bob2blockchain.ref, router.ref, relayerB.ref))
|
||||
SetupFixture(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, router, relayerA, relayerB, channelUpdateListener)
|
||||
}
|
||||
|
||||
def reachNormal(alice: TestFSMRef[State, Data, Channel],
|
||||
bob: TestFSMRef[State, Data, Channel],
|
||||
alice2bob: TestProbe,
|
||||
bob2alice: TestProbe,
|
||||
alice2blockchain: TestProbe,
|
||||
bob2blockchain: TestProbe,
|
||||
relayer: TestProbe,
|
||||
def reachNormal(setup: SetupFixture,
|
||||
tags: Set[String] = Set.empty): Unit = {
|
||||
import setup._
|
||||
val channelFlags = if (tags.contains("channels_public")) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
|
||||
val (aliceParams, bobParams) = (Alice.channelParams, Bob.channelParams)
|
||||
val aliceInit = Init(aliceParams.globalFeatures, aliceParams.localFeatures)
|
||||
|
@ -107,8 +103,8 @@ trait StateTestsHelperMethods extends TestKitBase {
|
|||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
// x2 because alice and bob share the same relayer
|
||||
relayer.expectMsgType[LocalChannelUpdate]
|
||||
relayer.expectMsgType[LocalChannelUpdate]
|
||||
channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
}
|
||||
|
||||
def addHtlc(amountMsat: Int, s: TestFSMRef[State, Data, Channel], r: TestFSMRef[State, Data, Channel], s2r: TestProbe, r2s: TestProbe): (BinaryData, UpdateAddHtlc) = {
|
||||
|
|
|
@ -18,13 +18,15 @@ package fr.acinq.eclair.channel.states.e
|
|||
|
||||
import akka.actor.Status
|
||||
import akka.actor.Status.Failure
|
||||
import akka.testkit.{TestFSMRef, TestProbe}
|
||||
import akka.testkit.TestProbe
|
||||
import fr.acinq.bitcoin.Crypto.Scalar
|
||||
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, ScriptFlags, Transaction}
|
||||
import fr.acinq.eclair.TestConstants.{Alice, Bob}
|
||||
import fr.acinq.eclair.UInt64.Conversions._
|
||||
import fr.acinq.eclair.blockchain._
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||
import fr.acinq.eclair.channel.Channel.TickRefreshChannelUpdate
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.Channel.{RevocationTimeout, TickRefreshChannelUpdate}
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
|
@ -45,16 +47,16 @@ import scala.concurrent.duration._
|
|||
|
||||
class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayer: TestProbe)
|
||||
type FixtureParam = SetupFixture
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val setup = init()
|
||||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer, test.tags)
|
||||
reachNormal(setup, test.tags)
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)))
|
||||
withFixture(test.toNoArgTest(setup))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -295,6 +297,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)))
|
||||
// bob won't forward the add before it is cross-signed
|
||||
relayerB.expectNoMsg()
|
||||
}
|
||||
|
||||
test("recv UpdateAddHtlc (unexpected id)") { f =>
|
||||
|
@ -336,7 +340,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -351,7 +355,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -368,7 +372,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -384,7 +388,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -398,7 +402,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -416,7 +420,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -715,7 +719,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.expectMsgType[Error]
|
||||
awaitCond(bob.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -800,7 +804,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv RevokeAndAck (one htlc received)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
val (_, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
|
||||
sender.send(alice, CMD_SIGN)
|
||||
sender.expectMsg("ok")
|
||||
|
@ -813,10 +817,17 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.expectMsgType[CommitSig]
|
||||
bob2alice.forward(alice)
|
||||
|
||||
// at this point bob still hasn't forwarded the htlc downstream
|
||||
relayerB.expectNoMsg()
|
||||
|
||||
// actual test begins
|
||||
alice2bob.expectMsgType[RevokeAndAck]
|
||||
alice2bob.forward(bob)
|
||||
awaitCond(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.remoteNextCommitInfo.isRight)
|
||||
// now bob will forward the htlc downstream
|
||||
val forward = relayerB.expectMsgType[ForwardAdd]
|
||||
assert(forward.add === htlc)
|
||||
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (multiple htlcs in both directions)") { f =>
|
||||
|
@ -893,7 +904,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -908,12 +919,70 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (forward UpdateFailHtlc)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
val (_, 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]
|
||||
bob2alice.forward(alice)
|
||||
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)
|
||||
// alice still hasn't forwarded the fail because it is not yet cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
|
||||
// actual test begins
|
||||
bob2alice.expectMsgType[RevokeAndAck]
|
||||
bob2alice.forward(alice)
|
||||
// alice will forward the fail upstream
|
||||
val forward = relayerA.expectMsgType[ForwardFail]
|
||||
assert(forward.fail === fail)
|
||||
assert(forward.htlc === htlc)
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (forward UpdateFailMalformedHtlc)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
val (_, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
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)
|
||||
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)
|
||||
// alice still hasn't forwarded the fail because it is not yet cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
|
||||
// actual test begins
|
||||
bob2alice.expectMsgType[RevokeAndAck]
|
||||
bob2alice.forward(alice)
|
||||
// alice will forward the fail upstream
|
||||
val forward = relayerA.expectMsgType[ForwardFailMalformed]
|
||||
assert(forward.fail === fail)
|
||||
assert(forward.htlc === htlc)
|
||||
}
|
||||
|
||||
test("recv RevocationTimeout") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
|
@ -985,6 +1054,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fulfill))))
|
||||
// alice immediately propagates the fulfill upstream
|
||||
val forward = relayerA.expectMsgType[ForwardFulfill]
|
||||
assert(forward.fulfill === fulfill)
|
||||
assert(forward.htlc === htlc)
|
||||
}
|
||||
|
||||
test("recv UpdateFulfillHtlc (sender has not signed htlc)") { f =>
|
||||
|
@ -1001,7 +1074,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1015,7 +1088,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1026,15 +1099,15 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
relayerB.expectMsgType[ForwardAdd]
|
||||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap] // main delayed
|
||||
alice2blockchain.expectMsgType[PublishAsap] // htlc timeout
|
||||
|
@ -1106,9 +1179,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
test("recv UpdateFailHtlc") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
val (_, 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]
|
||||
|
@ -1118,6 +1190,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))))
|
||||
// alice won't forward the fail before it is cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
}
|
||||
|
||||
test("recv UpdateFailMalformedHtlc") { f =>
|
||||
|
@ -1125,9 +1199,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
val sender = TestProbe()
|
||||
|
||||
// Alice sends an HTLC to Bob, which they both sign
|
||||
val (r, htlc) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
val (_, 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))
|
||||
|
@ -1137,6 +1210,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
|
||||
awaitCond(alice.stateData == initialState.copy(
|
||||
commitments = initialState.commitments.copy(remoteChanges = initialState.commitments.remoteChanges.copy(initialState.commitments.remoteChanges.proposed :+ fail))))
|
||||
// alice won't forward the fail before it is cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
|
||||
sender.send(bob, CMD_SIGN)
|
||||
val sig = bob2alice.expectMsgType[CommitSig]
|
||||
|
@ -1182,7 +1257,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1196,7 +1271,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1264,7 +1339,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Error]
|
||||
awaitCond(alice.stateName == CLOSING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
alice2blockchain.expectMsg(PublishAsap(tx))
|
||||
alice2blockchain.expectMsgType[PublishAsap]
|
||||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1282,7 +1357,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.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]
|
||||
|
@ -1297,7 +1372,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1312,7 +1387,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === bob.stateData.asInstanceOf[DATA_CLOSING].channelId)
|
||||
bob2blockchain.expectMsg(PublishAsap(tx))
|
||||
bob2blockchain.expectMsgType[PublishAsap]
|
||||
bob2blockchain.expectMsgType[WatchConfirmed]
|
||||
|
@ -1326,10 +1401,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
sender.send(alice, CMD_UPDATE_RELAY_FEE(newFeeBaseMsat, newFeeProportionalMillionth))
|
||||
sender.expectMsg("ok")
|
||||
|
||||
val localUpdate = relayer.expectMsgType[LocalChannelUpdate]
|
||||
val localUpdate = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(localUpdate.channelUpdate.feeBaseMsat == newFeeBaseMsat)
|
||||
assert(localUpdate.channelUpdate.feeProportionalMillionths == newFeeProportionalMillionth)
|
||||
relayer.expectNoMsg(1 seconds)
|
||||
relayerA.expectNoMsg(1 seconds)
|
||||
}
|
||||
|
||||
test("recv CMD_CLOSE (no pending htlcs)") { f =>
|
||||
|
@ -1405,7 +1480,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[ClosingSigned]
|
||||
awaitCond(alice.stateName == NEGOTIATING)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_NEGOTIATING].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_NEGOTIATING].channelId)
|
||||
}
|
||||
|
||||
test("recv Shutdown (with unacked sent htlcs)") { f =>
|
||||
|
@ -1426,7 +1501,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2bob.expectMsgType[Shutdown]
|
||||
awaitCond(alice.stateName == SHUTDOWN)
|
||||
// channel should be advertised as down
|
||||
assert(relayer.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_SHUTDOWN].channelId)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelDown].channelId === alice.stateData.asInstanceOf[DATA_SHUTDOWN].channelId)
|
||||
}
|
||||
|
||||
test("recv Shutdown (with unacked received htlcs)") { f =>
|
||||
|
@ -1910,7 +1985,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
channelUpdateListener.expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_DEEPLYBURIED (short channel id changed)", Tag("channels_public")) { f =>
|
||||
|
@ -1921,8 +1996,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
// 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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId)
|
||||
channelUpdateListener.expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel)") { f =>
|
||||
|
@ -1933,7 +2008,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
channelUpdateListener.expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_DEEPLYBURIED (private channel, short channel id changed)") { f =>
|
||||
|
@ -1944,8 +2019,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].shortChannelId == alice.stateData.asInstanceOf[DATA_NORMAL].shortChannelId)
|
||||
channelUpdateListener.expectNoMsg(1 second)
|
||||
}
|
||||
|
||||
test("recv AnnouncementSignatures", Tag("channels_public")) { f =>
|
||||
|
@ -1964,7 +2039,7 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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))
|
||||
assert(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelAnnouncement_opt === Some(channelAnn))
|
||||
}
|
||||
|
||||
test("recv AnnouncementSignatures (re-send)", Tag("channels_public")) { f =>
|
||||
|
@ -1994,12 +2069,12 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42))
|
||||
bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
bob2alice.forward(alice)
|
||||
val update1 = relayer.expectMsgType[LocalChannelUpdate]
|
||||
val update1 = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
|
||||
// actual test starts here
|
||||
Thread.sleep(1100)
|
||||
sender.send(alice, TickRefreshChannelUpdate)
|
||||
val update2 = relayer.expectMsgType[LocalChannelUpdate]
|
||||
val update2 = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(update1.channelUpdate.timestamp < update2.channelUpdate.timestamp)
|
||||
}
|
||||
|
||||
|
@ -2010,13 +2085,13 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
sender.send(bob, WatchEventConfirmed(BITCOIN_FUNDING_DEEPLYBURIED, 400000, 42))
|
||||
bob2alice.expectMsgType[AnnouncementSignatures]
|
||||
bob2alice.forward(alice)
|
||||
val update1 = relayer.expectMsgType[LocalChannelUpdate]
|
||||
val update1 = channelUpdateListener.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]
|
||||
val update2 = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(update1.channelUpdate.timestamp < update2.channelUpdate.timestamp)
|
||||
assert(Announcements.isEnabled(update2.channelUpdate.channelFlags) == false)
|
||||
awaitCond(alice.stateName == OFFLINE)
|
||||
|
@ -2032,8 +2107,8 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
|
||||
// actual test starts here
|
||||
sender.send(alice, INPUT_DISCONNECTED)
|
||||
assert(relayer.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc1.paymentHash)
|
||||
assert(relayer.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc2.paymentHash)
|
||||
assert(relayerA.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc1.paymentHash)
|
||||
assert(relayerA.expectMsgType[Status.Failure].cause.asInstanceOf[AddHtlcFailed].paymentHash === htlc2.paymentHash)
|
||||
awaitCond(alice.stateName == OFFLINE)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,16 +36,16 @@ import scala.concurrent.duration._
|
|||
|
||||
class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayer: TestProbe)
|
||||
type FixtureParam = SetupFixture
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val setup = init()
|
||||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||
reachNormal(setup)
|
||||
awaitCond(alice.stateName == NORMAL)
|
||||
awaitCond(bob.stateName == NORMAL)
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)))
|
||||
withFixture(test.toNoArgTest(setup))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,15 +337,15 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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)
|
||||
assert(Announcements.isEnabled(channelUpdateListener.expectMsgType[LocalChannelUpdate].channelUpdate.channelFlags) == false)
|
||||
assert(Announcements.isEnabled(channelUpdateListener.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)
|
||||
channelUpdateListener.expectNoMsg(300 millis)
|
||||
|
||||
// then we reconnect them
|
||||
sender.send(alice, INPUT_RECONNECTED(alice2bob.ref, aliceInit, bobInit))
|
||||
|
@ -358,13 +358,13 @@ class OfflineStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
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
|
||||
val channelUpdate = channelUpdateListener.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)
|
||||
channelUpdateListener.expectNoMsg(300 millis)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import fr.acinq.eclair.blockchain._
|
|||
import fr.acinq.eclair.blockchain.fee.FeeratesPerKw
|
||||
import fr.acinq.eclair.channel.states.StateTestsHelperMethods
|
||||
import fr.acinq.eclair.channel.{Data, State, _}
|
||||
import fr.acinq.eclair.payment.{ForwardAdd, Local, PaymentLifecycle}
|
||||
import fr.acinq.eclair.payment._
|
||||
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}
|
||||
|
@ -38,13 +38,13 @@ import scala.concurrent.duration._
|
|||
|
||||
class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayer: TestProbe)
|
||||
type FixtureParam = SetupFixture
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val setup = init()
|
||||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||
reachNormal(setup)
|
||||
val sender = TestProbe()
|
||||
// alice sends an HTLC to bob
|
||||
val r1: BinaryData = "11" * 32
|
||||
|
@ -80,8 +80,8 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.forward(alice)
|
||||
alice2bob.expectMsgType[RevokeAndAck]
|
||||
alice2bob.forward(bob)
|
||||
relayer.expectMsgType[ForwardAdd]
|
||||
relayer.expectMsgType[ForwardAdd]
|
||||
relayerB.expectMsgType[ForwardAdd]
|
||||
relayerB.expectMsgType[ForwardAdd]
|
||||
// alice initiates a closing
|
||||
sender.send(alice, CMD_CLOSE(None))
|
||||
alice2bob.expectMsgType[Shutdown]
|
||||
|
@ -90,7 +90,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
bob2alice.forward(alice)
|
||||
awaitCond(alice.stateName == SHUTDOWN)
|
||||
awaitCond(bob.stateName == SHUTDOWN)
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)))
|
||||
channelUpdateListener.expectMsgType[LocalChannelDown]
|
||||
channelUpdateListener.expectMsgType[LocalChannelDown]
|
||||
withFixture(test.toNoArgTest(setup))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,6 +443,58 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
alice2blockchain.expectMsgType[WatchConfirmed]
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (forward UpdateFailHtlc)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FAIL_HTLC(1, Right(PermanentChannelFailure)))
|
||||
sender.expectMsg("ok")
|
||||
val fail = bob2alice.expectMsgType[UpdateFailHtlc]
|
||||
bob2alice.forward(alice)
|
||||
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)
|
||||
// alice still hasn't forwarded the fail because it is not yet cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
|
||||
// actual test begins
|
||||
bob2alice.expectMsgType[RevokeAndAck]
|
||||
bob2alice.forward(alice)
|
||||
// alice will forward the fail upstream
|
||||
val forward = relayerA.expectMsgType[ForwardFail]
|
||||
assert(forward.fail === fail)
|
||||
}
|
||||
|
||||
test("recv RevokeAndAck (forward UpdateFailMalformedHtlc)") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
sender.send(bob, CMD_FAIL_MALFORMED_HTLC(1, Crypto.sha256("should be htlc.onionRoutingPacket".getBytes()), FailureMessageCodecs.BADONION))
|
||||
sender.expectMsg("ok")
|
||||
val fail = bob2alice.expectMsgType[UpdateFailMalformedHtlc]
|
||||
bob2alice.forward(alice)
|
||||
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)
|
||||
// alice still hasn't forwarded the fail because it is not yet cross-signed
|
||||
relayerA.expectNoMsg()
|
||||
|
||||
// actual test begins
|
||||
bob2alice.expectMsgType[RevokeAndAck]
|
||||
bob2alice.forward(alice)
|
||||
// alice will forward the fail upstream
|
||||
val forward = relayerA.expectMsgType[ForwardFailMalformed]
|
||||
assert(forward.fail === fail)
|
||||
}
|
||||
|
||||
test("recv CMD_UPDATE_FEE") { f =>
|
||||
import f._
|
||||
val sender = TestProbe()
|
||||
|
|
|
@ -40,13 +40,13 @@ import scala.util.Success
|
|||
|
||||
class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe)
|
||||
type FixtureParam = SetupFixture
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val setup = init()
|
||||
import setup._
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||
reachNormal(setup)
|
||||
val sender = TestProbe()
|
||||
// alice initiates a closing
|
||||
if (test.tags.contains("fee2")) Globals.feeratesPerKw.set(FeeratesPerKw.single(4319)) else Globals.feeratesPerKw.set(FeeratesPerKw.single(10000))
|
||||
|
@ -61,7 +61,7 @@ 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)
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)))
|
||||
withFixture(test.toNoArgTest(setup))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,23 +38,25 @@ import scala.concurrent.duration._
|
|||
|
||||
class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
||||
|
||||
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])
|
||||
case class FixtureParam(alice: TestFSMRef[State, Data, Channel], bob: TestFSMRef[State, Data, Channel], alice2bob: TestProbe, bob2alice: TestProbe, alice2blockchain: TestProbe, bob2blockchain: TestProbe, relayerA: TestProbe, relayerB: TestProbe, channelUpdateListener: TestProbe, bobCommitTxes: List[PublishableTxs])
|
||||
|
||||
override def withFixture(test: OneArgTest): Outcome = {
|
||||
val setup = init()
|
||||
import setup._
|
||||
|
||||
within(30 seconds) {
|
||||
reachNormal(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer)
|
||||
reachNormal(setup)
|
||||
val bobCommitTxes: List[PublishableTxs] = (for (amt <- List(100000000, 200000000, 300000000)) yield {
|
||||
val (r, htlc) = addHtlc(amt, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
relayer.expectMsgType[ForwardAdd]
|
||||
relayerB.expectMsgType[ForwardAdd]
|
||||
val bobCommitTx1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs
|
||||
fulfillHtlc(htlc.id, r, bob, alice, bob2alice, alice2bob)
|
||||
relayer.expectMsgType[ForwardFulfill]
|
||||
// alice forwards the fulfill upstream
|
||||
relayerA.expectMsgType[ForwardFulfill]
|
||||
crossSign(bob, alice, bob2alice, alice2bob)
|
||||
relayer.expectMsgType[CommandBuffer.CommandAck]
|
||||
// bob confirms that it has forwarded the fulfill to alice
|
||||
relayerB.expectMsgType[CommandBuffer.CommandAck]
|
||||
val bobCommitTx2 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs
|
||||
bobCommitTx1 :: bobCommitTx2 :: Nil
|
||||
}).flatten
|
||||
|
@ -70,7 +72,7 @@ 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.
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayer, bobCommitTxes)))
|
||||
withFixture(test.toNoArgTest(FixtureParam(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, relayerA, relayerB, channelUpdateListener, bobCommitTxes)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,7 +193,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
// alice sends an htlc to bob
|
||||
val (ra1, htlca1) = addHtlc(50000000, alice, bob, alice2bob, bob2alice)
|
||||
crossSign(alice, bob, alice2bob, bob2alice)
|
||||
relayer.expectMsgType[ForwardAdd]
|
||||
relayerB.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)
|
||||
|
@ -208,17 +210,17 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
assert(initialState.localCommitPublished.isDefined)
|
||||
|
||||
// actual test starts here
|
||||
relayer.expectMsgType[LocalChannelDown]
|
||||
channelUpdateListener.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))
|
||||
assert(relayerA.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(relayerA.expectMsgType[ForwardFulfill].fulfill === UpdateFulfillHtlc(htlca1.channelId, htlca1.id, ra1))
|
||||
|
||||
assert(alice.stateData == initialState) // this was a no-op
|
||||
}
|
||||
|
@ -271,14 +273,14 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
awaitCond(alice.stateName == CLOSING)
|
||||
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
assert(aliceData.localCommitPublished.isDefined)
|
||||
relayer.expectMsgType[LocalChannelDown]
|
||||
channelUpdateListener.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)))
|
||||
relayerA.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)") { f =>
|
||||
|
@ -296,14 +298,14 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
|
|||
awaitCond(alice.stateName == CLOSING)
|
||||
val aliceData = alice.stateData.asInstanceOf[DATA_CLOSING]
|
||||
assert(aliceData.remoteCommitPublished.isDefined)
|
||||
relayer.expectMsgType[LocalChannelDown]
|
||||
channelUpdateListener.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)))
|
||||
relayerA.expectMsg(Status.Failure(AddHtlcFailed(aliceData.channelId, htlc.paymentHash, HtlcOverridenByLocalCommit(aliceData.channelId), origin, None, None)))
|
||||
}
|
||||
|
||||
test("recv BITCOIN_FUNDING_SPENT (remote commit)") { f =>
|
||||
|
|
Loading…
Add table
Reference in a new issue