-
Notifications
You must be signed in to change notification settings - Fork 376
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
Add min feerate checks #1552
Add min feerate checks #1552
Conversation
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.
ACK ba8d54e
lightning/src/chain/package.rs
Outdated
@@ -777,12 +777,15 @@ fn compute_fee_from_spent_amounts<F: Deref, L: Deref>(input_amounts: u64, predic | |||
L::Target: Logger, | |||
{ | |||
let mut updated_feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64; | |||
assert!(updated_feerate >= FEERATE_FLOOR_SATS_PER_KW 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.
Mh, do we really want to assert
and panic here if something goes wrong?
Couldn't this just default to the floor, i.e., updated_feerate = max(updated_feerate, FEERATE_FLOOR_SATS_PER_KW)
?
Just raising the question, but maybe panicking out is exactly what we want 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.
Hmm yeah I get what you mean, but if we're getting strange feerates from our estimator then maybe we'd prefer to panic instead of silently defaulting? Although, I do think we need some sort of panic message at the least which is just an extra arg here. 🤷♂️
Would also be keen to know if this is what we normally want with these kinds of things in LDK.
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 add the same thought if we should assert
or panic
. Not sure if we have yet consistent defensive programming recommendations.
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 not a huge fan of asserting on these, I agree its nice to check the users' response, but if we're gonna add code around all the fee estimation fetches, we should just do the cmp::max
ourself IMO.
Cool, that sounds good. Happy to do this. Do we then just get rid of the trait comments about bothering with |
I think we should leave it on the trait, or at least mention that we'll always use at least 253. |
Personally, I would prefer if we still wrap that check around |
I'm not sure I undersood - are you agreeing that you'd rather see a wrapper of the FeeEstimator that does the max for us? |
Yes, if that what you suggested as a could-be direction with your previous comment :) ? |
Heh, yea, sorry, that wasn't clear, yes, I was suggesting we just wrap the trait in some helper that does the max. |
b992a6a
to
5922330
Compare
Codecov Report
@@ Coverage Diff @@
## main #1552 +/- ##
==========================================
+ Coverage 90.81% 91.01% +0.19%
==========================================
Files 80 80
Lines 44534 45507 +973
Branches 44534 45507 +973
==========================================
+ Hits 40445 41417 +972
- Misses 4089 4090 +1
Continue to review full report at Codecov.
|
/// | ||
/// This method can be implemented with the following unit conversions: | ||
/// * max(satoshis-per-byte * 250, 253) | ||
/// * max(satoshis-per-kbyte / 4, 253) |
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 unit conversions are still really useful.
F::Target: FeeEstimator, | ||
{ | ||
pub(crate) fn new(fee_estimator: &'a F) -> Self | ||
where |
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 shoul dbe able to drop the where clause here - its redundant.
|
||
/// Wraps a `FeeEstimator` so that any fee estimations provided by it | ||
/// are bounded below by `FEERATE_FLOOR_SATS_PER_KW` (253 sats/KW) | ||
pub(crate) struct LowerBoundedFeeEstimator<'a, F: Deref>(pub(crate) &'a F) |
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.
Instead of storing a reference to a deref to a feeestimator and constructing the lowerboundedestimator whenever we need to get fees, lets just store the full deref to a feeestimator here, and then update places in the crate to store a LowerBoundedFeeEstimator, so its always that way and its a bit harder to forget to do the wrapping.
} | ||
} | ||
|
||
impl<F: Deref> FeeEstimator for LowerBoundedFeeEstimator<'_, F> |
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.
Lets not bother with the trait and just implement directly, then in the codebase make things take a LowerBoundFeeEstimator
.
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, will make things simpler and then there's no forgetting to create a LowerBoundFeeEstimator
as you mentioned :)
fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32; | ||
} | ||
|
||
/// Minimum relay fee as required by bitcoin network mempool policy. | ||
pub const MIN_RELAY_FEE_SAT_PER_1000_WEIGHT: u64 = 4000; | ||
/// Minimum feerate that takes a sane approach to rounding | ||
pub const FEERATE_FLOOR_SATS_PER_KW: u32 = 253; |
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.
"Minimum feerate that takes a sane approach to bitcoind weight-to-vbytes rounding"
I don't know if this subtlety is documented anywhere well, so feel free to ref Rusty's commit : ElementsProject/lightning@2e687b9
4093743
to
1956024
Compare
/// | ||
/// This method can be implemented with the following unit conversions: | ||
/// This wrapped method will be implemented with the following unit conversions: |
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 point of listing the conversions is the user may need to convert from whatever unit their backend feerate API returns to the units here. Thus, its not that it "will" be implemented with the conversions, but that users "can" implement it using the listed conversions (but drop the max/253 bit) if they want.
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.
Oh yeah, of course. I didn't even realise that was the main point of the comment here. I had just considered the max
part. Will fix.
@@ -1129,7 +1129,7 @@ impl<Signer: Sign> ChannelMonitor<Signer> { | |||
&self, | |||
updates: &ChannelMonitorUpdate, | |||
broadcaster: &B, | |||
fee_estimator: &F, | |||
fee_estimator: &LowerBoundedFeeEstimator<F>, |
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 public - let's drop the wrapper from the parameter.
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.
Seems we run into a few tricky issues here as we cannot clone a Deref to a FeeEstimator so that we can wrap it further down the line. Unless I'm missing something subtle and rusty. The main problem is we take in &F
here and in some other places and we can't move out of the shared reference when we need to wrap.
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.
Uhhhh, right, uhhhh, uhhhh, so I think it'll work if we impl <D: Deref> FeeEstimator for D where D::Target: FeeEstimator
. It doesn't currently break anything in test, dont think it'll break downstream stuff.
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.
Alright, let me go that direction and see :)
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.
Seems to do the trick!
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 general I think we need to go that way for all of our traits, but that's a rather large change and dealing with the bindings for it sounds....un-fun.
|
||
/// Wraps a `Deref` to a `FeeEstimator` so that any fee estimations provided by it | ||
/// are bounded below by `FEERATE_FLOOR_SATS_PER_KW` (253 sats/KW) | ||
pub struct LowerBoundedFeeEstimator<F: Deref>(pub F) |
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 don't think we want this to be public at all - it should be pub(crate)
instead, I think.
/// are bounded below by `FEERATE_FLOOR_SATS_PER_KW` (253 sats/KW) | ||
pub struct LowerBoundedFeeEstimator<F: Deref>(pub F) | ||
where | ||
F::Target: FeeEstimator; |
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: does this really need its own line?
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.
Think rustfmt got me here 😅. Will fix.
Rebasing and directly modifying commits just this one last time before more review as a lot has changed from the previous revision. |
1956024
to
98ae8bf
Compare
Let me know when you're ready for more review here. |
7179109
to
9ec469f
Compare
/// | ||
/// This method can be implemented with the following unit conversions: | ||
/// The following unit conversions can be used to convert to sats/KW. Note that it is not | ||
/// necessary to use max() as the minimum of 253 will be enforced by LDK: |
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: then why even mention the 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.
True story. Remove this and just leave in the max in the example conversion or also get rid of that?
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 think just skip the max but leave the conversions.
lightning/src/chain/onchaintx.rs
Outdated
@@ -667,7 +667,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> { | |||
} | |||
} | |||
for (_, request) in bump_candidates.iter_mut() { | |||
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &request, &&*fee_estimator, &&*logger) { | |||
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &request, &fee_estimator, &&*logger) { |
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: you can drop the &
on the fee_estimator entirely. Fewer &
s good - we're already calling a fee estimator that is a reference to a reference to a reference :(
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 seems like quite the onion 😅
lightning/src/ln/channelmanager.rs
Outdated
@@ -3550,7 +3550,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |||
PersistenceNotifierGuard::optionally_notify(&self.total_consistency_lock, &self.persistence_notifier, || { | |||
let mut should_persist = NotifyOption::SkipPersist; | |||
|
|||
let new_feerate = self.fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); | |||
let new_feerate = LowerBoundedFeeEstimator::new(&self.fee_estimator).get_est_sat_per_1000_weight(ConfirmationTarget::Normal); |
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.
Instead of peppering these everywhere, the ChannelManager::fee_estimator
field should be a LowerBoundedFeeEstimator
, then its very hard to screw up and forget to wrap.
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.
Hmm, ok so no need to worry about serialisation here? Field is completely ignored in public contract?
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 fee estimator itself? No, its not serialized, the user passes in a new fee estimator to the ChannelManager deserialize method.
lightning/src/ln/channelmanager.rs
Outdated
@@ -4723,7 +4723,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |||
if chan_entry.get().get_counterparty_node_id() != *counterparty_node_id { | |||
return Err(MsgHandleErrInternal::send_err_msg_no_close("Got a message for a channel from the wrong node!".to_owned(), msg.channel_id)); | |||
} | |||
let (closing_signed, tx) = try_chan_entry!(self, chan_entry.get_mut().closing_signed(&self.fee_estimator, &msg), channel_state, chan_entry); | |||
let (closing_signed, tx) = try_chan_entry!(self, chan_entry.get_mut().closing_signed(&&self.fee_estimator, &msg), channel_state, chan_entry); |
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 should be able to drop a &
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.
Hmm yeah I think I can now with how FeeEstimator currently looks.
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.
So yeah we don't need it in this case, but if we make ChannelManager::fee_estimator
a LowerBoundedFeeEstimator
, then it seems necessary as we're working with something that's not a Deref to a FeeEstimator.
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.
Right, you'll need a single &
here, which is a reference to a LowerBoundedFeeEstimator
which holds a Deref
to a FeeEstimator
. Its still two references, but shouldn't need to touch this line of code, I belive?
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 think the main issue here is that the signature expects &F
for first argument and F: Deref where F::Target: FeeEstimator.
So it's a ref to a deref to a FeeEstimator, so that's a bit of a problem here :/
Errors with just a single &
in the case where ChannelManager::fee_estimator is a LowerBoundedFeeEstimator, because of the signature.
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.
Right, but I thought we were moving to channel.rs
only ever seeing LowerBoundedFeeEstimator
s, never a FeeEstimator
itself.
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.
Sorry, thought that was a public API but seems like Channel
itself is not. So I can go ahead and adjust the signature.
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! okay, wasn't sure what the confusion was. Thanks.
lightning/src/ln/channel.rs
Outdated
@@ -917,7 +917,7 @@ impl<Signer: Sign> Channel<Signer> { | |||
return Err(APIError::APIMisuseError { err: format!("Holder selected channel reserve below implemention limit dust_limit_satoshis {}", holder_selected_channel_reserve_satoshis) }); | |||
} | |||
|
|||
let feerate = fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); | |||
let feerate = LowerBoundedFeeEstimator::new(fee_estimator).get_est_sat_per_1000_weight(ConfirmationTarget::Normal); |
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.
We don't need to wrap the estimator in channel.rs anymore, no? Plus a few places further down.
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.
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.
LGTM. Feel free to squash and then let's land this!
lightning/src/ln/channel.rs
Outdated
@@ -4097,7 +4099,7 @@ impl<Signer: Sign> Channel<Signer> { | |||
|
|||
if !self.is_outbound() { | |||
if let Some(msg) = &self.pending_counterparty_closing_signed.take() { | |||
return self.closing_signed(fee_estimator, &msg); | |||
return self.closing_signed(&fee_estimator, &msg); |
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 shouldnt need to touch this line now.
`LowerBoundedFeeEstimator` is a wrapper for `Deref`s to `FeeEstimator`s that limits the get_est_sat_per_1000_weight() method to no less than 253 sats/kW.
fb0a015
to
7bc6d0e
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.
Code Review ACK 7bc6d0e
Fixes #1016