1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 14:40:34 +01:00

fixed bug when remote close during opening of the channel

This commit is contained in:
pm47 2017-02-03 19:14:52 +01:00
parent 1b74f1c6cb
commit 3a3b8c6eb1
9 changed files with 44 additions and 29 deletions

View file

@ -75,9 +75,10 @@ class PeerWatcher(client: ExtendedBitcoinClient, blockCount: Long)(implicit ec:
// absolute timeout in blocks
val timeout = Math.max(cltvTimeout, csvTimeout)
if (timeout <= currentBlockCount) {
log.info(s"publishing tx ${Transaction.write(tx)}")
publish(tx)
} else {
log.info(s"delaying publication of tx $tx until block=$timeout (curblock=$currentBlockCount)")
log.info(s"delaying publication of tx ${Transaction.write(tx)} until block=$timeout (curblock=$currentBlockCount)")
val block2tx1 = block2tx.updated(timeout, tx +: block2tx.getOrElse(timeout, Seq.empty[Transaction]))
context.become(watching(watches, block2tx1, currentBlockCount))
}

View file

@ -310,8 +310,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(e: Error, d: DATA_WAIT_FOR_FUNDING_LOCKED_INTERNAL) =>
log.error(s"peer sent $e, closing connection") // see bolt #2: A node MUST fail the connection if it receives an err message
blockchain ! PublishAsap(d.commitments.localCommit.publishableTxs.commitTx.tx)
blockchain ! WatchConfirmed(self, d.commitments.localCommit.publishableTxs.commitTx.tx.txid, d.params.minimumDepth, BITCOIN_CLOSE_DONE)
val localCommitTx = d.commitments.localCommit.publishableTxs.commitTx.tx
blockchain ! PublishAsap(localCommitTx)
blockchain ! WatchConfirmed(self, localCommitTx.txid, d.params.minimumDepth, BITCOIN_LOCALCOMMIT_DONE)
// there can't be htlcs at this stage
// TODO: LocalCommitPublished.claimDelayedOutputTx should be defined
val localCommitPublished = LocalCommitPublished(d.commitments.localCommit.publishableTxs.commitTx.tx, None, Nil, Nil, Nil)
@ -672,9 +673,9 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
case Event(WatchEventConfirmed(BITCOIN_CLOSE_DONE, _, _), d: DATA_CLOSING) if d.mutualClosePublished.isDefined => goto(CLOSED)
case Event(WatchEventConfirmed(BITCOIN_SPEND_OURS_DONE, _, _), d: DATA_CLOSING) if d.localCommitPublished.isDefined => goto(CLOSED)
case Event(WatchEventConfirmed(BITCOIN_LOCALCOMMIT_DONE, _, _), d: DATA_CLOSING) if d.localCommitPublished.isDefined => goto(CLOSED)
case Event(WatchEventConfirmed(BITCOIN_SPEND_THEIRS_DONE, _, _), d: DATA_CLOSING) if d.remoteCommitPublished.isDefined => goto(CLOSED)
case Event(WatchEventConfirmed(BITCOIN_REMOTECOMMIT_DONE, _, _), d: DATA_CLOSING) if d.remoteCommitPublished.isDefined => goto(CLOSED)
case Event(WatchEventConfirmed(BITCOIN_PUNISHMENT_DONE, _, _), d: DATA_CLOSING) if d.revokedCommitPublished.size > 0 => goto(CLOSED)
@ -778,7 +779,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
blockchain ! PublishAsap(tx)
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_SPEND_OURS_DONE)
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_LOCALCOMMIT_DONE)
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(d.commitments, tx)
localCommitPublished.claimMainDelayedOutputTx.foreach(tx => blockchain ! PublishAsap(tx))
@ -799,7 +800,7 @@ class Channel(val them: ActorRef, val blockchain: ActorRef, router: ActorRef, re
require(tx.txid == d.commitments.remoteCommit.txid, "txid mismatch")
// TODO hardcoded mindepth + shouldn't we watch the claim tx instead?
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_SPEND_THEIRS_DONE)
blockchain ! WatchConfirmed(self, tx.txid, 3, BITCOIN_REMOTECOMMIT_DONE)
val remoteCommitPublished = Helpers.Closing.claimCurrentRemoteCommitTxOutputs(d.commitments, tx)
remoteCommitPublished.claimMainOutputTx.foreach(tx => blockchain ! PublishAsap(tx))

