Configurable fee target#1066
Conversation
c414f8c to
f0627fd
Compare
1af515c to
59d96de
Compare
add5e2c to
0173668
Compare
0173668 to
8099297
Compare
# Conflicts: # eclair-core/src/test/scala/fr/acinq/eclair/io/PeerSpec.scala
Codecov Report
@@ Coverage Diff @@
## master #1066 +/- ##
==========================================
+ Coverage 73.7% 82.49% +8.79%
==========================================
Files 126 99 -27
Lines 8458 7581 -877
Branches 321 300 -21
==========================================
+ Hits 6234 6254 +20
+ Misses 2224 1327 -897
|
There are actually 3 stages transactions in the case of a local commit. The last stage ( |
|
How about |
Yes correct, the claim transaction spending the HTLC-2nd stage back to our wallet isn't competing with anyone and we don't need to have fast confirmation so we can let the user customize it, will add. |
| Transactions.weight2fee(feeratePerKw, closingWeight) | ||
| } | ||
|
|
||
| def firstClosingFee(nodeParams: NodeParams, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { |
There was a problem hiding this comment.
| def firstClosingFee(nodeParams: NodeParams, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { | |
| def firstClosingFee(feeTargets: FeeTargets, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { |
There was a problem hiding this comment.
Agree with the proposed change, and let's move it to last position for consistency.
There was a problem hiding this comment.
In 63becf3 we don't pass NodeParams anymore and all the feeTargets parameters have been moved to the last position
| * @return a list of transactions (one per HTLC that we can claim) | ||
| */ | ||
| def claimRemoteCommitTxOutputs(keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { | ||
| def claimRemoteCommitTxOutputs(feeTargets: FeeTargets, keyManager: KeyManager, commitments: Commitments, remoteCommit: RemoteCommit, tx: Transaction)(implicit log: LoggingAdapter): RemoteCommitPublished = { |
There was a problem hiding this comment.
Nit: I'm not sure why but I wouldn't use feeTargets as first parameter. Maybe between keyManager and commitments?
There was a problem hiding this comment.
How about moving it to last position (and update the javadoc)?
| case 12 => blocks_12 | ||
| case 36 => blocks_36 | ||
| case 72 => blocks_72 | ||
| case _ => throw new IllegalArgumentException(s"can't choose ratePerBlock=$blockTarget") |
There was a problem hiding this comment.
How about:
def getRatePerBlock(blockTarget: Int): Long = blockTarget match {
case i if i <=1 => block_1
case i if i <= 2 => blocks_2
case i if i <= 6 => blocks_6
case i if i <= 12 => blocks_12
case i if i <= 36 => blocks_36
case i if i <= 72 => blocks_72
case _ => blocks_144
}A bit more lax to reduce support requests?
There was a problem hiding this comment.
I don't like silently choosing a different feerate for the user, this can result in unexpected delays. The current block of code is wrong though (it would fail for blockTarget = 144).
There was a problem hiding this comment.
Commit 5195614 fixes the problem but it does not introduce lax behavior.
There was a problem hiding this comment.
I'd rather agree with @pm47 here: onchain fee estimation is very unreliable and it gets worse as the confirmation target goes up, so forcing users to use arbitrary targets seems a bit too strict.
I think we should be able to accept any confirmation target and convert it to a feerate
On a second thought, do we really want to offer so much flexibility? Considering that all the |
| } | ||
|
|
||
| def firstClosingFee(nodeParams: NodeParams, commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector)(implicit log: LoggingAdapter): Satoshi = { | ||
| def firstClosingFee(commitments: Commitments, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feeTargets: FeeTargets)(implicit log: LoggingAdapter): Satoshi = { |
There was a problem hiding this comment.
This make the two firstClosingFee methods very similar. Would it be a food idea to merge them?
There was a problem hiding this comment.
I split them to facilitate testing, in this test i needed to compute the expected fee for the first closing signed negotiation.
I like this suggestion, |
sstone
left a comment
There was a problem hiding this comment.
The way we estimate fees and use these estimates in helper functions is still ugly. A potential improvement would be to add a fee estimator to NodeParams, something simple like
trait FeeEstimator {
def getFeeratePerKb(target: Int) : Long
def getFeeratePerKw(target: Int) : Long
}implement it with call to Globals in Setup, and with a basic estimator that returns fix values in unit tests whenever possible, and use it to get actual fee rates that would be explicitly passed to helper functions (no more calls to Globals).
This could be done first as a refactoring that does not change how we set fees, and then we could add additional configuration options. WDYT ?
| case 12 => blocks_12 | ||
| case 36 => blocks_36 | ||
| case 72 => blocks_72 | ||
| case _ => throw new IllegalArgumentException(s"can't choose ratePerBlock=$blockTarget") |
There was a problem hiding this comment.
I'd rather agree with @pm47 here: onchain fee estimation is very unreliable and it gets worse as the confirmation target goes up, so forcing users to use arbitrary targets seems a bit too strict.
I think we should be able to accept any confirmation target and convert it to a feerate
| } | ||
|
|
||
| val localFeeratePerKw = Globals.feeratesPerKw.get.blocks_2 | ||
| val localFeeratePerKw = Globals.feeratesPerKw.get.getRatePerBlock(feeTargets.commitmentBlockTarget) |
There was a problem hiding this comment.
If we are to revisit this I'd rather pass an actual fee rate to this method instead of calling Globals.feeratesPerKw
I agree that we should avoid using |
I like this proposal too but i thought it's a huge change, if we are going to accept any block target the current structure of the |
|
Why would it be a huge change ? we could compute a fee rate for any target using the same pattern as above: // stores fee rate in satoshi/kb (1 kb = 1000 bytes)
case class FeeratesPerKB(block_1: Long, blocks_2: Long, blocks_6: Long, blocks_12: Long, blocks_36: Long, blocks_72: Long) {
require(block_1 > 0 && blocks_2 > 0 && blocks_6 > 0 && blocks_12 > 0 && blocks_36 > 0 && blocks_72 > 0, "all feerates must be strictly greater than 0")
def feeRatePerKb(target: Int) = target match {
case 1 => block_1
case 2 => blocks_2
case t if t <= 6 => blocks_6
case t if t <= 12 => blocks_12
case t if t <= 36 => blocks_36
case _ => blocks_72
}
}then our estimator is simply: val feeEstimator = new FeeEstimator {
override def getFeeratePerKb(target: Int): Long = Globals.feeratesPerKB.get().feeRatePerKb(target)
override def getFeeratePerKw(target: Int): Long = Globals.feeratesPerKw.get().feeRatePerKw(target)
}This way we can hide how our fee provider is implemented |
|
Closed in favor of #1083 |
Summary of changes
Rationale
Fee computation is a sensitive topic and directly affects the profitability of node operators, thus saving as much as possible is a priority and a nice-to-have feature. Currently eclair uses a very conservative estimation for most of the transactions, resulting in unnecessary high fees under certain scenarios; this PR aims at making this behavior configurable and give the node operators the opportunity to fine tune on chain transaction costs.
High level overview
There is a new configuration block that can be used to define default values for the block target used in the fee calculation for various transactions, currently supported are types: "funding", "commitment", "mutual-close" and "claim-main". Eclair will target the block number defined there when computing the fees, this means that according to the current feerate (still taken from the usual fee providers) we calculate the absolute transaction fee trying to target the confirmation on the N-th block where N is user defined default.
fundingis the block target for the funding transaction (you can override this when opening a channel)commitmentis the block target using when computing the commitment transaction, this is used when force closing the channel.mutual-closeblock target used when negotiating the closing transaction fee with the remote peer.claim-mainblock target is used to compute fee for the claim-main transaction, this used to spend our main channel output to our on-chain walletImplementation details
The
claim-mainblock target is applied to both the claim-main-delayed and claim-main transaction, those are logically equal and none of them is competing with the counterparty for confirmation.We still don't provide configurable block target for htlc-2nd stage transactions and main penalty transaction, this is due to the fact that in these scenarios we're competing with the counterparty and we
must act swiftly.
Transactions and block target
fundingcommitmentmutual-close( must be >=commitment)claim-mainclaim-mainclaim-mainFixes #1028