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/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/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/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/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/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/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..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 @@ -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) @@ -554,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)) @@ -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)