diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index 675fdd758e..9a6458bac5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -49,16 +49,16 @@ case class ChannelParams(channelId: ByteVector32, ) /** - * As funder we trust ourselves to not double spend funding txs: we could always use a zero-confirmation watch, - * but we need a scid to send the initial channel_update and remote may not provide an alias. That's why we always - * wait for one conf, except if the channel has the zero-conf feature (because presumably the peer will send an - * alias in that case). + * Returns the number of confirmations needed to safely handle a funding transaction that we unilaterally funded. + * As funder we trust ourselves to not double spend funding txs, so we don't need to scale the number of confirmations + * based on the funding amount. We want to wait a few blocks though to ensure that the short_channel_id we obtain will + * not be invalidated by a reorg. */ - def minDepthFunder: Option[Long] = { + def minDepthFunder(defaultMinDepth: Int): Option[Long] = { if (localParams.initFeatures.hasFeature(Features.ZeroConf)) { None } else { - Some(1) + Some(defaultMinDepth.toLong) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index 944c6885c9..e14d949e19 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -342,7 +342,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val blockHeight = nodeParams.currentBlockHeight context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) log.info(s"publishing funding tx fundingTxid=${commitment.fundingTxId}") - watchFundingConfirmed(commitment.fundingTxId, params.minDepthFunder, delay_opt = None) + watchFundingConfirmed(commitment.fundingTxId, params.minDepthFunder(nodeParams.channelConf.minDepth), delay_opt = None) // we will publish the funding tx only after the channel state has been written to disk because we want to // make sure we first persist the commitment that returns back the funds to us in case of problem goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, blockHeight, None, Left(fundingCreated)) storing() calling publishFundingTx(d.channelId, fundingTx, fundingTxFee, d.replyTo) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/SingleFundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/SingleFundingHandlers.scala index e7d4eb5dad..2842be4bd5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/SingleFundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/SingleFundingHandlers.scala @@ -116,7 +116,7 @@ trait SingleFundingHandlers extends CommonFundingHandlers { def singleFundingMinDepth(d: ChannelDataWithCommitments): Long = { val minDepth_opt = if (d.commitments.params.localParams.isChannelOpener) { - d.commitments.params.minDepthFunder + d.commitments.params.minDepthFunder(nodeParams.channelConf.minDepth) } else { // When we're not the channel initiator we scale the min_depth confirmations depending on the funding amount. d.commitments.params.minDepthFundee(nodeParams.channelConf.minDepth, d.commitments.latest.commitInput.txOut.amount) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala index 33ef3074c0..fd778546c9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala @@ -93,7 +93,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] val fundingTxId = watchConfirmed.txId - assert(watchConfirmed.minDepth == 1) // when funder we trust ourselves so we never wait more than 1 block + assert(watchConfirmed.minDepth == 6) val txPublished = listener.expectMsgType[TransactionPublished] assert(txPublished.tx.txid == fundingTxId) assert(txPublished.miningFee > 0.sat) @@ -117,7 +117,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS bob2alice.forward(alice) awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED) val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed] - assert(watchConfirmed.minDepth == 1) // when funder we trust ourselves so we never wait more than 1 block + assert(watchConfirmed.minDepth == 6) // when funder we don't scale the number of confirmations based on the funding amount aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Created] } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index 30734814e7..8b6c939404 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -124,13 +124,10 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { connect(nodes("C"), nodes("F"), 5000000 sat, 500000000 msat) awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds) awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_FUNDING_CONFIRMED, max = 30 seconds) - generateBlocks(1, Some(minerAddress)) - // the funder sends its channel_ready after only one block - awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == WAIT_FOR_CHANNEL_READY, max = 30 seconds) - generateBlocks(5, Some(minerAddress)) - // the fundee sends its channel_ready after 6 blocks - awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds) + // we exchange channel_ready and move to the NORMAL state after 6 blocks + generateBlocks(6, Some(minerAddress)) awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds) + awaitCond(stateListenerF.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds) awaitAnnouncements(2) // first we make sure we are in sync with current blockchain height val currentBlockHeight = getBlockHeight()