Skip to content

Commit

Permalink
Add Fast-Close in case of fees spikes
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoine Riard committed Dec 14, 2021
1 parent 1a74367 commit 8cce3cc
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 3 deletions.
38 changes: 35 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@ pub(crate) enum ChannelMonitorUpdateStep {
ShutdownScript {
scriptpubkey: Script,
},
FeesSpikes {
feerate: u32,
},
}

impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
Expand All @@ -505,6 +508,9 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
(5, ShutdownScript) => {
(0, scriptpubkey, required),
},
(6, FeesSpikes) => {
(0, feerate, required),
}
);

/// Details about the balance(s) available for spending once the channel appears on chain.
Expand Down Expand Up @@ -1925,6 +1931,30 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
panic!("Attempted to replace shutdown script {} with {}", shutdown_script, scriptpubkey);
}
},
ChannelMonitorUpdateStep::FeesSpikes { feerate } => {
let should_broadcast = self.should_broadcast_holder_commitment_txn(logger, true);
let mut claimable_outpoints = Vec::new();
let mut watch_outputs = Vec::new();
if should_broadcast { //XXX: do a helper to factor code with block_confirmed()
let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone());
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height(), false, self.best_block.height());
claimable_outpoints.push(commitment_package);
self.pending_monitor_events.push(MonitorEvent::CommitmentTxConfirmed(self.funding_info.0));
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
self.holder_tx_signed = true;
// Because we're broadcasting a commitment transaction, we should construct the package
// assuming it gets confirmed in the next block. Sadly, we have code which considers
// "not yet confirmed" things as discardable, so we cannot do that here.
let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, self.best_block.height());
let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx);
if !new_outputs.is_empty() {
watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs));
}
claimable_outpoints.append(&mut new_outpoints);
};
self.onchain_tx_handler.update_claims_view(&Vec::new(), claimable_outpoints, self.best_block.height(), self.best_block.height(), broadcaster, fee_estimator, logger);
//XXX: return watch_outputs to ChainMonitor, factor code from process_chain_data about outputs registration
}
}
}
self.latest_update_id = updates.update_id;
Expand Down Expand Up @@ -2431,7 +2461,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
log_trace!(logger, "Processing {} matched transactions for block at height {}.", txn_matched.len(), conf_height);
debug_assert!(self.best_block.height() >= conf_height);

let should_broadcast = self.should_broadcast_holder_commitment_txn(logger);
let should_broadcast = self.should_broadcast_holder_commitment_txn(logger, false);
if should_broadcast {
let funding_outp = HolderFundingOutput::build(self.funding_redeemscript.clone());
let commitment_package = PackageTemplate::build_package(self.funding_info.0.txid.clone(), self.funding_info.0.index as u32, PackageSolvingData::HolderFundingOutput(funding_outp), self.best_block.height(), false, self.best_block.height());
Expand Down Expand Up @@ -2622,7 +2652,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
false
}

fn should_broadcast_holder_commitment_txn<L: Deref>(&self, logger: &L) -> bool where L::Target: Logger {
fn should_broadcast_holder_commitment_txn<L: Deref>(&self, logger: &L, spikes: bool) -> bool where L::Target: Logger {
// We need to consider all HTLCs which are:
// * in any unrevoked counterparty commitment transaction, as they could broadcast said
// transactions and we'd end up in a race, or
Expand Down Expand Up @@ -2661,8 +2691,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
// The final, above, condition is checked for statically in channelmanager
// with CHECK_CLTV_EXPIRY_SANITY_2.
let htlc_outbound = $holder_tx == htlc.offered;
//XXX: don't go on-chain for inbound payment not worthy the dust threshold ?
//XXX: scale down the timer in function of feerate ?
if ( htlc_outbound && htlc.cltv_expiry + LATENCY_GRACE_PERIOD_BLOCKS <= height) ||
(!htlc_outbound && htlc.cltv_expiry <= height + CLTV_CLAIM_BUFFER && self.payment_preimages.contains_key(&htlc.payment_hash)) {
(!htlc_outbound && htlc.cltv_expiry <= height + CLTV_CLAIM_BUFFER / (if spikes { 2 } else { 1 }) && self.payment_preimages.contains_key(&htlc.payment_hash)) {
log_info!(logger, "Force-closing channel due to {} HTLC timeout, HTLC expiry is {}", if htlc_outbound { "outbound" } else { "inbound "}, htlc.cltv_expiry);
return true;
}
Expand Down
31 changes: 31 additions & 0 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,8 @@ pub(super) struct Channel<Signer: Sign> {

/// This channel's type, as negotiated during channel open
channel_type: ChannelTypeFeatures,

autoclose_timestamp: u64,
}

#[cfg(any(test, feature = "fuzztarget"))]
Expand Down Expand Up @@ -837,6 +839,8 @@ impl<Signer: Sign> Channel<Signer> {
// on error messages. When we support more we'll need fallback support (assuming we
// want to support old types).
channel_type: ChannelTypeFeatures::only_static_remote_key(),

autoclose_timestamp: 0,
})
}

Expand Down Expand Up @@ -1128,6 +1132,8 @@ impl<Signer: Sign> Channel<Signer> {
historical_inbound_htlc_fulfills: HashSet::new(),

channel_type,

autoclose_timestamp: 0
};

Ok(chan)
Expand Down Expand Up @@ -3154,6 +3160,26 @@ impl<Signer: Sign> Channel<Signer> {
})
}

