mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 22:25:26 +01:00
Increase min-depth
for funding transactions (#2973)
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. We also update our 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.
This commit is contained in:
parent
ef1a029dff
commit
96183a93aa
19 changed files with 73 additions and 77 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue