From f4bec6b639aae95154cb1a7b11b13b5da2222dd2 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 26 Dec 2024 10:36:37 +0100 Subject: [PATCH 1/2] Increase `min-depth` to 6 blocks for funding transactions We already use a minimum depth of 6 before announcing channels to protect against reorgs. However, we allowed using the channel for payments after only 3 confirmations (for small channels). A reorg of 3 blocks that invalidates the funding transaction would allow our peer to potentially steal funds. It's more consistent to use the same depth for announcing the channel and actually using it. Note that for wumbo channels, we already scaled the number of confirmations based on the size of the channel. For closing transaction, we don't need the same reorg safety, since we keep watching the funding output for any transaction that spends it, and concurrently spend any commitment transaction that we detect. We thus keep a minimum depth of 3 for closing transactions. --- eclair-core/src/main/resources/reference.conf | 3 +- .../scala/fr/acinq/eclair/NodeParams.scala | 9 ++++-- .../fr/acinq/eclair/channel/fsm/Channel.scala | 29 ++++++++++--------- .../channel/fsm/ChannelOpenDualFunded.scala | 16 +++++----- .../channel/fsm/ChannelOpenSingleFunded.scala | 8 ++--- .../channel/fsm/DualFundingHandlers.scala | 2 +- .../eclair/channel/fsm/ErrorHandlers.scala | 4 +-- .../channel/fsm/SingleFundingHandlers.scala | 4 +-- .../channel/publish/MempoolTxMonitor.scala | 2 +- .../publish/ReplaceableTxPrePublisher.scala | 4 +-- .../scala/fr/acinq/eclair/TestConstants.scala | 6 ++-- .../publish/MempoolTxMonitorSpec.scala | 8 ++--- .../b/WaitForFundingCreatedStateSpec.scala | 4 +-- .../integration/ChannelIntegrationSpec.scala | 22 +++++--------- .../eclair/integration/IntegrationSpec.scala | 1 - .../integration/PaymentIntegrationSpec.scala | 4 +-- 16 files changed, 61 insertions(+), 65 deletions(-) diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf index bb912da344..adec4332c4 100644 --- a/eclair-core/src/main/resources/reference.conf +++ b/eclair-core/src/main/resources/reference.conf @@ -130,7 +130,8 @@ eclair { to-remote-delay-blocks = 720 // number of blocks that the other node's to-self outputs must be delayed (720 ~ 5 days) max-to-local-delay-blocks = 2016 // maximum number of blocks that we are ready to accept for our own delayed outputs (2016 ~ 2 weeks) - mindepth-blocks = 3 + min-depth-funding-blocks = 6 // minimum number of confirmations for funding transactions + min-depth-closing-blocks = 3 // minimum number of confirmations for closing transactions expiry-delta-blocks = 144 max-expiry-delta-blocks = 2016 // we won't forward HTLCs with timeouts greater than this delta // When we receive the preimage for an HTLC and want to fulfill it but the upstream peer stops responding, we want to diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala index 38957487c6..024dc3555e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala @@ -314,9 +314,11 @@ object NodeParams extends Logging { "channel.min-funding-satoshis" -> "channel.min-public-funding-satoshis, channel.min-private-funding-satoshis", // v0.8.0 "bitcoind.batch-requests" -> "bitcoind.batch-watcher-requests", - // vx.x.x + // v0.9.0 "on-chain-fees.target-blocks.safe-utxos-threshold" -> "on-chain-fees.safe-utxos-threshold", - "on-chain-fees.target-blocks" -> "on-chain-fees.confirmation-priority" + "on-chain-fees.target-blocks" -> "on-chain-fees.confirmation-priority", + // v0.12.0 + "channel.mindepth-blocks" -> "channel.min-depth-funding-blocks", ) deprecatedKeyPaths.foreach { case (old, new_) => require(!config.hasPath(old), s"configuration key '$old' has been replaced by '$new_'") @@ -573,7 +575,8 @@ object NodeParams extends Logging { minFundingPrivateSatoshis = Satoshi(config.getLong("channel.min-private-funding-satoshis")), toRemoteDelay = offeredCLTV, maxToLocalDelay = maxToLocalCLTV, - minDepthBlocks = config.getInt("channel.mindepth-blocks"), + minDepthFunding = config.getInt("channel.min-depth-funding-blocks"), + minDepthClosing = config.getInt("channel.min-depth-closing-blocks"), expiryDelta = expiryDelta, maxExpiryDelta = maxExpiryDelta, fulfillSafetyBeforeTimeout = fulfillSafetyBeforeTimeout, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index 06a9d83ea9..a18712b594 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -86,7 +86,8 @@ object Channel { minFundingPrivateSatoshis: Satoshi, toRemoteDelay: CltvExpiryDelta, maxToLocalDelay: CltvExpiryDelta, - minDepthBlocks: Int, + minDepthFunding: Int, + minDepthClosing: Int, expiryDelta: CltvExpiryDelta, maxExpiryDelta: CltvExpiryDelta, fulfillSafetyBeforeTimeout: CltvExpiryDelta, @@ -294,11 +295,11 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with watchFundingConfirmed(commitment.fundingTxId, Some(singleFundingMinDepth(data)), herdDelay_opt) case fundingTx: LocalFundingStatus.DualFundedUnconfirmedFundingTx => publishFundingTx(fundingTx) - val minDepth_opt = data.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, fundingTx.sharedTx.tx) + val minDepth_opt = data.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, fundingTx.sharedTx.tx) watchFundingConfirmed(fundingTx.sharedTx.txId, minDepth_opt, herdDelay_opt) case fundingTx: LocalFundingStatus.ZeroconfPublishedFundingTx => // those are zero-conf channels, the min-depth isn't critical, we use the default - watchFundingConfirmed(fundingTx.tx.txid, Some(nodeParams.channelConf.minDepthBlocks.toLong), herdDelay_opt) + watchFundingConfirmed(fundingTx.tx.txid, Some(nodeParams.channelConf.minDepthFunding.toLong), herdDelay_opt) case _: LocalFundingStatus.ConfirmedFundingTx => data match { case closing: DATA_CLOSING if Closing.nothingAtStake(closing) || Closing.isClosingTypeAlreadyKnown(closing).isDefined => @@ -581,7 +582,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with // We don't have their tx_sigs, but they have ours, and could publish the funding tx without telling us. // That's why we move on immediately to the next step, and will update our unsigned funding tx when we // receive their tx_sigs. - val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession1.fundingTx.sharedTx.tx) + val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession1.fundingTx.sharedTx.tx) watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments1 = d.commitments.add(signingSession1.commitment) val d1 = d.copy(commitments = commitments1, spliceStatus = SpliceStatus.NoSplice) @@ -1316,7 +1317,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with rollbackFundingAttempt(signingSession.fundingTx.tx, previousTxs = Seq.empty) // no splice rbf yet stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, f.getMessage) case Right(signingSession1) => - val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession1.fundingTx.sharedTx.tx) + val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession1.fundingTx.sharedTx.tx) watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments1 = d.commitments.add(signingSession1.commitment) val d1 = d.copy(commitments = commitments1, spliceStatus = SpliceStatus.NoSplice) @@ -1335,7 +1336,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match { case Right((commitments1, _)) => - watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthBlocks), delay_opt = None) + watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None) maybeEmitEventsPostSplice(d.shortIds, d.commitments, commitments1) maybeUpdateMaxHtlcAmount(d.channelUpdate.htlcMaximumMsat, commitments1) stay() using d.copy(commitments = commitments1) storing() sending SpliceLocked(d.channelId, w.tx.txid) @@ -1802,8 +1803,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } else { d.commitments.resolveCommitment(tx) match { case Some(commitment) => - log.warning(s"a commit tx for an older commitment has been published fundingTxId=${tx.txid} fundingTxIndex=${commitment.fundingTxIndex}") - blockchain ! WatchAlternativeCommitTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthBlocks) + log.warning("a commit tx for an older commitment has been published fundingTxId={} fundingTxIndex={}", tx.txid, commitment.fundingTxIndex) + blockchain ! WatchAlternativeCommitTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthClosing) stay() case None => // This must be a former funding tx that has already been pruned, because watches are unordered. @@ -1872,7 +1873,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case Event(WatchOutputSpentTriggered(tx), d: DATA_CLOSING) => // one of the outputs of the local/remote/revoked commit was spent // we just put a watch to be notified when it is confirmed - blockchain ! WatchTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthBlocks) + blockchain ! WatchTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthClosing) // when a remote or local commitment tx containing outgoing htlcs is published on the network, // we watch it in order to extract payment preimage if funds are pulled by the counterparty // we can then use these preimages to fulfill origin htlcs @@ -1907,7 +1908,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with val (localCommitPublished1, claimHtlcTx_opt) = Closing.LocalClose.claimHtlcDelayedOutput(localCommitPublished, keyManager, d.commitments.latest, tx, nodeParams.currentBitcoinCoreFeerates, nodeParams.onChainFeeConf, d.finalScriptPubKey) claimHtlcTx_opt.foreach(claimHtlcTx => { txPublisher ! PublishFinalTx(claimHtlcTx, claimHtlcTx.fee, None) - blockchain ! WatchTxConfirmed(self, claimHtlcTx.tx.txid, nodeParams.channelConf.minDepthBlocks, Some(RelativeDelay(tx.txid, d.commitments.params.remoteParams.toSelfDelay.toInt.toLong))) + blockchain ! WatchTxConfirmed(self, claimHtlcTx.tx.txid, nodeParams.channelConf.minDepthClosing, Some(RelativeDelay(tx.txid, d.commitments.params.remoteParams.toSelfDelay.toInt.toLong))) }) Closing.updateLocalCommitPublished(localCommitPublished1, tx) }), @@ -2490,8 +2491,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with val fundingStatus = LocalFundingStatus.ZeroconfPublishedFundingTx(w.tx, d.commitments.localFundingSigs(w.tx.txid), d.commitments.liquidityPurchase(w.tx.txid)) d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match { case Right((commitments1, _)) => - log.info(s"zero-conf funding txid=${w.tx.txid} has been published") - watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthBlocks), delay_opt = None) + log.info("zero-conf funding txid={} has been published", w.tx.txid) + watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None) val d1 = d match { // NB: we discard remote's stashed channel_ready, they will send it back at reconnection case d: DATA_WAIT_FOR_FUNDING_CONFIRMED => @@ -2566,9 +2567,9 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } else { d.commitments.resolveCommitment(tx) match { case Some(commitment) => - log.warning(s"a commit tx for an older commitment has been published fundingTxId=${tx.txid} fundingTxIndex=${commitment.fundingTxIndex}") + log.warning("a commit tx for an older commitment has been published fundingTxId={} fundingTxIndex={}", tx.txid, commitment.fundingTxIndex) // we watch the commitment tx, in the meantime we force close using the latest commitment - blockchain ! WatchAlternativeCommitTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthBlocks) + blockchain ! WatchAlternativeCommitTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthClosing) spendLocalCurrent(d) case None => // This must be a former funding tx that has already been pruned, because watches are unordered. diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala index 4cda1931a1..a32a13c049 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala @@ -173,7 +173,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { // At this point, the min_depth is an estimate and may change after we know exactly how our peer contributes // to the funding transaction. Maybe they will contribute 0 satoshis to the shared output, but still add inputs // and outputs. - val minDepth_opt = channelParams.minDepthFundee(nodeParams.channelConf.minDepthBlocks, localAmount + remoteAmount) + val minDepth_opt = channelParams.minDepthFundee(nodeParams.channelConf.minDepthFunding, localAmount + remoteAmount) val upfrontShutdownScript_opt = localParams.upfrontShutdownScript_opt.map(scriptPubKey => ChannelTlv.UpfrontShutdownScriptTlv(scriptPubKey)) val tlvs: Set[AcceptDualFundedChannelTlv] = Set( upfrontShutdownScript_opt, @@ -390,7 +390,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { // We don't have their tx_sigs, but they have ours, and could publish the funding tx without telling us. // That's why we move on immediately to the next step, and will update our unsigned funding tx when we // receive their tx_sigs. - val minDepth_opt = d.channelParams.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession1.fundingTx.sharedTx.tx) + val minDepth_opt = d.channelParams.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession1.fundingTx.sharedTx.tx) watchFundingConfirmed(d.signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments = Commitments( params = d.channelParams, @@ -413,7 +413,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { rollbackFundingAttempt(d.signingSession.fundingTx.tx, Nil) goto(CLOSED) sending Error(d.channelId, f.getMessage) case Right(signingSession) => - val minDepth_opt = d.channelParams.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession.fundingTx.sharedTx.tx) + val minDepth_opt = d.channelParams.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession.fundingTx.sharedTx.tx) watchFundingConfirmed(d.signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments = Commitments( params = d.channelParams, @@ -478,7 +478,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { rollbackRbfAttempt(signingSession, d) stay() using d.copy(status = DualFundingStatus.RbfAborted) sending TxAbort(d.channelId, f.getMessage) case Right(signingSession1) => - val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession1.fundingTx.sharedTx.tx) + val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession1.fundingTx.sharedTx.tx) watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments1 = d.commitments.add(signingSession1.commitment) val d1 = DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments1, d.localPushAmount, d.remotePushAmount, d.waitingSince, d.lastChecked, DualFundingStatus.WaitingForConfirmations, d.deferred) @@ -495,7 +495,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { } case Event(cmd: CMD_BUMP_FUNDING_FEE, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) => - val zeroConf = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, d.latestFundingTx.sharedTx.tx).isEmpty + val zeroConf = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, d.latestFundingTx.sharedTx.tx).isEmpty if (!d.latestFundingTx.fundingParams.isInitiator) { cmd.replyTo ! RES_FAILURE(cmd, InvalidRbfNonInitiator(d.channelId)) stay() @@ -524,7 +524,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { } case Event(msg: TxInitRbf, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) => - val zeroConf = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, d.latestFundingTx.sharedTx.tx).isEmpty + val zeroConf = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, d.latestFundingTx.sharedTx.tx).isEmpty if (d.latestFundingTx.fundingParams.isInitiator) { // Only the initiator is allowed to initiate RBF. log.info("rejecting tx_init_rbf, we're the initiator, not them!") @@ -661,7 +661,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { // No need to store their commit_sig, they will re-send it if we disconnect. stay() using d.copy(status = DualFundingStatus.RbfWaitingForSigs(signingSession1)) case signingSession1: InteractiveTxSigningSession.SendingSigs => - val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, signingSession1.fundingTx.sharedTx.tx) + val minDepth_opt = d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, signingSession1.fundingTx.sharedTx.tx) watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None) val commitments1 = d.commitments.add(signingSession1.commitment) val d1 = DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments1, d.localPushAmount, d.remotePushAmount, d.waitingSince, d.lastChecked, DualFundingStatus.WaitingForConfirmations, d.deferred) @@ -727,7 +727,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { d.commitments.updateLocalFundingStatus(w.tx.txid, fundingStatus) match { case Right((commitments1, _)) => // we still watch the funding tx for confirmation even if we can use the zero-conf channel right away - watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthBlocks), delay_opt = None) + watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None) val realScidStatus = RealScidStatus.Unknown val shortIds = createShortIds(d.channelId, realScidStatus) val channelReady = createChannelReady(shortIds, d.commitments.params) 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 7cd37bca2f..b2ad274f64 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 @@ -128,7 +128,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { val fundingPubkey = keyManager.fundingPublicKey(d.initFundee.localParams.fundingKeyPath, fundingTxIndex = 0).publicKey val channelKeyPath = keyManager.keyPath(d.initFundee.localParams, d.initFundee.channelConfig) val params = ChannelParams(d.initFundee.temporaryChannelId, d.initFundee.channelConfig, channelFeatures, d.initFundee.localParams, remoteParams, open.channelFlags) - val minimumDepth = params.minDepthFundee(nodeParams.channelConf.minDepthBlocks, open.fundingSatoshis) + val minimumDepth = params.minDepthFundee(nodeParams.channelConf.minDepthFunding, open.fundingSatoshis) log.info("will use fundingMinDepth={}", minimumDepth) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used. // See https://github.com/lightningnetwork/lightning-rfc/pull/714. @@ -296,8 +296,8 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) context.system.eventStream.publish(ChannelSignatureReceived(self, commitments)) // NB: we don't send a ChannelSignatureSent for the first commit - log.info(s"waiting for them to publish the funding tx for channelId=$channelId fundingTxid=${commitment.fundingTxId}") - watchFundingConfirmed(commitment.fundingTxId, params.minDepthFundee(nodeParams.channelConf.minDepthBlocks, fundingAmount), delay_opt = None) + log.info("waiting for them to publish the funding tx for channelId={} fundingTxid={}", channelId, commitment.fundingTxId) + watchFundingConfirmed(commitment.fundingTxId, params.minDepthFundee(nodeParams.channelConf.minDepthFunding, fundingAmount), delay_opt = None) goto(WAIT_FOR_FUNDING_CONFIRMED) using DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments, nodeParams.currentBlockHeight, None, Right(fundingSigned)) storing() sending fundingSigned } } @@ -396,7 +396,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { case Right((commitments1, _)) => log.info("funding txid={} was successfully published for zero-conf channelId={}", w.tx.txid, d.channelId) // we still watch the funding tx for confirmation even if we can use the zero-conf channel right away - watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthBlocks), delay_opt = None) + watchFundingConfirmed(w.tx.txid, Some(nodeParams.channelConf.minDepthFunding), delay_opt = None) val realScidStatus = RealScidStatus.Unknown val shortIds = createShortIds(d.channelId, realScidStatus) val channelReady = createChannelReady(shortIds, d.commitments.params) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/DualFundingHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/DualFundingHandlers.scala index eb26fa4df9..9a3dd14008 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/DualFundingHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/DualFundingHandlers.scala @@ -58,7 +58,7 @@ trait DualFundingHandlers extends CommonFundingHandlers { /** Return true if we should stop waiting for confirmations when receiving our peer's channel_ready. */ def switchToZeroConf(remoteChannelReady: ChannelReady, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED): Boolean = { - if (d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthBlocks, d.latestFundingTx.sharedTx.tx).nonEmpty) { + if (d.commitments.params.minDepthDualFunding(nodeParams.channelConf.minDepthFunding, d.latestFundingTx.sharedTx.tx).nonEmpty) { // We're not using zero-conf, but our peer decided to trust us anyway. We can skip waiting for confirmations if: // - they provided a channel alias // - there is a single version of the funding tx (otherwise we don't know which one to use) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala index cb6ebe1005..13563f1f0a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala @@ -62,7 +62,7 @@ trait ErrorHandlers extends CommonHandlers { // the initiator pays the fee val fee = if (localPaysClosingFees) closingTx.fee else 0.sat txPublisher ! PublishFinalTx(closingTx, fee, None) - blockchain ! WatchTxConfirmed(self, closingTx.tx.txid, nodeParams.channelConf.minDepthBlocks) + blockchain ! WatchTxConfirmed(self, closingTx.tx.txid, nodeParams.channelConf.minDepthClosing) } def handleLocalError(cause: Throwable, d: ChannelData, msg: Option[Any]) = { @@ -173,7 +173,7 @@ trait ErrorHandlers extends CommonHandlers { */ private def watchConfirmedIfNeeded(txs: Iterable[Transaction], irrevocablySpent: Map[OutPoint, Transaction], relativeDelays: Map[TxId, RelativeDelay]): Unit = { val (skip, process) = txs.partition(Closing.inputsAlreadySpent(_, irrevocablySpent)) - process.foreach(tx => blockchain ! WatchTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthBlocks, relativeDelays.get(tx.txid))) + process.foreach(tx => blockchain ! WatchTxConfirmed(self, tx.txid, nodeParams.channelConf.minDepthClosing, relativeDelays.get(tx.txid))) skip.foreach(tx => log.debug(s"no need to watch txid=${tx.txid}, it has already been confirmed")) } 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 9cb594ca8a..96855887c5 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 @@ -119,10 +119,10 @@ trait SingleFundingHandlers extends CommonFundingHandlers { d.commitments.params.minDepthFunder } 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.minDepthBlocks, d.commitments.latest.commitInput.txOut.amount) + d.commitments.params.minDepthFundee(nodeParams.channelConf.minDepthFunding, d.commitments.latest.commitInput.txOut.amount) } val minDepth = minDepth_opt.getOrElse { - val defaultMinDepth = nodeParams.channelConf.minDepthBlocks + val defaultMinDepth = nodeParams.channelConf.minDepthFunding // If we are in state WAIT_FOR_FUNDING_CONFIRMED, then the computed minDepth should be > 0, otherwise we would // have skipped this state. Maybe the computation method was changed and eclair was restarted? log.warning("min_depth should be defined since we're waiting for the funding tx to confirm, using default minDepth={}", defaultMinDepth) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitor.scala index 4346645507..2cf014dc9d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitor.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitor.scala @@ -152,7 +152,7 @@ private class MempoolTxMonitor(nodeParams: NodeParams, case Failure(reason) => GetTxConfirmationsFailed(reason) } Behaviors.same - } else if (confirmations < nodeParams.channelConf.minDepthBlocks) { + } else if (confirmations < nodeParams.channelConf.minDepthClosing) { log.debug("txid={} has {} confirmations, waiting to reach min depth", cmd.tx.txid, confirmations) cmd.replyTo ! TxRecentlyConfirmed(cmd.tx.txid, confirmations) Behaviors.same diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala index a971cf7970..f1da389800 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala @@ -211,7 +211,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams, */ private def checkHtlcOutput(commitment: FullCommitment, htlcTx: HtlcTx): Future[Command] = { getRemoteCommitConfirmations(commitment).flatMap { - case Some(depth) if depth >= nodeParams.channelConf.minDepthBlocks => Future.successful(RemoteCommitTxConfirmed) + case Some(depth) if depth >= nodeParams.channelConf.minDepthClosing => Future.successful(RemoteCommitTxConfirmed) case _ => bitcoinClient.isTransactionOutputSpent(htlcTx.input.outPoint.txid, htlcTx.input.outPoint.index.toInt).map { case true => HtlcOutputAlreadySpent case false => ParentTxOk @@ -289,7 +289,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams, */ private def checkClaimHtlcOutput(commitment: FullCommitment, claimHtlcTx: ClaimHtlcTx): Future[Command] = { bitcoinClient.getTxConfirmations(commitment.localCommit.commitTxAndRemoteSig.commitTx.tx.txid).flatMap { - case Some(depth) if depth >= nodeParams.channelConf.minDepthBlocks => Future.successful(LocalCommitTxConfirmed) + case Some(depth) if depth >= nodeParams.channelConf.minDepthClosing => Future.successful(LocalCommitTxConfirmed) case _ => bitcoinClient.isTransactionOutputSpent(claimHtlcTx.input.outPoint.txid, claimHtlcTx.input.outPoint.index.toInt).map { case true => HtlcOutputAlreadySpent case false => ParentTxOk diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala index 9d4f92ff5e..b10e297188 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala @@ -130,7 +130,8 @@ object TestConstants { maxTxPublishRetryDelay = 10 millis, maxChannelSpentRescanBlocks = 144, htlcMinimum = 0 msat, - minDepthBlocks = 3, + minDepthFunding = 6, + minDepthClosing = 3, toRemoteDelay = CltvExpiryDelta(144), maxToLocalDelay = CltvExpiryDelta(1000), reserveToFundingRatio = 0.01, // note: not used (overridden below) @@ -307,7 +308,8 @@ object TestConstants { maxTxPublishRetryDelay = 10 millis, maxChannelSpentRescanBlocks = 144, htlcMinimum = 1000 msat, - minDepthBlocks = 3, + minDepthFunding = 3, + minDepthClosing = 3, toRemoteDelay = CltvExpiryDelta(144), maxToLocalDelay = CltvExpiryDelta(1000), reserveToFundingRatio = 0.01, // note: not used (overridden below) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala index 36722db8be..c7a49fc5d4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitorSpec.scala @@ -106,13 +106,13 @@ class MempoolTxMonitorSpec extends TestKitBaseClass with AnyFunSuiteLike with Bi probe.expectMsg(TxInMempool(tx.txid, currentBlockHeight(), parentConfirmed = true)) probe.expectNoMessage(100 millis) - assert(TestConstants.Alice.nodeParams.channelConf.minDepthBlocks > 1) + assert(TestConstants.Alice.nodeParams.channelConf.minDepthClosing > 1) generateBlocks(1) monitor ! WrappedCurrentBlockHeight(currentBlockHeight()) probe.expectMsg(TxRecentlyConfirmed(tx.txid, 1)) probe.expectNoMessage(100 millis) // we wait for more than one confirmation to protect against reorgs - generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthBlocks - 1) + generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthClosing - 1) monitor ! WrappedCurrentBlockHeight(currentBlockHeight()) probe.expectMsg(TxDeeplyBuried(tx)) } @@ -129,7 +129,7 @@ class MempoolTxMonitorSpec extends TestKitBaseClass with AnyFunSuiteLike with Bi monitor ! Publish(probe.ref, tx2, tx2.txIn.head.outPoint, "test-tx", 10 sat) waitTxInMempool(bitcoinClient, tx2.txid, probe) - generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthBlocks) + generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthClosing) monitor ! WrappedCurrentBlockHeight(currentBlockHeight()) probe.expectMsg(TxDeeplyBuried(tx2)) } @@ -273,7 +273,7 @@ class MempoolTxMonitorSpec extends TestKitBaseClass with AnyFunSuiteLike with Bi assert(txPublished.miningFee == 15.sat) assert(txPublished.desc == "test-tx") - generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthBlocks) + generateBlocks(TestConstants.Alice.nodeParams.channelConf.minDepthClosing) monitor ! WrappedCurrentBlockHeight(currentBlockHeight()) eventListener.expectMsg(TransactionConfirmed(txPublished.channelId, txPublished.remoteNodeId, tx)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala index 48102f1a35..64138e82f5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala @@ -86,7 +86,7 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun bob2alice.expectMsgType[FundingSigned] bob2blockchain.expectMsgType[TxPublisher.SetChannelId] val watchConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] - assert(watchConfirmed.minDepth == Alice.nodeParams.channelConf.minDepthBlocks) + assert(watchConfirmed.minDepth == Bob.nodeParams.channelConf.minDepthFunding) } test("recv FundingCreated (large channel)", Tag(LargeChannel)) { f => @@ -98,7 +98,7 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun bob2blockchain.expectMsgType[TxPublisher.SetChannelId] val watchConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed] // when we are fundee, we use a higher min depth for wumbo channels - assert(watchConfirmed.minDepth > Bob.nodeParams.channelConf.minDepthBlocks) + assert(watchConfirmed.minDepth > Bob.nodeParams.channelConf.minDepthFunding) } test("recv FundingCreated (funder can't pay fees)", Tag(FunderBelowCommitFees)) { f => 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 4da80ab858..d9c022e37e 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 @@ -127,12 +127,10 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { 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(2, Some(minerAddress)) - // the fundee sends its channel_ready after 3 blocks + 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) awaitCond(stateListenerC.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL, max = 30 seconds) - // we generate more blocks for the funding tx to be deeply buried and the channel to be announced - generateBlocks(3, Some(minerAddress)) awaitAnnouncements(2) // first we make sure we are in sync with current blockchain height val currentBlockHeight = getBlockHeight() @@ -481,16 +479,13 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { connect(nodes("A"), nodes("C"), 11000000 sat, 0 msat) // confirm the funding tx - generateBlocks(2) + generateBlocks(6) within(60 seconds) { var count = 0 while (count < 2) { if (eventListener.expectMsgType[ChannelStateChanged](max = 60 seconds).currentState == NORMAL) count = count + 1 } } - - // generate more blocks so that all funding txes are buried under at least 6 blocks - generateBlocks(4) awaitAnnouncements(1) } @@ -502,7 +497,7 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { val sender = TestProbe() // mine the funding tx - generateBlocks(2) + generateBlocks(6) // get the channelId sender.send(fundee.register, Register.GetChannels) val Some((_, fundeeChannel)) = sender.expectMsgType[Map[ByteVector32, ActorRef]].find(_._1 == tempChannelId) @@ -514,13 +509,13 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_CHANNEL_READY }) - generateBlocks(6) + generateBlocks(2) // after 8 blocks the fundee is still waiting for more confirmations fundee.register ! Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender)) assert(sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_FUNDING_CONFIRMED) - // after 8 blocks the funder is still waiting for funding_locked from the fundee + // after 8 blocks the funder is still waiting for channel_ready from the fundee funder.register ! Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender)) assert(sender.expectMsgType[RES_GET_CHANNEL_STATE].state == WAIT_FOR_CHANNEL_READY) @@ -666,7 +661,7 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { connect(nodes("A"), nodes("C"), 11000000 sat, 0 msat) // confirm the funding tx - generateBlocks(2) + generateBlocks(6) within(60 seconds) { var count = 0 while (count < 2) { @@ -678,9 +673,6 @@ abstract class AnchorChannelIntegrationSpec extends ChannelIntegrationSpec { } } } - - // generate more blocks so that all funding txs are buried under at least 6 blocks - generateBlocks(4) awaitAnnouncements(1) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala index 711463c6fd..59cddaf12e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/IntegrationSpec.scala @@ -81,7 +81,6 @@ abstract class IntegrationSpec extends TestKitBaseClass with BitcoindService wit "eclair.bitcoind.zmqblock" -> s"tcp://127.0.0.1:$bitcoindZmqBlockPort", "eclair.bitcoind.zmqtx" -> s"tcp://127.0.0.1:$bitcoindZmqTxPort", "eclair.bitcoind.wallet" -> defaultWallet, - "eclair.channel.mindepth-blocks" -> 2, "eclair.channel.max-htlc-value-in-flight-msat" -> 100000000000L, "eclair.channel.max-htlc-value-in-flight-percent" -> 100, "eclair.channel.max-block-processing-delay" -> "2 seconds", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 1ebdce1270..a6d58136ee 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -109,7 +109,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { }, max = 20 seconds, interval = 1 second) // confirming the funding tx - generateBlocks(2) + generateBlocks(6) within(60 seconds) { var count = 0 @@ -138,8 +138,6 @@ class PaymentIntegrationSpec extends IntegrationSpec { } test("wait for network announcements") { - // generating more blocks so that all funding txes are buried under at least 6 blocks - generateBlocks(4) // A requires private channels, as a consequence: // - only A and B know about channel A-B (and there is no channel_announcement) // - A is not announced (no node_announcement) From c2fc03536ac4a23f8856b28fd466ecfef119bfc8 Mon Sep 17 00:00:00 2001 From: t-bast Date: Thu, 19 Dec 2024 10:35:17 +0100 Subject: [PATCH 2/2] Update confirmation scaling factor post-halving We were still using values from before the halving. We update those values and change the scaling factor to a reasonable scaling. This protects channels against attackers with significant mining power. --- .../fr/acinq/eclair/channel/Commitments.scala | 4 ++-- .../fr/acinq/eclair/channel/HelpersSpec.scala | 14 +++++++------- .../states/a/WaitForAcceptChannelStateSpec.scala | 2 +- .../integration/ChannelIntegrationSpec.scala | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) 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 7bc2b423b9..11a0ddc21b 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 @@ -141,8 +141,8 @@ object ChannelParams { // small amount: not scaled defaultMinDepth } else { - val blockReward = 6.25 // this is true as of ~May 2020, but will be too large after 2024 - val scalingFactor = 15 + val blockReward = 3.125 // this will be too large after the halving in 2028 + val scalingFactor = 10 val blocksToReachFunding = (((scalingFactor * amount.toBtc.toDouble) / blockReward).ceil + 1).toInt defaultMinDepth.max(blocksToReachFunding) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index 52b3d7bf4d..bd057a4757 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -39,13 +39,13 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging test("scale funding tx min depth according to funding amount") { - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(1)) == 4) - assert(ChannelParams.minDepthScaled(defaultMinDepth = 6, Btc(1)) == 6) // 4 conf would be enough but we use min-depth=6 - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(6.25)) == 16) // we use scaling_factor=15 and a fixed block reward of 6.25BTC - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(12.5)) == 31) - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(12.6)) == 32) - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(30)) == 73) - assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(50)) == 121) + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(1)) == 5) + assert(ChannelParams.minDepthScaled(defaultMinDepth = 6, Btc(1)) == 6) // 5 conf would be enough but we use min-depth=6 + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(3.125)) == 11) // we use scaling_factor=10 and a fixed block reward of 3.125BTC + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(6.25)) == 21) + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(10)) == 33) + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(25)) == 81) + assert(ChannelParams.minDepthScaled(defaultMinDepth = 3, Btc(50)) == 161) } test("compute refresh delay") { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala index 23c067afea..2592e248b1 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForAcceptChannelStateSpec.scala @@ -301,7 +301,7 @@ class WaitForAcceptChannelStateSpec extends TestKitBaseClass with FixtureAnyFunS test("recv AcceptChannel (large channel)", Tag(LargeChannel)) { f => import f._ val accept = bob2alice.expectMsgType[AcceptChannel] - assert(accept.minimumDepth == 13) // with large channel tag we create a 5BTC channel + assert(accept.minimumDepth == 17) // with large channel tag we create a 5BTC channel bob2alice.forward(alice, accept) awaitCond(alice.stateName == WAIT_FOR_FUNDING_INTERNAL) aliceOpenReplyTo.expectNoMessage() 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 d9c022e37e..1ceac3efc0 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 @@ -549,8 +549,8 @@ class StandardChannelIntegrationSpec extends ChannelIntegrationSpec { fundeeState == WAIT_FOR_FUNDING_CONFIRMED && funderState == WAIT_FOR_CHANNEL_READY }, max = 30 seconds, interval = 10 seconds) - // 5 extra blocks make it 13, just the amount of confirmations needed - generateBlocks(5) + // 10 extra blocks make it 18, which should be enough confirmations + generateBlocks(10) awaitCond({ fundee.register ! Register.Forward(sender.ref.toTyped[Any], channelId, CMD_GET_CHANNEL_STATE(ActorRef.noSender))