Skip to content

Commit e9ac2b1

Browse files
committed
Generate ClaimEvent for HolderFundingOutput inputs from anchor channels
1 parent bac1b79 commit e9ac2b1

File tree

3 files changed

+165
-18
lines changed

3 files changed

+165
-18
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,16 @@ use bitcoin::secp256k1;
2323

2424
use ln::msgs::DecodeError;
2525
use ln::PaymentPreimage;
26+
#[cfg(anchors)]
27+
use ln::chan_utils;
2628
use ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
29+
#[cfg(anchors)]
30+
use chain::chaininterface::ConfirmationTarget;
2731
use chain::chaininterface::{FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator};
2832
use chain::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER};
2933
use chain::keysinterface::{Sign, KeysInterface};
34+
#[cfg(anchors)]
35+
use chain::package::PackageSolvingData;
3036
use chain::package::PackageTemplate;
3137
use util::logger::Logger;
3238
use util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, VecWriter};
@@ -162,11 +168,28 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
162168
}
163169
}
164170

171+
// Represents the different types of claims for which events are yielded externally to satisfy said
172+
// claims.
173+
#[cfg(anchors)]
174+
pub(crate) enum ClaimEvent {
175+
/// Event yielded to signal that the commitment transaction fee must be bumped to claim any
176+
/// encumbered funds and proceed to HTLC resolution, if any HTLCs exist.
177+
BumpCommitment {
178+
package_target_feerate_sat_per_1000_weight: u32,
179+
commitment_tx: Transaction,
180+
anchor_output_idx: u32,
181+
},
182+
}
183+
165184
/// Represents the different ways an output can be claimed (i.e., spent to an address under our
166185
/// control) onchain.
167186
pub(crate) enum OnchainClaim {
168187
/// A finalized transaction pending confirmation spending the output to claim.
169188
Tx(Transaction),
189+
#[cfg(anchors)]
190+
/// An event yielded externally to signal additional inputs must be added to a transaction
191+
/// pending confirmation spending the output to claim.
192+
Event(ClaimEvent),
170193
}
171194

172195
/// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and
@@ -199,6 +222,8 @@ pub struct OnchainTxHandler<ChannelSigner: Sign> {
199222
pub(crate) pending_claim_requests: HashMap<Txid, PackageTemplate>,
200223
#[cfg(not(test))]
201224
pending_claim_requests: HashMap<Txid, PackageTemplate>,
225+
#[cfg(anchors)]
226+
pending_claim_events: HashMap<Txid, ClaimEvent>,
202227

203228
// Used to link outpoints claimed in a connected block to a pending claim request.
204229
// Key is outpoint than monitor parsing has detected we have keys/scripts to claim
@@ -348,6 +373,8 @@ impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler<K::Signer> {
348373
locktimed_packages,
349374
pending_claim_requests,
350375
onchain_events_awaiting_threshold_conf,
376+
#[cfg(anchors)]
377+
pending_claim_events: HashMap::new(),
351378
secp_ctx,
352379
})
353380
}
@@ -367,6 +394,8 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
367394
claimable_outpoints: HashMap::new(),
368395
locktimed_packages: BTreeMap::new(),
369396
onchain_events_awaiting_threshold_conf: Vec::new(),
397+
#[cfg(anchors)]
398+
pending_claim_events: HashMap::new(),
370399

371400
secp_ctx,
372401
}
@@ -380,10 +409,14 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
380409
self.holder_commitment.to_broadcaster_value_sat()
381410
}
382411