View file

@ -66,9 +66,8 @@ case object BITCOIN_FUNDING_DEPTHOK extends BitcoinEvent
case object BITCOIN_FUNDING_LOST extends BitcoinEvent
case object BITCOIN_FUNDING_TIMEOUT extends BitcoinEvent
case object BITCOIN_FUNDING_SPENT extends BitcoinEvent
case object BITCOIN_FUNDING_OURCOMMIT_DELAYPASSED extends BitcoinEvent
case object BITCOIN_SPEND_THEIRS_DONE extends BitcoinEvent
case object BITCOIN_SPEND_OURS_DONE extends BitcoinEvent
case object BITCOIN_LOCALCOMMIT_DONE extends BitcoinEvent
case object BITCOIN_REMOTECOMMIT_DONE extends BitcoinEvent
case object BITCOIN_PUNISHMENT_DONE extends BitcoinEvent
case object BITCOIN_CLOSE_DONE extends BitcoinEvent
case class BITCOIN_FUNDING_OTHER_CHANNEL_SPENT(channelId: Long) extends BitcoinEvent

View file

@ -105,7 +105,7 @@ class WaitForFundingLockedInternalStateSpec extends TestkitBaseClass {
alice ! Error(0, "oops".getBytes)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_LOCALCOMMIT_DONE)
}
}
@ -115,7 +115,7 @@ class WaitForFundingLockedInternalStateSpec extends TestkitBaseClass {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_CLOSE_DONE)
}
}

View file

@ -115,7 +115,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
alice ! Error(0, "oops".getBytes)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_LOCALCOMMIT_DONE)
}
}
@ -125,7 +125,7 @@ class WaitForFundingLockedStateSpec extends TestkitBaseClass {
alice ! CMD_CLOSE(None)
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_CLOSE_DONE)
}
}

View file

@ -2,7 +2,7 @@ package fr.acinq.eclair.channel.states.e
import akka.actor.Props
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.Crypto.{Point, Scalar}
import fr.acinq.bitcoin.Crypto.Scalar
import fr.acinq.bitcoin.{BinaryData, Crypto, Satoshi, Script, ScriptFlags, Transaction}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain._
@ -820,7 +820,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(bobCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === bobCommitTx.txid)
assert(watch.event === BITCOIN_REMOTECOMMIT_DONE)
// in addition to its main output, alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the preimage
val amountClaimed = (for (i <- 0 until 4) yield {
@ -872,7 +874,10 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(revokedTx.txOut.size == 6)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx)
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsgType[WatchConfirmed]
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === revokedTx.txid)
assert(watch.event === BITCOIN_PUNISHMENT_DONE)
val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx
val punishTx = alice2blockchain.expectMsgType[PublishAsap].tx
@ -919,7 +924,9 @@ class NormalStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
assert(aliceCommitTx.txOut.size == 6) // two main outputs and 4 pending htlcs
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === aliceCommitTx.txid)
assert(watch.event === BITCOIN_LOCALCOMMIT_DONE)
// alice can only claim 3 out of 4 htlcs, she can't do anything regarding the htlc sent by bob for which she does not have the htlc
// so we expect 7 transactions:

View file

