Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ eclair {

// number of blocks to target when computing fees for each transaction type
target-blocks {
// When this field is set, eclair will randomly target a more aggressive confirmation window.
// This makes eclair's fee-bumping less predictable for potentially malicious peers, and makes force-close
// transactions confirm more quickly (at the cost of paying more on-chain fees).
randomize = true
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*
Expand Down
3 changes: 2 additions & 1 deletion eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ object NodeParams extends Logging {
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")
claimMainBlockTarget = config.getInt("on-chain-fees.target-blocks.claim-main"),
randomize = config.getBoolean("on-chain-fees.target-blocks.randomize")
)

def getRelayFees(relayFeesConfig: Config): RelayFees = {
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, commitmentWithoutHtlcsBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int)
case class FeeTargets(fundingBlockTarget: Int, commitmentBlockTarget: Int, commitmentWithoutHtlcsBlockTarget: Int, mutualCloseBlockTarget: Int, claimMainBlockTarget: Int, randomize: Boolean)

/**
* @param maxExposure maximum exposure to pending dust htlcs we tolerate: we will automatically fail HTLCs when going above this threshold.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,12 @@ object ReplaceableTxPublisher {
}
}

def getFeerate(feeEstimator: FeeEstimator, confirmBefore: BlockHeight, currentBlockHeight: BlockHeight): FeeratePerKw = {
val remainingBlocks = confirmBefore - currentBlockHeight
def getFeerate(feeEstimator: FeeEstimator, confirmBefore: BlockHeight, currentBlockHeight: BlockHeight, randomize: Boolean): FeeratePerKw = {
val remainingBlocks = if (randomize) {
Random.nextLong((confirmBefore - currentBlockHeight).max(1))
} else {
confirmBefore - currentBlockHeight
}
val blockTarget = remainingBlocks match {
// If our target is still very far in the future, no need to rush
case t if t >= 144 => 144
Expand Down Expand Up @@ -140,7 +144,7 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
}

def fund(txWithWitnessData: ReplaceableTxWithWitnessData): Behavior[Command] = {
val targetFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, nodeParams.currentBlockHeight)
val targetFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, nodeParams.currentBlockHeight, nodeParams.onChainFeeConf.feeTargets.randomize)
val txFunder = context.spawn(ReplaceableTxFunder(nodeParams, bitcoinClient, txPublishContext), "tx-funder")
txFunder ! ReplaceableTxFunder.FundTransaction(context.messageAdapter[ReplaceableTxFunder.FundingResult](WrappedFundingResult), cmd, Right(txWithWitnessData), targetFeerate)
Behaviors.receiveMessagePartial {
Expand Down Expand Up @@ -173,15 +177,15 @@ private class ReplaceableTxPublisher(nodeParams: NodeParams,
case MempoolTxMonitor.TxInMempool(_, currentBlockHeight) =>
// We make sure we increase the fees by at least 20% as we get closer to the confirmation target.
val bumpRatio = 1.2
val currentFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, currentBlockHeight)
val currentFeerate = getFeerate(nodeParams.onChainFeeConf.feeEstimator, confirmBefore, currentBlockHeight, nodeParams.onChainFeeConf.feeTargets.randomize)
val targetFeerate_opt = if (confirmBefore <= currentBlockHeight + 6) {
log.debug("{} confirmation target is close (in {} blocks): bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate.max(tx.feerate * bumpRatio))
} else if (tx.feerate * bumpRatio <= currentFeerate) {
log.debug("{} confirmation target is in {} blocks: bumping fees", cmd.desc, confirmBefore - currentBlockHeight)
Some(currentFeerate)
} else {
log.debug("{} confirmation target is in {} blocks: no need to bump fees", cmd.desc, confirmBefore - currentBlockHeight)
log.debug("{} confirmation target is in {} blocks: no need to bump fees (previous feerate={}, current feerate={})", cmd.desc, confirmBefore - currentBlockHeight, tx.feerate, currentFeerate)
None
}
// We avoid a herd effect whenever we fee bump transactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ object TestConstants {
dustLimit = 1100 sat,
maxRemoteDustLimit = 1500 sat,
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 36, 12, 18),
feeTargets = FeeTargets(6, 2, 36, 12, 18, randomize = false),
feeEstimator = new TestFeeEstimator,
closeOnOfflineMismatch = true,
updateFeeMinDiffRatio = 0.1,
Expand Down Expand Up @@ -235,7 +235,7 @@ object TestConstants {
dustLimit = 1000 sat,
maxRemoteDustLimit = 1500 sat,
onChainFeeConf = OnChainFeeConf(
feeTargets = FeeTargets(6, 2, 36, 12, 18),
feeTargets = FeeTargets(6, 2, 36, 12, 18, randomize = false),
feeEstimator = new TestFeeEstimator,
closeOnOfflineMismatch = true,
updateFeeMinDiffRatio = 0.1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class FeeEstimatorSpec extends AnyFunSuite {
val defaultFeerateTolerance = FeerateTolerance(0.5, 2.0, FeeratePerKw(2500 sat), DustTolerance(15000 sat, closeOnUpdateFeeOverflow = false))

test("should update fee when diff ratio exceeded") {
val feeConf = OnChainFeeConf(FeeTargets(1, 1, 1, 1, 1), new TestFeeEstimator(), closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
val feeConf = OnChainFeeConf(FeeTargets(1, 1, 1, 1, 1, randomize = false), new TestFeeEstimator(), closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1000 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(900 sat)))
assert(!feeConf.shouldUpdateFee(FeeratePerKw(1000 sat), FeeratePerKw(1100 sat)))
Expand All @@ -38,7 +38,7 @@ class FeeEstimatorSpec extends AnyFunSuite {
test("get commitment feerate") {
val feeEstimator = new TestFeeEstimator()
val channelType = ChannelTypes.Standard
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1, randomize = false), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map.empty)

feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = FeeratePerKw(5000 sat)))
assert(feeConf.getCommitmentFeerate(randomKey().publicKey, channelType, 100000 sat, None) === FeeratePerKw(5000 sat))
Expand All @@ -53,7 +53,7 @@ class FeeEstimatorSpec extends AnyFunSuite {
val defaultMaxCommitFeerate = defaultFeerateTolerance.anchorOutputMaxCommitFeerate
val overrideNodeId = randomKey().publicKey
val overrideMaxCommitFeerate = defaultMaxCommitFeerate * 2
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map(overrideNodeId -> defaultFeerateTolerance.copy(anchorOutputMaxCommitFeerate = overrideMaxCommitFeerate)))
val feeConf = OnChainFeeConf(FeeTargets(1, 2, 6, 1, 1, randomize = false), feeEstimator, closeOnOfflineMismatch = true, updateFeeMinDiffRatio = 0.1, defaultFeerateTolerance, Map(overrideNodeId -> defaultFeerateTolerance.copy(anchorOutputMaxCommitFeerate = overrideMaxCommitFeerate)))

feeEstimator.setFeerate(FeeratesPerKw.single(FeeratePerKw(10000 sat)).copy(blocks_2 = defaultMaxCommitFeerate / 2, mempoolMinFee = FeeratePerKw(250 sat)))
assert(feeConf.getCommitmentFeerate(defaultNodeId, ChannelTypes.AnchorOutputs, 100000 sat, None) === defaultMaxCommitFeerate / 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
implicit val log: akka.event.LoggingAdapter = akka.event.NoLogging

val feeConfNoMismatch = OnChainFeeConf(
FeeTargets(6, 2, 12, 2, 6),
FeeTargets(6, 2, 12, 2, 6, randomize = false),
new TestFeeEstimator(),
closeOnOfflineMismatch = false,
1.0,
Expand Down