pub fn monitor_fees_spikes(&mut self, new_feerate: u32, highest_seen_timestamp: u64) -> Option<ChannelMonitorUpdate> {
if new_feerate > self.feerate_per_kw * 1300 / 1000 {
//XXX: more than 30 ticks ?
if highest_seen_timestamp + 30 < self.autoclose_timestamp {
let monitor_update = ChannelMonitorUpdate {
update_id: self.latest_monitor_update_id,
updates: vec![ChannelMonitorUpdateStep::FeesSpikes {
feerate: new_feerate,
}]
};
return Some(monitor_update);
} else {
self.autoclose_timestamp += 1;
}
} else {
self.autoclose_timestamp = 0;
}
return None;
}

pub fn send_update_fee_and_commit<L: Deref>(&mut self, feerate_per_kw: u32, logger: &L) -> Result<Option<(msgs::UpdateFee, msgs::CommitmentSigned, ChannelMonitorUpdate)>, ChannelError> where L::Target: Logger {
match self.send_update_fee(feerate_per_kw, logger) {
Some(update_fee) => {
Expand Down Expand Up @@ -5384,6 +5410,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
(9, self.target_closing_feerate_sats_per_kw, option),
(11, self.monitor_pending_finalized_fulfills, vec_type),
(13, self.channel_creation_height, required),
(15, self.autoclose_timestamp, required),
});

Ok(())
Expand Down Expand Up @@ -5623,6 +5650,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
// only, so we default to that if none was written.
let mut channel_type = Some(ChannelTypeFeatures::only_static_remote_key());
let mut channel_creation_height = Some(serialized_height);
let mut autoclose_timestamp = 0;
read_tlv_fields!(reader, {
(0, announcement_sigs, option),
(1, minimum_depth, option),
Expand All @@ -5633,6 +5661,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
(9, target_closing_feerate_sats_per_kw, option),
(11, monitor_pending_finalized_fulfills, vec_type),
(13, channel_creation_height, option),
(15, autoclose_timestamp, required)
});

let chan_features = channel_type.as_ref().unwrap();
Expand Down Expand Up @@ -5742,6 +5771,8 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<(&'a K, u32)> for Channel<Signer>
historical_inbound_htlc_fulfills,

channel_type: channel_type.unwrap(),

autoclose_timestamp,
})
}
}
Expand Down
18 changes: 18 additions & 0 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3021,6 +3021,16 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
(retain_channel, NotifyOption::DoPersist, ret_err)
}

fn monitor_fees_spikes(&self, short_to_id: &mut HashMap<u64, [u8; 32]>, chan_id: &[u8; 32], chan: &mut Channel<Signer>, new_feerate: u32, highest_seen_timestamp: u64) -> (bool, NotifyOption, Result<(), MsgHandleErrInternal>) {
if let Some(monitor_update) = chan.monitor_fees_spikes(new_feerate, highest_seen_timestamp) {
if let Err(e) = self.chain_monitor.update_channel(chan.get_funding_txo().unwrap(), monitor_update) {
let (res, drop) = handle_monitor_err!(self, e, short_to_id, chan, RAACommitmentOrder::CommitmentFirst, false, false, Vec::new(), Vec::new(), Vec::new(), chan_id);
return (if drop { false } else { true }, NotifyOption::DoPersist, res);
}
}
(true, NotifyOption::SkipPersist, Ok(()))
}

#[cfg(fuzzing)]
/// In chanmon_consistency we want to sometimes do the channel fee updates done in
/// timer_tick_occurred, but we can't generate the disabled channel updates as it considers
Expand Down Expand Up @@ -3077,6 +3087,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
let short_to_id = &mut channel_state.short_to_id;
channel_state.by_id.retain(|chan_id, chan| {
let counterparty_node_id = chan.get_counterparty_node_id();

let (retain_channel, chan_needs_persist, err) = self.monitor_fees_spikes(short_to_id, chan_id, chan, new_feerate, self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
if err.is_err() {
handle_errors.push((err, counterparty_node_id));
}
if !retain_channel { return false; }

let (retain_channel, chan_needs_persist, err) = self.update_channel_fee(short_to_id, pending_msg_events, chan_id, chan, new_feerate);
if chan_needs_persist == NotifyOption::DoPersist { should_persist = NotifyOption::DoPersist; }
if err.is_err() {
Expand Down

0 comments on commit 8cce3cc

Please sign in to comment.