-
Notifications
You must be signed in to change notification settings - Fork 422
Put a 10_000vByte cap on HolderHTLCOutput 0FC package templates
#4129
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
base: main
Are you sure you want to change the base?
Put a 10_000vByte cap on HolderHTLCOutput 0FC package templates
#4129
Conversation
|
👋 Thanks for assigning @TheBlueMatt as a reviewer! |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4129 +/- ##
==========================================
+ Coverage 88.63% 88.83% +0.20%
==========================================
Files 180 180
Lines 134878 137390 +2512
Branches 134878 137390 +2512
==========================================
+ Hits 119543 122048 +2505
+ Misses 12567 12529 -38
- Partials 2768 2813 +45
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
lightning/src/chain/package.rs
Outdated
| // See rust-bitcoin to_vbytes_ceil | ||
| let self_vbytes = (self.weight() + 3) / 4; // This is the weight of the witnesses alone, we need to add more here | ||
| let other_vbytes = (other.weight() + 3) / 4; | ||
| // What is a good offset to use here to leave room for the user-provided input-output pair? |
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.
Hmmmmmmmmm, I wonder if we shouldn't just not merge and then do the work in the events::bump_transaction module and add like a needs_v3_transaction: Option<()> in the event? That way its at least pretty explicit.
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.
You mean aggregate in bump_transaction ? How about restrict ourselves to aggregate max 25 HTLCs ? This should be good for any conceivable user-provided input-output pair.
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.
No as in break an aggregation in bump_transaction
63e8f1c to
db273bb
Compare
db273bb to
107eb25
Compare
|
🔔 1st Reminder Hey @TheBlueMatt! This PR has been waiting for your review. |
| // Cap the size of transactions claiming `HolderHTLCOutput` in 0FC channels. | ||
| // Otherwise, we could hit the max 10_000vB size limit on V3 transactions | ||
| // (BIP 431 rule 4). | ||
| 25 |
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.
I guess this is okay, but I thought the point of pulling this out into the bump_transaction logic is so that we could do the split after we see the inputs the user wants to contribute so that we can get close to 10k without exceeding it, rather than picking some random "max overhead" constant.
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.
Thanks yea let me know the commit below sounds
107eb25 to
c222c41
Compare
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.
approach lgtm
| let expected_signed_tx_weight = unsigned_tx_weight + total_satisfaction_weight; | ||
| if expected_signed_tx_weight >= max_weight { | ||
| let extra_weight = expected_signed_tx_weight - max_weight; | ||
| let htlcs_to_remove = (extra_weight / chan_utils::htlc_timeout_tx_weight(channel_type) |
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.
Not critical, but I think these weights include transaction overhead (version etc) so we'll overcount by 40 wu per htlc?
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.
Good catch, we should just use the weight of the HTLC input-output pair
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.
thanks ! yes this likely resolves the weird + 2 I've got below
| if expected_signed_tx_weight >= max_weight { | ||
| let extra_weight = expected_signed_tx_weight - max_weight; | ||
| let htlcs_to_remove = (extra_weight / chan_utils::htlc_timeout_tx_weight(channel_type) | ||
| // If we remove extra_weight / timeout_weight + 1 we sometimes still land above max_weight |
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.
I assume this wouldn't be an issue if we just round up the integer division?
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.
likely it's because htlc_timeout_tx_weight is the weight of the full-transaction, but we want to only count the bytes from the single input-output pair
| // (BIP 431 rule 4). | ||
| chan_utils::TRUC_MAX_WEIGHT | ||
| } else { | ||
| u64::MAX |
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.
Might as well cap this to the max weight we can relay?
| engine.input(&htlc.commitment_txid.to_byte_array()); | ||
| engine.input(&htlc.htlc.transaction_output_index.unwrap().to_be_bytes()); | ||
| } | ||
| let utxo_id = ClaimId(Sha256::from_engine(engine).to_byte_array()); |
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.
Wouldn't we want to reuse the same ID until we move on to the next batch?
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.
Yea, was just wondering this - should we use the one from the event at least until we split the batch, which would work in the common case. Might want to sort the outputs so that we use them in a deterministic order (I imagine they are in the event, but...)
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.
addressed below, don't quiet yet sort the HTLCs, need to look into whether they are already deterministic in the event.
| }); | ||
| htlc_tx.input.push(htlc_input); | ||
| let htlc_output = htlc_descriptor.tx_output(&self.secp); | ||
| htlc_tx.output.push(htlc_output); |
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.
Do we want to break out of this loop early if we get close to the max_weight? Would mean generating the claim ID during this loop rather than separately but that's trivial. Would avoid an extra coin-selection pass in a few cases, but its not critical.
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.
addressed below
8d2b31c to
4d54289
Compare
|
@wpaulino Carla raised this question: what do we do for the 1000vB limit on the anchor transaction ? For now I opted for documentation + debug assertion in 952a6b1 |
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.
I was wondering if we add some kind of weight budget to the select_confirmed_utxos call.
This is something I was thinking about when reviewing this PR, seems reasonable for the anchor bump case where we don't have any outputs that we could trim.
For HTLC batching, the current approach of selecting a set of HTLCs we think will fit still seems appropriate, but a weight limit would be nice to have.
lightning/src/ln/chan_utils.rs
Outdated
| const TXIN_WEIGHT: u64 = 41 * WITNESS_SCALE_FACTOR as u64; | ||
| const TXOUT_WEIGHT: u64 = 43 * WITNESS_SCALE_FACTOR as u64; | ||
| let witness_weight = | ||
| if channel_type_features.supports_anchors_zero_fee_htlc_tx() { 327 } else { 324 }; |
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.
nit: could use HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT and HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT here (+ the timeout versions in htlc_timeout_input_output_pair_weight).
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.
thanks again went ahead and cleanup the constants, see below
952a6b1 to
8614904
Compare
|
🔔 2nd Reminder Hey @wpaulino! This PR has been waiting for your review. |
8614904 to
dcc361d
Compare
|
I added a
|
3fadf50 to
837b9cc
Compare
| /// provided, in which case a zero-value empty OP_RETURN output can be used instead. | ||
| /// 3. Enough inputs must be selected/contributed for the resulting transaction (including the | ||
| /// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`. | ||
| /// 4. The final transaction must have a weight smaller than `max_tx_weight`. |
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.
This could probably use additional docs. Something about it being okay to return an Err if it can't be met and that it'll be driven again.
|
BDK does not yet have an equivalent |
Yea, it would be nice, but also I think its okay, it should be rare for the coin selector to give us 11KvB in inputs... |
The bottleneck is the anchor tx - that one is limited to 1000vB. Should be fine for most single-sig wallets. |
lightning/src/ln/chan_utils.rs
Outdated
| pub const TRUC_CHILD_MAX_WEIGHT: u64 = 1000 * WITNESS_SCALE_FACTOR as u64; | ||
|
|
||
| /// The maximum weight of any standard transaction, see bitcoin/src/policy/policy.h | ||
| pub const MAX_STANDARD_TX_WEIGHT: u64 = 400_000; |
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.
Already defined as bitcoin::policy::MAX_STANDARD_TX_WEIGHT
lightning/src/ln/chan_utils.rs
Outdated
| } | ||
|
|
||
| /// Gets the weight of a single input-output pair in HTLC-success transactions | ||
| pub fn htlc_success_input_output_pair_weight(channel_type_features: &ChannelTypeFeatures) -> u64 { |
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.
This method is only intended to be used for anchor channels but the name doesn't make that clear
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.
aggregated_htlc_success_input_output_pair_weight ?
| let change_script = self.source.get_change_script().await?; | ||
| let max_utxo_selection_weight = max_tx_weight | ||
| - preexisting_tx_weight | ||
| - (change_script.len() as u64 + 8 + 1) * WITNESS_SCALE_FACTOR as u64; |
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.
I understand we're being conservative here by considering the change script weight even though we may not end up having one. Unfortunately, calling this too early before we know if we need it could lead to address inflation.
| } | ||
| if selected_amount < target_amount_sat + total_fees { | ||
| if selected_amount < target_amount_sat + total_fees | ||
| || selected_utxos_weight > max_utxo_selection_weight |
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.
Would be nice to have weight condition logged separately
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.
I'm going to keep a single log that mentions both conditions. For example we could fail to meet the target feerate condition because we've been trimming UTXOs to satisfy the max weight condition.
| let min_weight_to_remove = USER_COINS_WEIGHT_BUDGET; | ||
| let htlcs_to_remove = min_weight_to_remove | ||
| / chan_utils::htlc_timeout_input_output_pair_weight(channel_type) | ||
| + 1; |
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.
Why + 1?
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.
The idea here was to remove at least USER_COINS_WEIGHT_BUDGET from the total preexisting tx size.
| let utxo_id = if broadcasted_htlcs == 0 { | ||
| claim_id |
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.
It's not just the first case. For every time we start a new batch, we should reuse claim_id until we move on to the next.
| } | ||
|
|
||
| #[test] | ||
| fn test_htlc_claim_chunking() { |
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.
I'd also like to see tests for when too many inputs are contributed so we have to lower the batch size
|
🔔 1st Reminder Hey @carlaKC! This PR has been waiting for your review. |
|
🔔 2nd Reminder Hey @carlaKC! This PR has been waiting for your review. |
3ed04aa to
92d1ce7
Compare
| } | ||
| while !selected_utxos.is_empty() | ||
| && selected_amount - selected_utxos.front().unwrap().0.output.value | ||
| >= target_amount_sat + total_fees |
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.
Should this be total_fees - selected_utxos.front().unwrap().1 to also remove the fees for this utxo if we're going to drop it?
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.
yes ! thank you
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.
lgtm, two non-blocking micro-nits
| .ok_or_else(|| { | ||
| log_debug!( | ||
| self.logger, | ||
| "max_tx_weight is too small to accomodate the preexisting tx weight plus a P2WSH/P2TR output" |
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.
s/accomodate/accommodate
| /// 4. The final transaction must have a weight smaller than `max_tx_weight`; if this | ||
| /// constraint can't be met, return an `Err`. In the case of counterparty-signed HTLC | ||
| /// transactions, we will remove a chunk of HTLCs and try your algorithm again. Anchor | ||
| /// transactions cannot be trimmed, so if you fail to meet the `max_tx_weight` we will |
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.
nit: "fail to meet max_tx_weight" makes it sound like this is a goal that we want to reach, rather than a limit we should stay under.
Otherwise, we could hit the max 10_000vB size limit on V3 transactions (BIP 431 rule 4). Also introduce a `max_tx_weight` parameter to `select_confirmed_utxos`. This constraint makes sure anchor and HTLC transactions in 0FC channels satisfy the `TRUC_MAX_WEIGHT` and the `TRUC_CHILD_MAX_WEIGHT` maximums. Expand the coin-selection algorithm provided for any `T: WalletSource` to satisfy this new constraint.
bdfedc4 to
386075d
Compare
|
🔔 1st Reminder Hey @TheBlueMatt! This PR has been waiting for your review. |
Otherwise, we could potentially hit the max 10_000vB size limit on V3 transactions (BIP 431 rule 4) if we aggregate enough HTLC-claims together.