-
Notifications
You must be signed in to change notification settings - Fork 267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replaceable txs fee bumping #2113
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2113 +/- ##
==========================================
- Coverage 87.62% 87.46% -0.17%
==========================================
Files 166 169 +3
Lines 12886 13139 +253
Branches 543 559 +16
==========================================
+ Hits 11292 11492 +200
- Misses 1594 1647 +53
|
d24446e
to
795daf7
Compare
be83b8c
to
af721ba
Compare
When a channel is force-closed, we tell the `TxPublisher` the block before which the transaction should be confirmed. Then it will be up to the `TxPublisher` to set the feerate appropriately and fee-bump whenever it becomes necessary.
We previously evaluated the feerate when we issued the command to publish a transaction. However, the transaction may actually be published many blocks later (especially for HTLC-timeout transactions, for which we may issue commands long before the timeout has been reached). We change the command to include the deadline, and evaluate the feerate to meet that deadline when we're ready to broadcast a valid transaction.
We should retry replaceable transactions whenever we find a conflict in the mempool: as we get closer to the deadline, we will try higher fees. This ensures we either succeed in replacing the mempool transaction, or it gets confirmed before the deadline, which is good as well. In particular, this handles the case where we restart our node. The mempool conflict can be a transaction that we submitted ourselves, but we've lost the context in the restart. We want to keep increasing the fees of that transaction as we get closer to the deadline, so we need this retry mechanism.
This commit contains very minimal logic changes: it mostly moves code from the ReplaceableTxPublisher to two new actors (the first one to check preconditions, the second one to encapsulate the logic for funding and signing replaceable transactions). This makes it easier to understand the state machine involved in each of these steps, and will make it easier to introduce fee-bumping by reusing these actors with minimal changes.
af721ba
to
27d989a
Compare
Once we've published a first version of a replaceable transaction, at every new block we evaluate whether we should RBF it. When we choose to RBF, we try to reuse the previous wallet inputs and simply lower the change amount. When that's not possible, we ask bitcoind to add more wallet inputs.
27d989a
to
d1209ad
Compare
I have made additional E2E tests on regtest, with pending htlc-success and htlc-timeout. I have run a modified version of eclair where I increased the feerate every minute (using the The transactions are correctly regularly fee-bumped, the state machine seems to correctly handle both cases:
It is working ok, nothing strange is happening. When we run out of utxos, funding simply fails and sends event to the node operator (at that point eclair cannot do anything until the bitcoind wallet receives new utxos to use for fee-bumping). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review for 3b1537e.
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala
Outdated
Show resolved
Hide resolved
* Add a BlockHeight type * Rename deadline to confirmation target * Add comments on HtlcTx type inconsistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review for 9bfdd90.
eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
* Remove toString on BlockHeight * Add confirmBefore to ReplaceableTransactionWithInputInfo * Add new codecs to add confirmation target to txs
eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review for cc65aee, not much to say.
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass on d1209ad.
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
This avoids passing around fields that are unchanged and repeating a long list of arguments in each behavior.
* Protect against herd effect on new blocks * MempoolTxMonitor broadcasts tx status at every block
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/MempoolTxMonitor.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisher.scala
Outdated
Show resolved
Hide resolved
We only need a Stop message at the `FinalTxPublisher` and `ReplaceableTxPublisher` level, their child actors will be automatically stopped whenever these parent actors stop themselves.
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Show resolved
Hide resolved
Instead of letting the TxPublisher create a new, competing replaceable attempt, we can simply tell the existing one to update its confirmation target.
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/TxPublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala
Show resolved
Hide resolved
eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
Phew, that was something... but the end result is worth the effort!
Props for having gone the extra mile to facilitate the work of the reviewer. 👍
This feature is now ready to be widely deployed. The transaction format has been tested for a long time (between Phoenix iOS and our node) and automatic fee-bumping has been implemented in #2113.
Alright, a word of advice before we start: this PR is not for the faint of heart.
If you're thinking "I have 30 minutes to spare, let's give this a quick review", you're going to be disappointed.
This is the kind of PR that will keep you awake at night, wondering why you even got into bitcoin in the first place (maybe it wasn't that bad doing boring API plumbing work in a SaaS company after all!).
But if you're one of the brave few who like a challenge, clear up your agenda for the next few days, grab a lot of coffee, get into your cozy don't-disturb-me-until-I'm-done clothes, and read on. 😎 ⚡
In this PR, we introduce a deadline to replaceable transactions, and do the best we can to get these transactions confirmed before we reach that deadline.
For htlc transactions, that deadline is the htlc expiry:
For commitment transactions, there are two cases:
You may be a bit lost in all the actors in the
publish
package, so here is a rough explanation of why the existing layers are important. We need to evaluate the feerate based on the deadline and the current block height only once we're ready to broadcast the transaction: this means it has to be inside theReplaceableTxPublisher
actor, once all the timelocks and preconditions have been fulfilled. But then why do we also need a retry mechanism in theTxPublisher
actor? There are a few reasons for that. The first reason is to handle broadcast failures: if our transaction is evicted from the mempool (e.g. replaced by an external transaction), this bubbles up to theTxPublisher
, who will then choose whether we should retry or not (depending on whether the external conflicting transaction gets confirmed or not). The second reason is to handle node restarts: we then lose the intermediate publishing state, but we may have put some transactions in the mempool. TheTxPublisher
will ensure we try different feerates as we get closer to the deadline, and eventually replace the mempool transaction if its feerate was too low. A third reason is that deadlines can change: we may initially have no htlcs to claim from a commit tx, but then we later receive a preimage, and realize that we now need to make the commit tx confirm more quickly than we thought. This request will go through theTxPublisher
(which is the entry point for channel requests), which can create a newReplaceableTxPublisher
to replace the pending one.Each commit is self-contained a has a detailed commit message. I recommend reviewing commit-by-commit to see each step taken towards the end goal. However, after that it may make sense to review everything at once, to zoom out and ensure that this doesn't have any fundamental design flaw.
I thought about splitting this PR into multiple PRs, but the issue is that using only parts of it is less safe than the current
master
branch: you really need to have the fee-bumping mechanism (that comes in the last commit) to make what the previous commits do safe. Since we know of a few node operators runningmaster
on mainnet, I think it's better to merge everything at once.The things we do in the
ReplaceableTxFunder
actor to reach the right feerate are very ugly, butbitcoind
doesn't give us the tools yet to make it better (because we have inputs that are external to thebitcoind
wallet). Please help me lobby for bitcoin/bitcoin#23201 (and a follow-up PR to add the same arguments to thebumpfee
RPCs) to allow us to remove those horrible hacks!