383-
/// 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
384-
/// (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.
385-
/// Panics if there are signing errors, because signing operations in reaction to on-chain events
386-
/// are not expected to fail, and if they do, we may lose funds.
412+
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize counterparty
413+
/// onchain) lays on the assumption of claim transactions getting confirmed before timelock
414+
/// expiration (CSV or CLTV following cases). In case of high-fee spikes, claim tx may get stuck
415+
/// in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or
416+
/// Child-Pay-For-Parent.
417+
///
418+
/// Panics if there are signing errors, because signing operations in reaction to on-chain
419+
/// events are not expected to fail, and if they do, we may lose funds.
387420
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)>
388421
where F::Target: FeeEstimator,
389422
L::Target: Logger,
@@ -405,12 +438,60 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
405438
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
406439
}
407440
} else {
408-
// Note: Currently, amounts of holder outputs spending witnesses aren't used
409-
// as we can't malleate spending package to increase their feerate. This
410-
// should change with the remaining anchor output patchset.
411-
if let Some(transaction) = cached_request.finalize_untractable_package(self, logger) {
412-
return Some((None, 0, OnchainClaim::Tx(transaction)));
441+
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
442+
// packages may support fee bumping through Child-Pays-For-Parent, indicated by those
443+
// which require external funding.
444+
#[cfg(not(anchors))]
445+
let inputs = cached_request.inputs();
446+
#[cfg(anchors)]
447+
let mut inputs = cached_request.inputs();
448+
debug_assert_eq!(inputs.len(), 1);
449+
let tx = match cached_request.finalize_untractable_package(self, logger) {
450+
Some(tx) => tx,
451+
None => return None,
452+
};
453+
if !cached_request.requires_external_funding() {
454+
return Some((None, 0, OnchainClaim::Tx(tx)));
413455
}
456+
#[cfg(anchors)]
457+
return inputs.find_map(|input| match input {
458+
// Commitment inputs with anchors support are the only untractable inputs supported
459+
// thus far that require external funding.
460+
PackageSolvingData::HolderFundingOutput(..) => {
461+
debug_assert_eq!(tx.txid(), self.holder_commitment.trust().txid(),
462+
"Holder commitment transaction mismatch");
463+
// We'll locate an anchor output we can spend within the commitment transaction.
464+
let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey;
465+
match chan_utils::get_anchor_output(&tx, funding_pubkey) {
466+
// An anchor output was found, so we should yield a funding event externally.
467+
Some((idx, _)) => {
468+
// TODO: Use a lower confirmation target when both our and the
469+
// counterparty's latest commitment don't have any HTLCs present.
470+
let conf_target = ConfirmationTarget::HighPriority;
471+
let package_target_feerate_sat_per_1000_weight = cached_request
472+
.compute_package_feerate(fee_estimator, conf_target);
473+
Some((
474+
new_timer,
475+
package_target_feerate_sat_per_1000_weight as u64,
476+
OnchainClaim::Event(ClaimEvent::BumpCommitment {
477+
package_target_feerate_sat_per_1000_weight,
478+
commitment_tx: tx.clone(),
479+
anchor_output_idx: idx,
480+
}),
481+
))
482+
},
483+
// An anchor output was not found. There's nothing we can do other than
484+
// attempt to broadcast the transaction with its current fee rate and hope
485+
// it confirms. This is essentially the same behavior as a commitment
486+
// transaction without anchor outputs.
487+
None => Some((None, 0, OnchainClaim::Tx(tx.clone()))),
488+
}
489+
},
490+
_ => {
491+
debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
492+
None
493+
},
494+
});
414495
}
415496
None
416497
}
@@ -490,6 +571,15 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
490571
broadcaster.broadcast_transaction(&tx);
491572
tx.txid()
492573
},
574+
#[cfg(anchors)]
575+
OnchainClaim::Event(claim_event) => {
576+
log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
577+
let txid = match claim_event {
578+
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
579+
};
580+
self.pending_claim_events.insert(txid, claim_event);
581+
txid
582+
},
493583
};
494584
for k in req.outpoints() {
495585
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
@@ -587,6 +677,8 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
587677
for outpoint in request.outpoints() {
588678
log_debug!(logger, "Removing claim tracking for {} due to maturation of claim tx {}.", outpoint, claim_request);
589679
self.claimable_outpoints.remove(&outpoint);
680+
#[cfg(anchors)]
681+
self.pending_claim_events.remove(&claim_request);
590682
}
591683
}
592684
},
@@ -619,6 +711,11 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
619711
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
620712
broadcaster.broadcast_transaction(&bump_tx);
621713
},
714+
#[cfg(anchors)]
715+
OnchainClaim::Event(claim_event) => {
716+
log_info!(logger, "Yielding RBF-bumped onchain event to spend inputs {:?}", request.outpoints());
717+
self.pending_claim_events.insert(*first_claim_txid, claim_event);
718+
},
622719
}
623720
if let Some(request) = self.pending_claim_requests.get_mut(first_claim_txid) {
624721
request.set_timer(new_timer);
@@ -681,7 +778,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
681778
self.onchain_events_awaiting_threshold_conf.push(entry);
682779
}
683780
}
684-
for (_, request) in bump_candidates.iter_mut() {
781+
for (_first_claim_txid_height, request) in bump_candidates.iter_mut() {
685782
if let Some((new_timer, new_feerate, bump_claim)) = self.generate_claim(height, &request, fee_estimator, &&*logger) {
686783
request.set_timer(new_timer);
687784
request.set_feerate(new_feerate);
@@ -690,6 +787,11 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
690787
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx));
691788
broadcaster.broadcast_transaction(&bump_tx);
692789
},
790+
#[cfg(anchors)]
791+
OnchainClaim::Event(claim_event) => {
792+
log_info!(logger, "Yielding onchain event after reorg to spend inputs {:?}", request.outpoints());
793+
self.pending_claim_events.insert(_first_claim_txid_height.0, claim_event);
794+
},
693795
}
694796
}
695797
}

lightning/src/chain/package.rs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ use util::ser::{Readable, Writer, Writeable};
3434
use io;
3535
use prelude::*;
3636
use core::cmp;
37+
#[cfg(anchors)]
38+
use core::convert::TryInto;
3739
use core::mem;
3840
use core::ops::Deref;
3941
use bitcoin::{PackedLockTime, Sequence, Witness};
@@ -548,6 +550,9 @@ impl PackageTemplate {
548550
pub(crate) fn outpoints(&self) -> Vec<&BitcoinOutPoint> {
549551
self.inputs.iter().map(|(o, _)| o).collect()
550552
}
553+
pub(crate) fn inputs(&self) -> impl ExactSizeIterator<Item = &PackageSolvingData> {
554+
self.inputs.iter().map(|(_, i)| i)
555+
}
551556
pub(crate) fn split_package(&mut self, split_outp: &BitcoinOutPoint) -> Option<PackageTemplate> {
552557
match self.malleability {
553558
PackageMalleability::Malleable => {
@@ -611,7 +616,7 @@ impl PackageTemplate {
611616
}
612617
/// Gets the amount of all outptus being spent by this package, only valid for malleable
613618
/// packages.
614-
fn package_amount(&self) -> u64 {
619+
pub(crate) fn package_amount(&self) -> u64 {
615620
let mut amounts = 0;
616621
for (_, outp) in self.inputs.iter() {
617622
amounts += outp.amount();
@@ -637,7 +642,7 @@ impl PackageTemplate {
637642
inputs_weight + witnesses_weight + transaction_weight + output_weight
638643
}
639644
pub(crate) fn finalize_malleable_package<L: Deref, Signer: Sign>(
640-
&self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L,
645+
&self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L
641646
) -> Option<Transaction> where L::Target: Logger {
642647
debug_assert!(self.is_malleable());
643648
let mut bumped_tx = Transaction {
@@ -713,14 +718,45 @@ impl PackageTemplate {
713718
}
714719
None
715720
}
721+
722+
#[cfg(anchors)]
723+
/// Computes a feerate based on the given confirmation target. If a previous feerate was used,
724+
/// and the new feerate is below it, we'll use a 25% increase of the previous feerate instead of
725+
/// the new one.
726+
pub(crate) fn compute_package_feerate<F: Deref>(
727+
&self, fee_estimator: &LowerBoundedFeeEstimator<F>, conf_target: ConfirmationTarget,
728+
) -> u32 where F::Target: FeeEstimator {
729+
let feerate_estimate = fee_estimator.bounded_sat_per_1000_weight(conf_target);
730+
if self.feerate_previous != 0 {
731+
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
732+
if feerate_estimate as u64 > self.feerate_previous {
733+
feerate_estimate
734+
} else {
735+
// ...else just increase the previous feerate by 25% (because that's a nice number)
736+
(self.feerate_previous + (self.feerate_previous / 4)).try_into().unwrap_or(u32::max_value())
737+
}
738+
} else {
739+
feerate_estimate
740+
}
741+
}
742+
743+
/// Determines whether a package contains an input which must have additional external inputs
744+
/// attached to help the spending transaction reach confirmation.
745+
pub(crate) fn requires_external_funding(&self) -> bool {
746+
self.inputs.iter().find(|input| match input.1 {
747+
PackageSolvingData::HolderFundingOutput(ref outp) => outp.opt_anchors(),
748+
_ => false,
749+
}).is_some()
750+
}
751+
716752
pub (crate) fn build_package(txid: Txid, vout: u32, input_solving_data: PackageSolvingData, soonest_conf_deadline: u32, aggregable: bool, height_original: u32) -> Self {
717753
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 },
754+
PackageSolvingData::RevokedOutput(..) => PackageMalleability::Malleable,
755+
PackageSolvingData::RevokedHTLCOutput(..) => PackageMalleability::Malleable,
756+
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => PackageMalleability::Malleable,
757+
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => PackageMalleability::Malleable,
758+
PackageSolvingData::HolderHTLCOutput(..) => PackageMalleability::Untractable,
759+
PackageSolvingData::HolderFundingOutput(..) => PackageMalleability::Untractable,
724760
};
725761
let mut inputs = Vec::with_capacity(1);
726762
inputs.push((BitcoinOutPoint { txid, vout }, input_solving_data));

lightning/src/ln/chan_utils.rs

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

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

0 commit comments

Comments
 (0)