-
Notifications
You must be signed in to change notification settings - Fork 622
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
fix: FeeRate::checked_mul_by_weight should scale output down by 1000 #2182
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,10 +82,12 @@ impl FeeRate { | |
|
||
/// Checked weight multiplication. | ||
/// | ||
/// Computes `self * rhs` where rhs is of type Weight. `None` is returned if an overflow | ||
/// occurred. | ||
/// Computes the absolute fee amount for a given [`Weight`] at this fee rate. | ||
/// | ||
/// `None` is returned if an overflow occurred. | ||
pub fn checked_mul_by_weight(self, rhs: Weight) -> Option<Amount> { | ||
self.0.checked_mul(rhs.to_wu()).map(Amount::from_sat) | ||
let sats = self.0.checked_mul(rhs.to_wu())?.checked_add(999)? / 1000; | ||
Some(Amount::from_sat(sats)) | ||
} | ||
|
||
/// Calculates fee by multiplying this fee rate by weight, in weight units, returning `None` | ||
|
@@ -95,13 +97,14 @@ impl FeeRate { | |
/// | ||
/// # Examples | ||
/// | ||
/// ```no_run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure, but I think it was because the example didn't assert or panic, so running it would be pointless. Now that we have an |
||
/// ``` | ||
/// # use bitcoin::{absolute, transaction, FeeRate, Transaction}; | ||
/// # // Dummy transaction. | ||
/// # let tx = Transaction { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] }; | ||
/// | ||
/// let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/vbyte is valid"); | ||
/// let fee = rate.fee_wu(tx.weight()); | ||
/// let fee = rate.fee_wu(tx.weight()).unwrap(); | ||
/// assert_eq!(fee.to_sat(), tx.vsize() as u64); | ||
/// ``` | ||
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) } | ||
|
||
|
@@ -209,12 +212,20 @@ mod tests { | |
|
||
#[test] | ||
fn checked_weight_mul_test() { | ||
let weight = Weight::from_wu(10); | ||
let fee: Amount = FeeRate(10).checked_mul_by_weight(weight).expect("expected Amount"); | ||
let weight = Weight::from_vb(10).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why change this to Suggest:
Also, the current expect messages suck (sorry). It should say something like "Amount not to overflow". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same reason as for this change: intuition for the numbers of the test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's already two acks, and I think it's good to get this fixed asap, so no more changes needed. Although, I still feel like
since we start at 10 Weight units, and then add 1_000, inside checked_mul, this results in 1,000 Wu * 10 sats / 1,000 Wu = 1 sat. The multiplication by 4 when using |
||
let fee: Amount = FeeRate::from_sat_per_vb(10) | ||
.unwrap() | ||
.checked_mul_by_weight(weight) | ||
.expect("expected Amount"); | ||
Kixunil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert_eq!(Amount::from_sat(100), fee); | ||
|
||
let fee = FeeRate(10).checked_mul_by_weight(Weight::MAX); | ||
assert!(fee.is_none()); | ||
|
||
let weight = Weight::from_vb(3).unwrap(); | ||
let fee_rate = FeeRate::from_sat_per_vb(3).unwrap(); | ||
let fee = fee_rate.checked_mul_by_weight(weight).unwrap(); | ||
assert_eq!(Amount::from_sat(9), fee); | ||
} | ||
|
||
#[test] | ||
|
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, but wouldn't this be simpler read using checked_mul instead of checked_add?
suggest
I don't really understand why the checked_add here.
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.
That would produce an incorrect result. I'm guessing you meant
checked_div
? Checked division isn't necessary as we're dividing by a fixed non-zero constant.The
.checked_add(999)
call is there to ensure we compute a ceiling without overflowing. See theimpl Mul<FeeRate> for Weight
implementation below at L137 for an example that isn't overflow-safe.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.
Ah, alright, this is confusing. It feels like what we really want is for
rhs: Weight
to instead beKWU
instead of WU. Right?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.
In other-words, if fee_rate is in sats/kwu, then wouldn't the interface be simpler as: sats/kwu * kwu = sats
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 i suppose that's a good way to visualize it, but we couldn't implement it that way. We'd have to represent
KWU
as a float (or a new struct type) to perform accurate arithmetic on weight-unit numbers less than 1000.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, exactly, like here:
https://github.com/rust-bitcoin/rust-bitcoin/pull/1787/files#diff-c2ea40075e93ccd068673873166cfa3312ec7439d6bc5a4cbc03e972c7e045c4R16
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.
Looks like #1787 also fixes the same bug as this PR. Although it's much bigger and has been held up since April so maybe a small patch like this PR might be appropriate until #1787 can be merged. I filed this PR because I got unexpected results back from
FeeRate::fee_wu
. #1787 is probably a better place to consider refactoring APIs; this PR is just a hotfix after all.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.
Definitely let's get this fix in here. No point waiting for #1787
I'd be in favor of making a point release as well.
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.
Yeah definitively better to not wait for #1787