Skip to content

Commit e496d62

Browse files
committed
Extend OnchainTxHandler::generate_claim to optionally force feerate bump
In the next commit, we plan to extend the `OnchainTxHandler` to retry pending claims on a timer. This timer may fire with much more frequency than incoming blocks, so we want to avoid manually bumping feerates (currently by 25%) each time our fee estimator provides a lower feerate than before.
1 parent 9d5adfc commit e496d62

File tree

3 files changed

+69
-32
lines changed

3 files changed

+69
-32
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -489,9 +489,13 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
489489
///
490490
/// Panics if there are signing errors, because signing operations in reaction to on-chain
491491
/// events are not expected to fail, and if they do, we may lose funds.
492-
fn generate_claim<F: Deref, L: Deref>(&mut self, cur_height: u32, cached_request: &PackageTemplate, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u32, u64, OnchainClaim)>
493-
where F::Target: FeeEstimator,
494-
L::Target: Logger,
492+
fn generate_claim<F: Deref, L: Deref>(
493+
&mut self, cur_height: u32, cached_request: &PackageTemplate, force_feerate_bump: bool,
494+
fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
495+
) -> Option<(u32, u64, OnchainClaim)>
496+
where
497+
F::Target: FeeEstimator,
498+
L::Target: Logger,
495499
{
496500
let request_outpoints = cached_request.outpoints();
497501
if request_outpoints.is_empty() {
@@ -538,8 +542,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
538542
#[cfg(anchors)]
539543
{ // Attributes are not allowed on if expressions on our current MSRV of 1.41.
540544
if cached_request.requires_external_funding() {
541-
let target_feerate_sat_per_1000_weight = cached_request
542-
.compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority);
545+
let target_feerate_sat_per_1000_weight = cached_request.compute_package_feerate(
546+
fee_estimator, ConfirmationTarget::HighPriority, force_feerate_bump
547+
);
543548
if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
544549
return Some((
545550
new_timer,
@@ -558,7 +563,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
558563

559564
let predicted_weight = cached_request.package_weight(&self.destination_script);
560565
if let Some((output_value, new_feerate)) = cached_request.compute_package_output(
561-
predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger,
566+
predicted_weight, self.destination_script.dust_value().to_sat(),
567+
force_feerate_bump, fee_estimator, logger,
562568
) {
563569
assert!(new_feerate != 0);
564570

@@ -601,7 +607,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
601607
// counterparty's latest commitment don't have any HTLCs present.
602608
let conf_target = ConfirmationTarget::HighPriority;
603609
let package_target_feerate_sat_per_1000_weight = cached_request
604-
.compute_package_feerate(fee_estimator, conf_target);
610+
.compute_package_feerate(fee_estimator, conf_target, force_feerate_bump);
605611
Some((
606612
new_timer,
607613
package_target_feerate_sat_per_1000_weight as u64,
@@ -700,7 +706,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
700706
// Generate claim transactions and track them to bump if necessary at
701707
// height timer expiration (i.e in how many blocks we're going to take action).
702708
for mut req in preprocessed_requests {
703-
if let Some((new_timer, new_feerate, claim)) = self.generate_claim(cur_height, &req, &*fee_estimator, &*logger) {
709+
if let Some((new_timer, new_feerate, claim)) = self.generate_claim(
710+
cur_height, &req, true /* force_feerate_bump */, &*fee_estimator, &*logger,
711+
) {
704712
req.set_timer(new_timer);
705713
req.set_feerate(new_feerate);
706714
let package_id = match claim {
@@ -893,7 +901,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
893901
// Build, bump and rebroadcast tx accordingly
894902
log_trace!(logger, "Bumping {} candidates", bump_candidates.len());
895903
for (package_id, request) in bump_candidates.iter() {
896-
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(cur_height, &request, &*fee_estimator, &*logger) {
904+
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
905+
cur_height, &request, true /* force_feerate_bump */, &*fee_estimator, &*logger,
906+
) {
897907
match bump_claim {
898908
OnchainClaim::Tx(bump_tx) => {
899909
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
@@ -973,7 +983,9 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
973983
}
974984
}
975985
for ((_package_id, _), ref mut request) in bump_candidates.iter_mut() {
976-
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(height, &request, fee_estimator, &&*logger) {
986+
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(
987+
height, &request, true /* force_feerate_bump */, fee_estimator, &&*logger
988+
) {
977989
request.set_timer(new_timer);
978990
request.set_feerate(new_feerate);
979991
match bump_claim {

lightning/src/chain/package.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -762,16 +762,23 @@ impl PackageTemplate {
762762
/// Returns value in satoshis to be included as package outgoing output amount and feerate
763763
/// which was used to generate the value. Will not return less than `dust_limit_sats` for the
764764
/// value.
765-
pub(crate) fn compute_package_output<F: Deref, L: Deref>(&self, predicted_weight: usize, dust_limit_sats: u64, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u64, u64)>
766-
where F::Target: FeeEstimator,
767-
L::Target: Logger,
765+
pub(crate) fn compute_package_output<F: Deref, L: Deref>(
766+
&self, predicted_weight: usize, dust_limit_sats: u64, force_feerate_bump: bool,
767+
fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
768+
) -> Option<(u64, u64)>
769+
where
770+
F::Target: FeeEstimator,
771+
L::Target: Logger,
768772
{
769773
debug_assert!(self.malleability == PackageMalleability::Malleable, "The package output is fixed for non-malleable packages");
770774
let input_amounts = self.package_amount();
771775
assert!(dust_limit_sats as i64 > 0, "Output script must be broadcastable/have a 'real' dust limit.");
772776
// If old feerate is 0, first iteration of this claim, use normal fee calculation
773777
if self.feerate_previous != 0 {
774-
if let Some((new_fee, feerate)) = feerate_bump(predicted_weight, input_amounts, self.feerate_previous, fee_estimator, logger) {
778+
if let Some((new_fee, feerate)) = feerate_bump(
779+
predicted_weight, input_amounts, self.feerate_previous, force_feerate_bump,
780+
fee_estimator, logger,
781+
) {
775782
return Some((cmp::max(input_amounts as i64 - new_fee as i64, dust_limit_sats as i64) as u64, feerate));
776783
}
777784
} else {
@@ -784,16 +791,19 @@ impl PackageTemplate {
784791

785792
#[cfg(anchors)]
786793
/// Computes a feerate based on the given confirmation target. If a previous feerate was used,
787-
/// and the new feerate is below it, we'll use a 25% increase of the previous feerate instead of
788-
/// the new one.
794+
/// the new feerate is below it, and `force_feerate_bump` is set, we'll use a 25% increase of
795+
/// the previous feerate instead of the new feerate.
789796
pub(crate) fn compute_package_feerate<F: Deref>(
790797
&self, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
798+
force_feerate_bump: bool,
791799
) -> u32 where F::Target: FeeEstimator {
792800
let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
793801
if self.feerate_previous != 0 {
794802
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
795803
if feerate_estimate as u64 > self.feerate_previous {
796804
feerate_estimate
805+
} else if !force_feerate_bump {
806+
self.feerate_previous.try_into().unwrap_or(u32::max_value())
797807
} else {
798808
// ...else just increase the previous feerate by 25% (because that's a nice number)
799809
(self.feerate_previous + (self.feerate_previous / 4)).try_into().unwrap_or(u32::max_value())
@@ -945,32 +955,47 @@ fn compute_fee_from_spent_amounts<F: Deref, L: Deref>(input_amounts: u64, predic
945955

946956
/// Attempt to propose a bumping fee for a transaction from its spent output's values and predicted
947957
/// weight. If feerates proposed by the fee-estimator have been increasing since last fee-bumping
948-
/// attempt, use them. Otherwise, blindly bump the feerate by 25% of the previous feerate. We also
949-
/// verify that those bumping heuristics respect BIP125 rules 3) and 4) and if required adjust
950-
/// the new fee to meet the RBF policy requirement.
951-
fn feerate_bump<F: Deref, L: Deref>(predicted_weight: usize, input_amounts: u64, previous_feerate: u64, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(u64, u64)>
952-
where F::Target: FeeEstimator,
953-
L::Target: Logger,
958+
/// attempt, use them. If `force_feerate_bump` is set, we bump the feerate by 25% of the previous
959+
/// feerate, or just use the previous feerate otherwise. If a feerate bump did happen, we also
960+
/// verify that those bumping heuristics respect BIP125 rules 3) and 4) and if required adjust the
961+
/// new fee to meet the RBF policy requirement.
962+
fn feerate_bump<F: Deref, L: Deref>(
963+
predicted_weight: usize, input_amounts: u64, previous_feerate: u64, force_feerate_bump: bool,
964+
fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L,
965+
) -> Option<(u64, u64)>
966+
where
967+
F::Target: FeeEstimator,
968+
L::Target: Logger,
954969
{
955970
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
956-
let new_fee = if let Some((new_fee, _)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, fee_estimator, logger) {
957-
let updated_feerate = new_fee / (predicted_weight as u64 * 1000);
958-
if updated_feerate > previous_feerate {
959-
new_fee
971+
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) = compute_fee_from_spent_amounts(input_amounts, predicted_weight, fee_estimator, logger) {
972+
if new_feerate > previous_feerate {
973+
(new_fee, new_feerate)
974+
} else if !force_feerate_bump {
975+
let previous_fee = previous_feerate * (predicted_weight as u64) / 1000;
976+
(previous_fee, previous_feerate)
960977
} else {
961978
// ...else just increase the previous feerate by 25% (because that's a nice number)
962-
let new_fee = previous_feerate * (predicted_weight as u64) / 750;
963-
if input_amounts <= new_fee {
979+
let bumped_feerate = previous_feerate + (previous_feerate / 4);
980+
let bumped_fee = bumped_feerate * (predicted_weight as u64) / 1000;
981+
if input_amounts <= bumped_fee {
964982
log_warn!(logger, "Can't 25% bump new claiming tx, amount {} is too small", input_amounts);
965983
return None;
966984
}
967-
new_fee
985+
(bumped_fee, bumped_feerate)
968986
}
969987
} else {
970988
log_warn!(logger, "Can't new-estimation bump new claiming tx, amount {} is too small", input_amounts);
971989
return None;
972990
};
973991

992+
// Our feerates should never decrease. If it hasn't changed though, we just need to
993+
// rebroadcast/re-sign the previous claim.
994+
debug_assert!(new_feerate >= previous_feerate);
995+
if new_feerate == previous_feerate {
996+
return Some((new_fee, new_feerate));
997+
}
998+
974999
let previous_fee = previous_feerate * (predicted_weight as u64) / 1000;
9751000
let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * (predicted_weight as u64) / 1000;
9761001
// BIP 125 Opt-in Full Replace-by-Fee Signaling

lightning/src/ln/functional_tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2991,9 +2991,9 @@ fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {
29912991
if nodes[1].connect_style.borrow().skips_blocks() {
29922992
assert_eq!(txn.len(), 1);
29932993
} else {
2994-
assert_eq!(txn.len(), 2); // Extra rebroadcast of timeout transaction
2994+
assert_eq!(txn.len(), 3); // Two extra fee bumps for timeout transaction
29952995
}
2996-
check_spends!(txn[0], commitment_tx[0]);
2996+
txn.iter().for_each(|tx| check_spends!(tx, commitment_tx[0]));
29972997
assert_eq!(txn[0].clone().input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
29982998
txn.remove(0)
29992999
};
@@ -7510,7 +7510,7 @@ fn test_bump_penalty_txn_on_remote_commitment() {
75107510
assert_ne!(feerate_preimage, 0);
75117511

75127512
// After exhaustion of height timer, new bumped claim txn should have been broadcast, check it
7513-
connect_blocks(&nodes[1], 15);
7513+
connect_blocks(&nodes[1], 1);
75147514
{
75157515
let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
75167516
assert_eq!(node_txn.len(), 1);

0 commit comments

Comments
 (0)