@ -359,7 +359,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(bobCommitTx.txOut.size == 4) // two main outputs and 2 pending htlcs
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, bobCommitTx)
alice2blockchain.expectMsgType[WatchConfirmed].txId == bobCommitTx.txid
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === bobCommitTx.txid)
assert(watch.event === BITCOIN_REMOTECOMMIT_DONE)
val amountClaimed = (for (i <- 0 until 3) yield {
val claimHtlcTx = alice2blockchain.expectMsgType[PublishAsap].tx
@ -395,7 +397,10 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
// bob published the revoked tx
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, revokedTx)
alice2bob.expectMsgType[Error]
alice2blockchain.expectMsgType[WatchConfirmed]
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === revokedTx.txid)
assert(watch.event === BITCOIN_PUNISHMENT_DONE)
val mainTx = alice2blockchain.expectMsgType[PublishAsap].tx
val punishTx = alice2blockchain.expectMsgType[PublishAsap].tx
@ -420,7 +425,9 @@ class ShutdownStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
alice2blockchain.expectMsg(PublishAsap(aliceCommitTx))
assert(aliceCommitTx.txOut.size == 4) // two main outputs and two htlcs
alice2blockchain.expectMsgType[WatchConfirmed].txId == aliceCommitTx.txid
val watch = alice2blockchain.expectMsgType[WatchConfirmed]
assert(watch.txId === aliceCommitTx.txid)
assert(watch.event === BITCOIN_LOCALCOMMIT_DONE)
// alice can claim both htlc after a timeout
// so we expect 5 transactions:

View file

@ -67,9 +67,9 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
bob2alice.forward(alice)
} while (aliceCloseFee != bobCloseFee)
alice2blockchain.expectMsgType[PublishAsap]
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_CLOSE_DONE)
bob2blockchain.expectMsgType[PublishAsap]
bob2blockchain.expectMsgType[WatchConfirmed]
assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_CLOSE_DONE)
awaitCond(alice.stateName == CLOSING)
awaitCond(bob.stateName == CLOSING)
}
@ -91,7 +91,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
// actual test starts here
assert(alice.stateName == NEGOTIATING)
val mutualCloseTx = bob2blockchain.expectMsgType[PublishAsap].tx
bob2blockchain.expectMsgType[WatchConfirmed]
assert(bob2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_CLOSE_DONE)
alice ! WatchEventSpent(BITCOIN_FUNDING_SPENT, mutualCloseTx)
alice2blockchain.expectNoMsg(1 second)
assert(alice.stateName == NEGOTIATING)
@ -104,7 +104,7 @@ class NegotiatingStateSpec extends TestkitBaseClass with StateTestsHelperMethods
alice ! Error(0, "oops".getBytes())
awaitCond(alice.stateName == CLOSING)
alice2blockchain.expectMsg(PublishAsap(tx))
alice2blockchain.expectMsgType[WatchConfirmed]
assert(alice2blockchain.expectMsgType[WatchConfirmed].event === BITCOIN_LOCALCOMMIT_DONE)
}
}

View file

@ -120,7 +120,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_SPEND_OURS_DONE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) =>
test("recv BITCOIN_LOCALCOMMIT_DONE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, _) =>
within(30 seconds) {
// an error occurs and alice publishes her commit tx
val aliceCommitTx = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.localCommit.publishableTxs.commitTx.tx
@ -131,7 +131,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(alice.stateData.asInstanceOf[DATA_CLOSING].localCommitPublished.isDefined)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_SPEND_OURS_DONE, 0, 0)
alice ! WatchEventConfirmed(BITCOIN_LOCALCOMMIT_DONE, 0, 0)
awaitCond(alice.stateName == CLOSED)
}
}
@ -152,7 +152,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
}
}
test("recv BITCOIN_SPEND_THEIRS_DONE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, bobCommitTxes) =>
test("recv BITCOIN_REMOTECOMMIT_DONE") { case (alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain, bobCommitTxes) =>
within(30 seconds) {
mutualClose(alice, bob, alice2bob, bob2alice, alice2blockchain, bob2blockchain)
val initialState = alice.stateData.asInstanceOf[DATA_CLOSING]
@ -166,7 +166,7 @@ class ClosingStateSpec extends TestkitBaseClass with StateTestsHelperMethods {
assert(alice.stateData.asInstanceOf[DATA_CLOSING].copy(remoteCommitPublished = None) == initialState)
// actual test starts here
alice ! WatchEventConfirmed(BITCOIN_SPEND_THEIRS_DONE, 0, 0)
alice ! WatchEventConfirmed(BITCOIN_REMOTECOMMIT_DONE, 0, 0)
awaitCond(alice.stateName == CLOSED)
}
}