Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3b1537e
Add deadline information to tx publication
t-bast Dec 16, 2021
9bfdd90
Evaluate feerate at tx broadcast time
t-bast Dec 17, 2021
1e4ecc0
Retry conflicting replaceable transactions
t-bast Dec 17, 2021
cc65aee
Refactor replaceable tx publication
t-bast Dec 21, 2021
d1209ad
Regularly bump transaction fees
t-bast Dec 23, 2021
b066753
Fix review comments for 3b1537e
t-bast Jan 11, 2022
3cd7b83
Fix review comments for 9bfdd90
t-bast Jan 12, 2022
29b5039
fixup! Fix review comments for 9bfdd90
t-bast Jan 13, 2022
0a554f7
fixup! fixup! Fix review comments for 9bfdd90
t-bast Jan 13, 2022
fa3e17d
Add more fields to actors private class
t-bast Jan 14, 2022
441a8ee
fixup! Add more fields to actors private class
t-bast Jan 14, 2022
8d13b91
Fix first pass review comments for d1209ad
t-bast Jan 14, 2022
25fa5e6
Remove explicit stopping of leaf actors
t-bast Jan 17, 2022
6d1b34e
Remove intermediate CheckFee message
t-bast Jan 17, 2022
315b0a6
Harmonize sendResult
t-bast Jan 17, 2022
4e635de
ReplaceableTxPublisher update confirmation target
t-bast Jan 17, 2022
0bfeb53
Clarify comment about tx fee
t-bast Jan 17, 2022
63b7cde
fixup! ReplaceableTxPublisher update confirmation target
t-bast Jan 17, 2022
7e863ba
Create case class to hold publish attempts
t-bast Jan 18, 2022
e389af6
Handle funding tx not found
t-bast Jan 18, 2022
7e59cf6
Merge branch 'master' into tx-publisher-deadline
t-bast Jan 18, 2022
d76ae31
Test the PublishAttempts case class
t-bast Jan 18, 2022
67e0d96
Improve tests
t-bast Jan 18, 2022
8b54837
fixup! Improve tests
t-bast Jan 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ eclair {

// number of blocks to target when computing fees for each transaction type
target-blocks {
funding = 6 // target for the funding transaction
commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing*
mutual-close = 12 // target for the mutual close transaction
claim-main = 12 // target for the claim main transaction (tx that spends main channel output back to wallet)
funding = 6 // target for the funding transaction
commitment = 2 // target for the commitment transaction (used in force-close scenario) *do not change this unless you know what you are doing*
commitment-without-htlcs = 12 // target for the commitment transaction when we have no htlcs to claim (used in force-close scenario) *do not change this unless you know what you are doing*
mutual-close = 12 // target for the mutual close transaction
claim-main = 12 // target for the claim main transaction (tx that spends main channel output back to wallet)
}

feerate-tolerance {
Expand Down
37 changes: 37 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/BlockHeight.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2022 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package fr.acinq.eclair

/**
* Created by t-bast on 11/01/2022.
*/

case class BlockHeight(private val underlying: Long) extends Ordered[BlockHeight] {
// @formatter:off
override def compare(other: BlockHeight): Int = underlying.compareTo(other.underlying)
def +(other: BlockHeight) = BlockHeight(underlying + other.underlying)
def +(i: Int) = BlockHeight(underlying + i)
def +(l: Long) = BlockHeight(underlying + l)
def -(other: BlockHeight) = BlockHeight(underlying - other.underlying)
def -(i: Int) = BlockHeight(underlying - i)
def -(l: Long) = BlockHeight(underlying - l)
def unary_- = BlockHeight(-underlying)

def toLong: Long = underlying
def toInt: Int = underlying.toInt
// @formatter:on
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ object NodeParams extends Logging {
val feeTargets = FeeTargets(
fundingBlockTarget = config.getInt("on-chain-fees.target-blocks.funding"),
commitmentBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment"),
commitmentWithoutHtlcsBlockTarget = config.getInt("on-chain-fees.target-blocks.commitment-without-htlcs"),
mutualCloseBlockTarget = config.getInt("on-chain-fees.target-blocks.mutual-close"),
claimMainBlockTarget = config.getInt("on-chain-fees.target-blocks.claim-main")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ trait FeeEstimator {
// @formatter:on
}

case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int)
case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, commitmentWithoutHtlcsBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int)

/**
* @param maxExposure maximum exposure to pending dust htlcs we tolerate: we will automatically fail HTLCs when going above this threshold.
Expand Down
19 changes: 10 additions & 9 deletions eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1405,12 +1405,12 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
Commitments.sendFulfill(d.commitments, c) match {
case Right((commitments1, _)) =>
log.info("got valid payment preimage, recalculating transactions to redeem the corresponding htlc on-chain")
val localCommitPublished1 = d.localCommitPublished.map(localCommitPublished => Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets))
val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets))
val localCommitPublished1 = d.localCommitPublished.map(localCommitPublished => Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, commitments1, localCommitPublished.commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets))
val remoteCommitPublished1 = d.remoteCommitPublished.map(remoteCommitPublished => Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, commitments1.remoteCommit, remoteCommitPublished.commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets))
val nextRemoteCommitPublished1 = d.nextRemoteCommitPublished.map(remoteCommitPublished => {
require(commitments1.remoteNextCommitInfo.isLeft, "next remote commit must be defined")
val remoteCommit = commitments1.remoteNextCommitInfo.swap.toOption.get.nextRemoteCommit
Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, remoteCommit, remoteCommitPublished.commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, commitments1, remoteCommit, remoteCommitPublished.commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
})

def republish(): Unit = {
Expand Down Expand Up @@ -2413,7 +2413,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
stay()
} else {
val commitTx = d.commitments.fullySignedLocalCommitTx(keyManager).tx
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(localCommitPublished = Some(localCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, fundingTx = None, waitingSinceBlock = nodeParams.currentBlockHeight, negotiating.closingTxProposed.flatten.map(_.unsignedTx), localCommitPublished = Some(localCommitPublished))
Expand Down Expand Up @@ -2467,8 +2467,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishFinalTx(tx, tx.fee, Some(commitTx.txid)))
List(PublishFinalTx(commitTx, commitInput, "commit-tx", Closing.commitTxFee(commitments.commitInput, commitTx, isFunder), None)) ++ (claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None)))
case _: Transactions.AnchorOutputsCommitmentFormat =>
val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitments))
val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx => PublishReplaceableTx(tx, commitments) }
val redeemableHtlcTxs = htlcTxs.values.collect { case Some(tx) => PublishReplaceableTx(tx, commitments) }
List(PublishFinalTx(commitTx, commitInput, "commit-tx", Closing.commitTxFee(commitments.commitInput, commitTx, isFunder), None)) ++ claimLocalAnchor ++ claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None))
}
publishIfNeeded(publishQueue, irrevocablySpent)
Expand All @@ -2491,7 +2491,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
require(commitTx.txid == d.commitments.remoteCommit.txid, "txid mismatch")

context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(d.commitments.commitInput, commitTx, d.commitments.localParams.isFunder), "remote-commit"))
val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, d.commitments.remoteCommit, commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(remoteCommitPublished = Some(remoteCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, fundingTx = None, waitingSinceBlock = nodeParams.currentBlockHeight, negotiating.closingTxProposed.flatten.map(_.unsignedTx), remoteCommitPublished = Some(remoteCommitPublished))
Expand Down Expand Up @@ -2525,7 +2525,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
require(commitTx.txid == remoteCommit.txid, "txid mismatch")

context.system.eventStream.publish(TransactionPublished(d.channelId, remoteNodeId, commitTx, Closing.commitTxFee(d.commitments.commitInput, commitTx, d.commitments.localParams.isFunder), "next-remote-commit"))
val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val remoteCommitPublished = Helpers.Closing.claimRemoteCommitTxOutputs(keyManager, d.commitments, remoteCommit, commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val nextData = d match {
case closing: DATA_CLOSING => closing.copy(nextRemoteCommitPublished = Some(remoteCommitPublished))
case negotiating: DATA_NEGOTIATING => DATA_CLOSING(d.commitments, fundingTx = None, waitingSinceBlock = nodeParams.currentBlockHeight, negotiating.closingTxProposed.flatten.map(_.unsignedTx), nextRemoteCommitPublished = Some(remoteCommitPublished))
Expand All @@ -2538,7 +2538,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo
private def doPublish(remoteCommitPublished: RemoteCommitPublished, commitments: Commitments): Unit = {
import remoteCommitPublished._

val publishQueue = claimMainOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)).toSeq ++ claimHtlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitments))
val redeemableHtlcTxs = claimHtlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitments))
val publishQueue = claimMainOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)).toSeq ++ redeemableHtlcTxs
publishIfNeeded(publishQueue, irrevocablySpent)

// we watch:
Expand Down Expand Up @@ -2602,7 +2603,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, remo

// let's try to spend our current local tx
val commitTx = d.commitments.fullySignedLocalCommitTx(keyManager).tx
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)
val localCommitPublished = Helpers.Closing.claimCurrentLocalCommitTxOutputs(keyManager, d.commitments, commitTx, BlockHeight(nodeParams.currentBlockHeight), nodeParams.onChainFeeConf.feeEstimator, nodeParams.onChainFeeConf.feeTargets)

goto(ERR_INFORMATION_LEAK) calling doPublish(localCommitPublished, d.commitments) sending error
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ sealed trait CommitPublished {
* None only for incoming HTLCs for which we don't have the preimage (we can't claim them yet).
* @param claimHtlcDelayedTxs 3rd-stage txs (spending the output of HTLC txs).
* @param claimAnchorTxs txs spending anchor outputs to bump the feerate of the commitment tx (if applicable).
* We currently only claim our local anchor, but it would be nice to claim both when it
* is economical to do so to avoid polluting the utxo set.
*/
case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[ClaimLocalDelayedOutputTx], htlcTxs: Map[OutPoint, Option[HtlcTx]], claimHtlcDelayedTxs: List[HtlcDelayedTx], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished {
/**
Expand Down Expand Up @@ -322,6 +324,8 @@ case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx:
* @param claimHtlcTxs txs claiming HTLCs. There will be one entry for each pending HTLC. The value will be None
* only for incoming HTLCs for which we don't have the preimage (we can't claim them yet).
* @param claimAnchorTxs txs spending anchor outputs to bump the feerate of the commitment tx (if applicable).
* We currently only claim our local anchor, but it would be nice to claim both when it is
* economical to do so to avoid polluting the utxo set.
*/
case class RemoteCommitPublished(commitTx: Transaction, claimMainOutputTx: Option[ClaimRemoteCommitMainOutputTx], claimHtlcTxs: Map[OutPoint, Option[ClaimHtlcTx]], claimAnchorTxs: List[ClaimAnchorOutputTx], irrevocablySpent: Map[OutPoint, Transaction]) extends CommitPublished {
/**
Expand Down Expand Up @@ -455,39 +459,39 @@ final case class DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments: Com

/**
* @param initFeatures current connection features, or last features used if the channel is disconnected. Note that these
* features are updated at each reconnection and may be different from the channel permanent features
* (see [[ChannelFeatures]]).
* features are updated at each reconnection and may be different from the channel permanent features
* (see [[ChannelFeatures]]).
*/
final case class LocalParams(nodeId: PublicKey,
fundingKeyPath: DeterministicWallet.KeyPath,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
channelReserve: Satoshi,
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
maxAcceptedHtlcs: Int,
isFunder: Boolean,
defaultFinalScriptPubKey: ByteVector,
walletStaticPaymentBasepoint: Option[PublicKey],
initFeatures: Features)
case class LocalParams(nodeId: PublicKey,
fundingKeyPath: DeterministicWallet.KeyPath,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
channelReserve: Satoshi,
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
maxAcceptedHtlcs: Int,
isFunder: Boolean,
defaultFinalScriptPubKey: ByteVector,
walletStaticPaymentBasepoint: Option[PublicKey],
initFeatures: Features)

/**
* @param initFeatures see [[LocalParams.initFeatures]]
*/
final case class RemoteParams(nodeId: PublicKey,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
channelReserve: Satoshi,
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
maxAcceptedHtlcs: Int,
fundingPubKey: PublicKey,
revocationBasepoint: PublicKey,
paymentBasepoint: PublicKey,
delayedPaymentBasepoint: PublicKey,
htlcBasepoint: PublicKey,
initFeatures: Features,
shutdownScript: Option[ByteVector])
case class RemoteParams(nodeId: PublicKey,
dustLimit: Satoshi,
maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi
channelReserve: Satoshi,
htlcMinimum: MilliSatoshi,
toSelfDelay: CltvExpiryDelta,
maxAcceptedHtlcs: Int,
fundingPubKey: PublicKey,
revocationBasepoint: PublicKey,
paymentBasepoint: PublicKey,
delayedPaymentBasepoint: PublicKey,
htlcBasepoint: PublicKey,
initFeatures: Features,
shutdownScript: Option[ByteVector])

object ChannelFlags {
val AnnounceChannel = 0x01.toByte
Expand Down
Loading