Skip to content

Commit 9aa564c

Browse files
committed
Generate ClaimEvent for HolderFundingOutput inputs from anchor channels
1 parent af0bdec commit 9aa564c

File tree

3 files changed

+167
-26
lines changed

3 files changed

+167
-26
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ use bitcoin::secp256k1;
2323

2424
use ln::msgs::DecodeError;
2525
use ln::PaymentPreimage;
26+
use ln::chan_utils;
2627
use ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
27-
use chain::chaininterface::{FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
28+
use chain::chaininterface::{ConfirmationTarget, FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
2829
use chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER};
2930
use chain::keysinterface::{Sign, KeysInterface};
30-
use chain::package::PackageTemplate;
31+
use chain::package::{PackageSolvingData, PackageTemplate};
3132
use util::logger::Logger;
3233
use util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, VecWriter};
3334
use util::byte_utils;
@@ -36,6 +37,7 @@ use io;
3637
use prelude::*;
3738
use alloc::collections::BTreeMap;
3839
use core::cmp;
40+
use core::convert::TryInto;
3941
use core::ops::Deref;
4042
use core::mem::replace;
4143
use bitcoin::hashes::Hash;
@@ -162,8 +164,17 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
162164
}
163165
}
164166

167+
pub(crate) enum ClaimEvent {
168+
BumpCommitment {
169+
target_feerate_sat_per_1000_weight: u32,
170+
commitment_tx: Transaction,
171+
anchor_output_idx: u32,
172+
},
173+
}
174+
165175
pub(crate) enum OnchainClaim {
166176
Tx(Transaction),
177+
Event(ClaimEvent),
167178
}
168179

169180
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
@@ -196,6 +207,7 @@ pub struct OnchainTxHandler<ChannelSigner: Sign> {
196207
pub(crate) pending_claim_requests: HashMap<Txid, PackageTemplate>,
197208
#[cfg(not(test))]
198209
pending_claim_requests: HashMap<Txid, PackageTemplate>,
210+
pending_claim_events: HashMap<Txid, ClaimEvent>,
199211

200212
// Used to link outpoints claimed in a connected block to a pending claim request.
201213
// Key is outpoint than monitor parsing has detected we have keys/scripts to claim
@@ -345,6 +357,7 @@ impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler<K::Signer> {
345357
locktimed_packages,
346358
pending_claim_requests,
347359
onchain_events_awaiting_threshold_conf,
360+
pending_claim_events: HashMap::new(),
348361
secp_ctx,
349362
})
350363
}
@@ -364,6 +377,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
364377
claimable_outpoints: HashMap::new(),
365378
locktimed_packages: BTreeMap::new(),
366379
onchain_events_awaiting_threshold_conf: Vec::new(),
380+
pending_claim_events: HashMap::new(),
367381

368382
secp_ctx,
369383
}
@@ -377,10 +391,14 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
377391
self.holder_commitment.to_broadcaster_value_sat()
378392
}
379393

380-
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize coutnerparty onchain) lays on the assumption of claim transactions getting confirmed before timelock expiration
381-
/// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent.
382-
/// Panics if there are signing errors, because signing operations in reaction to on-chain events
383-
/// are not expected to fail, and if they do, we may lose funds.
394+
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty
395+
/// onchain) lays on the assumption of claim transactions getting confirmed before timelock
396+
/// expiration (CSV or CLTV following cases). In case of high-fee spikes, claim tx may get stuck
397+
/// in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or
398+
/// Child-Pay-For-Parent.
399+
///
400+
/// Panics if there are signing errors, because signing operations in reaction to on-chain
401+
/// events are not expected to fail, and if they do, we may lose funds.
384402
fn generate_claim<F: Deref, L: Deref>(&mut self, cur_height: u32, cached_request: &PackageTemplate, fee_estimator: &LowerBoundedFeeEstimator<F>, logger: &L) -> Option<(Option<u32>, u64, OnchainClaim)>
385403
where F::Target: FeeEstimator,
386404
L::Target: Logger,
@@ -402,12 +420,57 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
402420
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
403421
}
404422
} else {
405-
// Note: Currently, amounts of holder outputs spending witnesses aren't used
406-
// as we can't malleate spending package to increase their feerate. This
407-
// should change with the remaining anchor output patchset.
408-
if let Some(transaction) = cached_request.finalize_untractable_package(self, logger) {
409-
return Some((None, 0, OnchainClaim::Tx(transaction)));
423+
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
424+
// packages may support fee bumping through Child-Pays-For-Parent, indicated by those
425+
// which require external funding.
426+
let inputs = cached_request.inputs();
427+
debug_assert_eq!(inputs.len(), 1);
428+
let tx = match cached_request.finalize_untractable_package(self, logger) {
429+
Some(tx) => tx,
430+
None => return None,
431+
};
432+
if !cached_request.requires_external_funding() {
433+
return Some((None, 0, OnchainClaim::Tx(tx)));
410434
}
435+
return inputs.iter().find_map(|input| match input {
436+
// Commitment inputs with anchors support are the only untractable inputs supported
437+
// thus far that require external funding.
438+
PackageSolvingData::HolderFundingOutput(..) => {
439+
// We'll locate an anchor output we can spend within the commitment transaction.
440+
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
441+
match chan_utils::get_anchor_output(&tx, funding_pubkey) {
442+
// An anchor output was found, so we should yield a funding event externally.
443+
Some((idx, _)) => {
444+
let target_feerate_sat_per_1000_weight: u32 = match cached_request
445+
.compute_package_child_feerate(&tx, fee_estimator, ConfirmationTarget::HighPriority)
446+
.map(|f| f.try_into().ok())
447+
.flatten()
448+
{
449+
Some(f) => f,
450+
_ => return None,
451+
};
452+
Some((
453+
new_timer,
454+
target_feerate_sat_per_1000_weight as u64,
455+
OnchainClaim::Event(ClaimEvent::BumpCommitment {
456+
target_feerate_sat_per_1000_weight,
457+
commitment_tx: tx.clone(),
458+
anchor_output_idx: idx,
459+
}),
460+
))
461+
},
462+
// An anchor output was not found. There's nothing we can do other than
463+
// attempt to broadcast the transaction with its current fee rate and hope
464+
// it confirms. This is essentially the same behavior as a commitment
465+
// transaction without anchor outputs.
466+
None => Some((None, 0, OnchainClaim::Tx(tx.clone()))),
467+
}
468+
},
469+
_ => {
470+
debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
471+
None
472+
},
473+
});
411474
}
412475
None
413476
}
@@ -481,18 +544,25 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
481544
if let Some((new_timer, new_feerate, claim)) = self.generate_claim(cur_height, &req, &*fee_estimator, &*logger) {
482545
req.set_timer(new_timer);
483546
req.set_feerate(new_feerate);
484-
match claim {
547+
let txid = match claim {
485548
OnchainClaim::Tx(tx) => {
486-
let txid = tx.txid();
487-
for k in req.outpoints() {
488-
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
489-
self.claimable_outpoints.insert(k.clone(), (txid, conf_height));
490-
}
491-
self.pending_claim_requests.insert(txid, req);
492549
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
493550
broadcaster.broadcast_transaction(&tx);
551+
tx.txid()
552+
},
553+
OnchainClaim::Event(claim_event) => {
554+
let txid = match claim_event {
555+
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
556+
};
557+
self.pending_claim_events.insert(txid, claim_event);
558+
txid
494559
},
560+
};
561+
for k in req.outpoints() {
562+
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
563+
self.claimable_outpoints.insert(k.clone(), (txid, conf_height));
495564
}
565+
self.pending_claim_requests.insert(txid, req);
496566
}
497567
}
498568

@@ -584,6 +654,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
584654
for outpoint in request.outpoints() {
585655
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim tx {}.", outpoint, claim_request);
586656
self.claimable_outpoints.remove(&outpoint);
657+
self.pending_claim_events.remove(&claim_request);
587658
}
588659
}
589660
},
@@ -616,6 +687,9 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
616687
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
617688
broadcaster.broadcast_transaction(&bump_tx);
618689
},
690+
OnchainClaim::Event(claim_event) => {
691+
self.pending_claim_events.insert(*first_claim_txid, claim_event);
692+
},
619693
}
620694
if let Some(request) = self.pending_claim_requests.get_mut(first_claim_txid) {
621695
request.set_timer(new_timer);
@@ -678,7 +752,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
678752
self.onchain_events_awaiting_threshold_conf.push(entry);
679753
}
680754
}
681-
for (_, request) in bump_candidates.iter_mut() {
755+
for (first_claim_txid_height, request) in bump_candidates.iter_mut() {
682756
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(height, &request, fee_estimator, &&*logger) {
683757
request.set_timer(new_timer);
684758
request.set_feerate(new_feerate);
@@ -687,6 +761,9 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
687761
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx));
688762
broadcaster.broadcast_transaction(&bump_tx);
689763
},
764+
OnchainClaim::Event(claim_event) => {
765+
self.pending_claim_events.insert(first_claim_txid_height.0, claim_event);
766+
},
690767
}
691768
}
692769
}

lightning/src/chain/package.rs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,9 @@ impl PackageTemplate {
548548
pub(crate) fn outpoints(&self) -> Vec<&BitcoinOutPoint> {
549549
self.inputs.iter().map(|(o, _)| o).collect()
550550
}
551+
pub(crate) fn inputs(&self) -> Vec<&PackageSolvingData> {
552+
self.inputs.iter().map(|(_, i)| i).collect()
553+
}
551554
pub(crate) fn split_package(&mut self, split_outp: &BitcoinOutPoint) -> Option<PackageTemplate> {
552555
match self.malleability {
553556
PackageMalleability::Malleable => {
@@ -611,7 +614,7 @@ impl PackageTemplate {
611614
}
612615
/// Gets the amount of all outptus being spent by this package, only valid for malleable
613616
/// packages.
614-
fn package_amount(&self) -> u64 {
617+
pub(crate) fn package_amount(&self) -> u64 {
615618
let mut amounts = 0;
616619
for (_, outp) in self.inputs.iter() {
617620
amounts += outp.amount();
@@ -713,14 +716,67 @@ impl PackageTemplate {
713716
}
714717
None
715718
}
719+
720+
pub(crate) fn requires_external_funding(&self) -> bool {
721+
self.inputs.iter().find(|input| match input.1 {
722+
PackageSolvingData::HolderFundingOutput(ref outp) => outp.opt_anchors(),
723+
_ => false,
724+
}).is_some()
725+
}
726+
727+
/// Computes a feerate based on the given confirmation target. If a previous feerate was used,
728+
/// and the new feerate is below it, we'll use a 25% increase of the previous feerate instead of
729+
/// the new one.
730+
fn compute_feerate<F: Deref>(
731+
previous_feerate: u64, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
732+
) -> u64 where F::Target: FeeEstimator {
733+
let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
734+
if previous_feerate != 0 {
735+
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
736+
if feerate_estimate as u64 > previous_feerate {
737+
feerate_estimate as u64
738+
} else {
739+
// ...else just increase the previous feerate by 25% (because that's a nice number)
740+
previous_feerate + (previous_feerate / 4)
741+
}
742+
} else {
743+
feerate_estimate as u64
744+
}
745+
}
746+
747+
/// Computes the feerate for a child transaction such that its parent transaction meets a
748+
/// feerate based on the given confirmation target.
749+
pub(crate) fn compute_package_child_feerate<F: Deref>(
750+
&self, parent: &Transaction, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
751+
) -> Option<u64> where F::Target: FeeEstimator {
752+
debug_assert!(!self.is_malleable());
753+
let parent_feerate = {
754+
let input_amount = self.package_amount();
755+
let output_amount = parent.output.iter().fold(0, |acc, outp| acc + outp.value);
756+
let fee = input_amount - output_amount;
757+
fee / (parent.weight() as u64 * 1000)
758+
};
759+
// The previous feerate tracks the previous child feerate used, so we'll need to map it back
760+
// to the overall package feerate.
761+
let previous_package_feerate = if self.feerate_previous != 0 {
762+
(parent_feerate + self.feerate_previous) / 2
763+
} else {
764+
0
765+
};
766+
let package_feerate = Self::compute_feerate(previous_package_feerate, fee_estimator, conf_target);
767+
// With the new package feerate obtained, compute the feerate the child transaction must
768+
// meet such that the package feerate is met.
769+
(package_feerate as u64).checked_mul(2).map(|f| f.checked_sub(parent_feerate)).flatten()
770+
}
771+
716772
pub (crate) fn build_package(txid: Txid, vout: u32, input_solving_data: PackageSolvingData, soonest_conf_deadline: u32, aggregable: bool, height_original: u32) -> Self {
717773
let malleability = match input_solving_data {
718-
PackageSolvingData::RevokedOutput(..) => { PackageMalleability::Malleable },
719-
PackageSolvingData::RevokedHTLCOutput(..) => { PackageMalleability::Malleable },
720-
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { PackageMalleability::Malleable },
721-
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { PackageMalleability::Malleable },
722-
PackageSolvingData::HolderHTLCOutput(..) => { PackageMalleability::Untractable },
723-
PackageSolvingData::HolderFundingOutput(..) => { PackageMalleability::Untractable },
774+
PackageSolvingData::RevokedOutput(..) => PackageMalleability::Malleable,
775+
PackageSolvingData::RevokedHTLCOutput(..) => PackageMalleability::Malleable,
776+
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => PackageMalleability::Malleable,
777+
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => PackageMalleability::Malleable,
778+
PackageSolvingData::HolderHTLCOutput(..) => PackageMalleability::Untractable,
779+
PackageSolvingData::HolderFundingOutput(..) => PackageMalleability::Untractable,
724780
};
725781
let mut inputs = Vec::with_capacity(1);
726782
inputs.push((BitcoinOutPoint { txid, vout }, input_solving_data));

lightning/src/ln/chan_utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,14 @@ pub fn get_anchor_redeemscript(funding_pubkey: &PublicKey) -> Script {
726726
.into_script()
727727
}
728728

729+
/// Locates the output with an anchor script paying to `funding_pubkey` within `commitment_tx`.
730+
pub(crate) fn get_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> {
731+
let anchor_script = chan_utils::get_anchor_redeemscript(funding_pubkey).to_v0_p2wsh();
732+
commitment_tx.output.iter().enumerate()
733+
.find(|(_, txout)| txout.script_pubkey == anchor_script)
734+
.map(|(idx, txout)| (idx as u32, txout))
735+
}
736+
729737
/// Per-channel data used to build transactions in conjunction with the per-commitment data (CommitmentTransaction).
730738
/// The fields are organized by holder/counterparty.
731739
///

0 commit comments

Comments
 (0)