Fee rate check hardening - #1845
Conversation
Coverage Report for CI Build 34396838644Coverage increased (+0.2%) to 86.812%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
f01f0e4 to
2c25a0e
Compare
| let tx = self.psbt.clone().extract_tx_fee_rate_limit().map_err(|e| match e { | ||
| bitcoin::psbt::ExtractTxError::AbsurdFeeRate { fee_rate, .. } => | ||
| InternalPayloadError::OriginalPsbtAbsurdFee( | ||
| fee_rate, | ||
| bitcoin::Psbt::DEFAULT_MAX_FEE_RATE, | ||
| ), | ||
| _ => unreachable!("Input UTXOs validated and fee computed successfully"), | ||
| })?; | ||
| Ok(original_psbt_fee / tx.weight()) |
There was a problem hiding this comment.
What's the benefit of sanity checking the sender's fee rate here?
There was a problem hiding this comment.
I guess that is a pretty simple and cheap check and sort of addresses an adjecent version of your statement above We probably shouldn't even proceed with the payjoin if a sender is intentionally specifying a max fee contribution that they can't match keeping this at the OriginalPayload stage. In this case its not the max fee though but actually a selected fee by the sender
There was a problem hiding this comment.
I am also not sure if we need to do this sanity check here, as an absurdly high fee shouldn't affect anyone but the sender? In general I think a check like this should fall to the wallets to catch. Also I think this check is redundant because extract_tx_fee_rate_limit() seems to return ExtractTxError::AbsurdFeeRate by calling psbt.fee() under the hood and mapping the original Error::FeeOverflow error, but we would have already encountered said error because we already call psbt.fee() above.
If we do want to do this sanity check though I think it would be more fitting to put it into check_broadcast_suitability instead (since we are already checking for min fee rate there and intuitively seems like more appropriate place to check this). Or I guess we could also just map Error::FeeOverflow error to the OriginalPsbtAbsurdFee error when we call self.psbt.fee() above.
2c25a0e to
5cda85f
Compare
| let tx = self.psbt.clone().extract_tx_fee_rate_limit().map_err(|e| match e { | ||
| bitcoin::psbt::ExtractTxError::AbsurdFeeRate { fee_rate, .. } => | ||
| InternalPayloadError::OriginalPsbtAbsurdFee( | ||
| fee_rate, | ||
| bitcoin::Psbt::DEFAULT_MAX_FEE_RATE, | ||
| ), | ||
| _ => unreachable!("Input UTXOs validated and fee computed successfully"), | ||
| })?; | ||
| Ok(original_psbt_fee / tx.weight()) |
There was a problem hiding this comment.
I am also not sure if we need to do this sanity check here, as an absurdly high fee shouldn't affect anyone but the sender? In general I think a check like this should fall to the wallets to catch. Also I think this check is redundant because extract_tx_fee_rate_limit() seems to return ExtractTxError::AbsurdFeeRate by calling psbt.fee() under the hood and mapping the original Error::FeeOverflow error, but we would have already encountered said error because we already call psbt.fee() above.
If we do want to do this sanity check though I think it would be more fitting to put it into check_broadcast_suitability instead (since we are already checking for min fee rate there and intuitively seems like more appropriate place to check this). Or I guess we could also just map Error::FeeOverflow error to the OriginalPsbtAbsurdFee error when we call self.psbt.fee() above.
35f6215 to
221b9df
Compare
xstoicunicornx
left a comment
There was a problem hiding this comment.
Just one small new comment about accounting for dust when determining whether receiver output is of sufficient value.
DanGould
left a comment
There was a problem hiding this comment.
Some questions regarding rationale of the changes as-is
| Some(fee_output) if !owned_vouts.contains(&additional_fee_output_index) => { | ||
| let max_contribution = fee_output | ||
| .value | ||
| .checked_sub(fee_output.script_pubkey.minimal_non_dust()) |
There was a problem hiding this comment.
introducing the dust check introduces a lot of complexity beyond just preventing an underflow. It also This function uses the default value of 0.00003 BTC/kB (3 sat/vByte) also.
On the sender side, there is a defined min_fee_rate that could be provided to minimal_non_dust_custom. On the receiver side, a minimum feerate is passed in two places, once at check_broadcast_suitability and more consequentially at apply_fee_range. It seems that either of these values may be passed to a minimal_non_dust_custom function that more accurately calculates dust, if necessary.
Or wait, is it just the max_contribution effective fee rate supplied to custom, because the max fee subtracts the most sats, I think it's actually THAT supplied to _custom.
But still, taking on this responsibility of preventing dust calculations at least deserves a rationale. Is it essential?
There was a problem hiding this comment.
Eager to get this one through. Pretty sure maxadditionalfeecontribution is the one that has potential to make a dust and therefore should be provided to minimal_non_dust_custom
There was a problem hiding this comment.
Since it is looking for a feerate not an amount what exact benefit are you looking for creating some precise calculation between minimal_non_dust() and minimal_non_dust_custom(1 sat/vbyte) by using maxadditionalfeecontribution?
There was a problem hiding this comment.
Should we even be going outside of the realm of what bitcoin will generally relay through the network or should we be using something like max(min_fee_rate, 3 sat/vbyte) so the dust will always be safely relay-able and satisfy the user's expected dust min at the time of the pj.
There was a problem hiding this comment.
I went with minimal_non_dust_custom(max(min_fee_rate, FeeRate::Dust)) where FeeRate::Dust is just the dust relay min of 3 sat / vbyte so the user's preference is accounted for before relay minimum.
There was a problem hiding this comment.
On the sender side, there is a defined min_fee_rate that could be provided to minimal_non_dust_custom. On the receiver side, a minimum feerate is passed in two places, once at check_broadcast_suitability and more consequentially at apply_fee_range. It seems that either of these values may be passed to a minimal_non_dust_custom function that more accurately calculates dust, if necessary.
I don't think I'm understanding why the min fee rate should be considered when ensuring that an output is over relay-able dust limit? Wouldn't that strictly be determined by node implementation? For example, with the updated implementation here if the min fee rate is over 3 sat/vByte then we are requiring the output value to be unnecessarily high.
But still, taking on this responsibility of preventing dust calculations at least deserves a rationale. Is it essential?
Thinking on this more I tend to think this is an important point to consider. Do we need to add complexity to account for dust limits right now? I think harding the fee rate check is the more urgent item and accounting for dust limits is a completely separate potential optimization that we would want to think through carefully. There do seem to already be tools for implementors to check the minimal output value required on their own and using the 3 sat/vByte limit also makes an assumption about node dust limit configuration. So maybe for now we should just focus on the fee rate check hardening?
There was a problem hiding this comment.
Ok, will do I will create a follow-up issue for us to address this fully.
997f620 to
0c38ce1
Compare
This ensures that the sender output is capable of paying for its fee contribution otherwise it is ignored.
By calculating the sender maxadditionalfeecontribution is within the range that the sender inputs can afford to pay this should avoid most cases where a receiver ignores this contribution from the sender in the happy path.
0c38ce1 to
d641de2
Compare
This hardens the math on several areas of fee calculation. Namely this does checks that may occur in the bitcoin network but ensures that we do the checks early in the state machine ensuring that we can error out and either try again or close the session rather than accept a tx that will not succeed broadcasting.
Coded up with GLM-5.3
Pull Request Checklist
Please confirm the following before requesting review:
AI
in the body of this PR.