mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 22:46:44 +01:00
Broadcast commit tx when nothing at stake (#2360)
When we have nothing at stake (channel was never used and we don't have funds to claim), we previously directly went to the CLOSED state without publishing our commitment. This can be an issue for our peer if they have lost data or had a hard time getting a funding tx confirmed. We now publish our commitment once to help them get their funds back in all cases and avoid the CSV delays when getting their funds back. Fixes #1730
This commit is contained in:
parent
8a42246b18
commit
85fea72720
2 changed files with 19 additions and 4 deletions
|
@ -83,7 +83,12 @@ trait ErrorHandlers extends CommonHandlers {
|
|||
context.system.eventStream.publish(ChannelErrorOccurred(self, stateData.channelId, remoteNodeId, LocalError(cause), isFatal = true))
|
||||
|
||||
d match {
|
||||
case dd: PersistentChannelData if Closing.nothingAtStake(dd) => goto(CLOSED)
|
||||
case dd: PersistentChannelData if Closing.nothingAtStake(dd) =>
|
||||
// The channel was never used and we don't have any funds: we don't need to publish our commitment, but it's a
|
||||
// nice thing to do because it lets our peer get their funds back without delays.
|
||||
val commitTx = dd.commitments.fullySignedLocalCommitTx(keyManager)
|
||||
txPublisher ! PublishFinalTx(commitTx, 0 sat, None)
|
||||
goto(CLOSED)
|
||||
case negotiating@DATA_NEGOTIATING(_, _, _, _, Some(bestUnpublishedClosingTx)) =>
|
||||
log.info(s"we have a valid closing tx, publishing it instead of our commitment: closingTxId=${bestUnpublishedClosingTx.tx.txid}")
|
||||
// if we were in the process of closing and already received a closing sig from the counterparty, it's always better to use that
|
||||
|
@ -127,7 +132,13 @@ trait ErrorHandlers extends CommonHandlers {
|
|||
case negotiating@DATA_NEGOTIATING(_, _, _, _, Some(bestUnpublishedClosingTx)) =>
|
||||
// if we were in the process of closing and already received a closing sig from the counterparty, it's always better to use that
|
||||
handleMutualClose(bestUnpublishedClosingTx, Left(negotiating))
|
||||
case d: DATA_WAIT_FOR_FUNDING_CONFIRMED if Closing.nothingAtStake(d) => goto(CLOSED) // the channel was never used and the funding tx may be double-spent
|
||||
case d: DATA_WAIT_FOR_FUNDING_CONFIRMED if Closing.nothingAtStake(d) =>
|
||||
// The channel was never used and the funding tx could be double-spent: we don't need to publish our commitment
|
||||
// since we don't have funds in the channel, but it's a nice thing to do because it lets our peer get their
|
||||
// funds back without delays if they can't double-spend the funding tx.
|
||||
val commitTx = d.commitments.fullySignedLocalCommitTx(keyManager)
|
||||
txPublisher ! PublishFinalTx(commitTx, 0 sat, None)
|
||||
goto(CLOSED)
|
||||
case hasCommitments: PersistentChannelData => spendLocalCurrent(hasCommitments) // NB: we publish the commitment even if we have nothing at stake (in a dataloss situation our peer will send us an error just for that)
|
||||
case _: TransientChannelData => goto(CLOSED) // when there is no commitment yet, we just go to CLOSED state in case an error occurs
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import fr.acinq.eclair.channel._
|
|||
import fr.acinq.eclair.channel.fsm.Channel
|
||||
import fr.acinq.eclair.channel.fsm.Channel.{BITCOIN_FUNDING_PUBLISH_FAILED, BITCOIN_FUNDING_TIMEOUT}
|
||||
import fr.acinq.eclair.channel.publish.TxPublisher
|
||||
import fr.acinq.eclair.channel.publish.TxPublisher.PublishFinalTx
|
||||
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
|
||||
import fr.acinq.eclair.transactions.Scripts.multiSig2of2
|
||||
import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelReady, Error, FundingCreated, FundingSigned, Init, OpenChannel, TlvStream}
|
||||
|
@ -268,8 +269,11 @@ class WaitForFundingConfirmedStateSpec extends TestKitBaseClass with FixtureAnyF
|
|||
|
||||
test("recv Error (nothing at stake)", Tag(ChannelStateTestsTags.NoPushMsat)) { f =>
|
||||
import f._
|
||||
bob ! Error(ByteVector32.Zeroes, "funding double-spent")
|
||||
bob2blockchain.expectNoMessage(100 millis) // we don't publish our commit tx when we have nothing at stake
|
||||
val tx = bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CONFIRMED].commitments.localCommit.commitTxAndRemoteSig.commitTx.tx
|
||||
bob ! Error(ByteVector32.Zeroes, "please help me recover my funds")
|
||||
// We have nothing at stake, but we publish our commitment to help our peer recover their funds more quickly.
|
||||
assert(bob2blockchain.expectMsgType[PublishFinalTx].tx.txid == tx.txid)
|
||||
bob2blockchain.expectNoMessage(100 millis)
|
||||
awaitCond(bob.stateName == CLOSED)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue