Skip to content

Commit

Permalink
Allow holder commitment and HTLC signature requests to fail
Browse files Browse the repository at this point in the history
As part of the ongoing async signer work, our holder signatures must
also be capable of being obtained asynchronously. We rely on our
existing `ChainMonitor::rebroadcast_pending_claims` method to retry
pending onchain claims by re-signing and rebroadcasting transactions.
Unfortunately, we cannot retry said claims without them being registered
first, so if we're not able to obtain the signature synchronously, we
must return the transaction as unsigned and ensure it is not broadcast.
  • Loading branch information
wpaulino committed Jan 25, 2024
1 parent 15c9f5b commit 242b20d
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 69 deletions.
50 changes: 30 additions & 20 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,8 +1396,8 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
/// Loads the funding txo and outputs to watch into the given `chain::Filter` by repeatedly
/// calling `chain::Filter::register_output` and `chain::Filter::register_tx` until all outputs
/// have been registered.
pub fn load_outputs_to_watch<F: Deref, L: Deref>(&self, filter: &F, logger: &L)
where
pub fn load_outputs_to_watch<F: Deref, L: Deref>(&self, filter: &F, logger: &L)
where
F::Target: chain::Filter, L::Target: Logger,
{
let lock = self.inner.lock().unwrap();
Expand Down Expand Up @@ -1542,21 +1542,16 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
self.inner.lock().unwrap().counterparty_node_id
}

/// Used by [`ChannelManager`] deserialization to broadcast the latest holder state if its copy
/// of the channel state was out-of-date.
///
/// You may also use this to broadcast the latest local commitment transaction, either because
/// You may use this to broadcast the latest local commitment transaction, either because
/// a monitor update failed or because we've fallen behind (i.e. we've received proof that our
/// counterparty side knows a revocation secret we gave them that they shouldn't know).
///
/// Broadcasting these transactions in the second case is UNSAFE, as they allow counterparty
/// Broadcasting these transactions in this manner is UNSAFE, as they allow counterparty
/// side to punish you. Nevertheless you may want to broadcast them if counterparty doesn't
/// close channel with their commitment transaction after a substantial amount of time. Best
/// may be to contact the other node operator out-of-band to coordinate other options available
/// to you.
///
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
pub fn get_latest_holder_commitment_txn<L: Deref>(&self, logger: &L) -> Vec<Transaction>
pub fn get_latest_holder_commitment_txn<L: Deref>(&self, logger: &L) -> Result<Vec<Transaction>, ()>
where L::Target: Logger {
let mut inner = self.inner.lock().unwrap();
let logger = WithChannelMonitor::from_impl(logger, &*inner);
Expand Down Expand Up @@ -1789,6 +1784,12 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
pub fn set_counterparty_payment_script(&self, script: ScriptBuf) {
self.inner.lock().unwrap().counterparty_payment_script = script;
}

#[cfg(test)]
pub fn do_signer_call<F: FnMut(&Signer) -> ()>(&self, mut f: F) {
let inner = self.inner.lock().unwrap();
f(&inner.onchain_tx_handler.signer);
}
}

impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
Expand Down Expand Up @@ -3462,16 +3463,19 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {

fn get_latest_holder_commitment_txn<L: Deref>(
&mut self, logger: &WithChannelMonitor<L>,
) -> Vec<Transaction> where L::Target: Logger {
) -> Result<Vec<Transaction>, ()> where L::Target: Logger {
log_debug!(logger, "Getting signed latest holder commitment transaction!");
self.holder_tx_signed = true;
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
let commitment_tx = self.onchain_tx_handler.get_maybe_signed_holder_tx(&self.funding_redeemscript);
if commitment_tx.input.iter().any(|input| input.witness.is_empty()) {
return Err(());
}
let txid = commitment_tx.txid();
let mut holder_transactions = vec![commitment_tx];
// When anchor outputs are present, the HTLC transactions are only valid once the commitment
// transaction confirms.
if self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() {
return holder_transactions;
return Ok(holder_transactions);
}
for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() {
if let Some(vout) = htlc.0.transaction_output_index {
Expand All @@ -3488,15 +3492,18 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
// confirmed in the next block.
continue;
} else { None };
if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(
&::bitcoin::OutPoint { txid, vout }, &preimage) {
holder_transactions.push(htlc_tx);
if let Some(htlc_tx) = self.onchain_tx_handler.get_maybe_signed_htlc_tx(
&::bitcoin::OutPoint { txid, vout }, &preimage
) {
if !htlc_tx.input.iter().any(|input| input.witness.is_empty()) {
holder_transactions.push(htlc_tx);
}
}
}
}
// We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do.
// The data will be re-generated and tracked in check_spend_holder_transaction if we get a confirmation.
holder_transactions
Ok(holder_transactions)
}

#[cfg(any(test,feature = "unsafe_revoked_tx_signing"))]
Expand All @@ -3521,9 +3528,12 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
continue;
}
} else { None };
if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx(
&::bitcoin::OutPoint { txid, vout }, &preimage) {
holder_transactions.push(htlc_tx);
if let Some(htlc_tx) = self.onchain_tx_handler.get_maybe_signed_htlc_tx(
&::bitcoin::OutPoint { txid, vout }, &preimage
) {
if !htlc_tx.input.iter().any(|input| input.witness.is_empty()) {
holder_transactions.push(htlc_tx);
}
}
}
}
Expand Down
61 changes: 38 additions & 23 deletions lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,13 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
}
match claim {
OnchainClaim::Tx(tx) => {
let log_start = if bumped_feerate { "Broadcasting RBF-bumped" } else { "Rebroadcasting" };
log_info!(logger, "{} onchain {}", log_start, log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
if tx.input.iter().any(|input| input.witness.is_empty()) {
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.txid());
} else {
let log_start = if bumped_feerate { "Broadcasting RBF-bumped" } else { "Rebroadcasting" };
log_info!(logger, "{} onchain {}", log_start, log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
}
},
OnchainClaim::Event(event) => {
let log_start = if bumped_feerate { "Yielding fee-bumped" } else { "Replaying" };
Expand Down Expand Up @@ -636,8 +640,8 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
let commitment_tx_feerate_sat_per_1000_weight =
compute_feerate_sat_per_1000_weight(fee_sat, tx.weight().to_wu());
if commitment_tx_feerate_sat_per_1000_weight >= package_target_feerate_sat_per_1000_weight {
log_debug!(logger, "Pre-signed {} already has feerate {} sat/kW above required {} sat/kW",
log_tx!(tx), commitment_tx_feerate_sat_per_1000_weight,
log_debug!(logger, "Pre-signed commitment {} already has feerate {} sat/kW above required {} sat/kW",
tx.txid(), commitment_tx_feerate_sat_per_1000_weight,
package_target_feerate_sat_per_1000_weight);
return Some((new_timer, 0, OnchainClaim::Tx(tx.clone())));
}
Expand Down Expand Up @@ -776,8 +780,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
// `OnchainClaim`.
let claim_id = match claim {
OnchainClaim::Tx(tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
if tx.input.iter().any(|input| input.witness.is_empty()) {
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", tx.txid());
} else {
log_info!(logger, "Broadcasting onchain {}", log_tx!(tx));
broadcaster.broadcast_transactions(&[&tx]);
}
ClaimId(tx.txid().to_byte_array())
},
OnchainClaim::Event(claim_event) => {
Expand Down Expand Up @@ -969,8 +977,13 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
) {
match bump_claim {
OnchainClaim::Tx(bump_tx) => {
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
if bump_tx.input.iter().any(|input| input.witness.is_empty()) {
log_info!(logger, "Waiting for signature of RBF-bumped unsigned onchain transaction {}",
bump_tx.txid());
} else {
log_info!(logger, "Broadcasting RBF-bumped onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
}
},
OnchainClaim::Event(claim_event) => {
log_info!(logger, "Yielding RBF-bumped onchain event to spend inputs {:?}", request.outpoints());
Expand Down Expand Up @@ -1052,8 +1065,12 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
request.set_feerate(new_feerate);
match bump_claim {
OnchainClaim::Tx(bump_tx) => {
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
if bump_tx.input.iter().any(|input| input.witness.is_empty()) {
log_info!(logger, "Waiting for signature of unsigned onchain transaction {}", bump_tx.txid());
} else {
log_info!(logger, "Broadcasting onchain {}", log_tx!(bump_tx));
broadcaster.broadcast_transactions(&[&bump_tx]);
}
},
OnchainClaim::Event(claim_event) => {
log_info!(logger, "Yielding onchain event after reorg to spend inputs {:?}", request.outpoints());
Expand Down Expand Up @@ -1106,13 +1123,10 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
&self.holder_commitment.trust().built_transaction().transaction
}

//TODO: getting lastest holder transactions should be infallible and result in us "force-closing the channel", but we may
// have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after OutboundV1Channel::get_funding_created,
// before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing
// to monitor before.
pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
let sig = self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment");
self.holder_commitment.add_holder_sig(funding_redeemscript, sig)
pub(crate) fn get_maybe_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction {
self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx)
.map(|sig| self.holder_commitment.add_holder_sig(funding_redeemscript, sig))
.unwrap_or_else(|_| self.get_unsigned_holder_commitment_tx().clone())
}

#[cfg(any(test, feature="unsafe_revoked_tx_signing"))]
Expand All @@ -1121,7 +1135,7 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
self.holder_commitment.add_holder_sig(funding_redeemscript, sig)
}

pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
pub(crate) fn get_maybe_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
let get_signed_htlc_tx = |holder_commitment: &HolderCommitmentTransaction| {
let trusted_tx = holder_commitment.trust();
if trusted_tx.txid() != outp.txid {
Expand Down Expand Up @@ -1149,10 +1163,11 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
preimage: preimage.clone(),
counterparty_sig: counterparty_htlc_sig.clone(),
};
let htlc_sig = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx).unwrap();
htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness(
htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage,
);
if let Ok(htlc_sig) = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx) {
htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness(
htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage,
);
}
Some(htlc_tx)
};

Expand Down
4 changes: 2 additions & 2 deletions lightning/src/chain/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,10 +637,10 @@ impl PackageSolvingData {
match self {
PackageSolvingData::HolderHTLCOutput(ref outp) => {
debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx());
return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage);
onchain_handler.get_maybe_signed_htlc_tx(outpoint, &outp.preimage)
}
PackageSolvingData::HolderFundingOutput(ref outp) => {
return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript));
Some(onchain_handler.get_maybe_signed_holder_tx(&outp.funding_redeemscript))
}
_ => { panic!("API Error!"); }
}
Expand Down
Loading

0 comments on commit 242b20d

Please sign in to comment.