Skip to content

Commit

Permalink
Merge pull request #7 from p2pderivatives/add-get-latest-holder-commi…
Browse files Browse the repository at this point in the history
…t-tx

Add ability to retrieve latest commitment transactions from ChannelManager
  • Loading branch information
luckysori authored Aug 10, 2023
2 parents 1e5b2a4 + 6577756 commit 420d961
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 4 deletions.
6 changes: 6 additions & 0 deletions lightning/src/chain/chainmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,12 @@ where C::Target: chain::Filter,
}
}

fn get_latest_holder_commitment_txn(&self, funding_txo: OutPoint) -> Result<Vec<bitcoin::Transaction>, ()> {
let monitors = self.monitors.read().unwrap();
let monitor = monitors.get(&funding_txo).ok_or(())?;
Ok(monitor.monitor.get_latest_holder_commitment_txn_internal(&self.logger))
}

/// Note that we persist the given `ChannelMonitor` update while holding the
/// `ChainMonitor` monitors lock.
fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus {
Expand Down
11 changes: 10 additions & 1 deletion lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,11 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
self.inner.lock().unwrap().get_latest_holder_commitment_txn(logger)
}

pub(crate) fn get_latest_holder_commitment_txn_internal<L: Deref>(&self, logger: &L) -> Vec<Transaction>
where L::Target: Logger {
self.inner.lock().unwrap().get_latest_holder_commitment_txn_internal(logger)
}

/// Unsafe test-only version of get_latest_holder_commitment_txn used by our test framework
/// to bypass HolderCommitmentTransaction state update lockdown after signature and generate
/// revoked commitment transaction.
Expand Down Expand Up @@ -2848,8 +2853,12 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
}

pub fn get_latest_holder_commitment_txn<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
log_debug!(logger, "Getting signed latest holder commitment transaction!");
self.holder_tx_signed = true;
self.get_latest_holder_commitment_txn_internal(logger)
}

pub(crate) fn get_latest_holder_commitment_txn_internal<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
log_debug!(logger, "Getting signed latest holder commitment transaction!");
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
let txid = commitment_tx.txid();
let mut holder_transactions = vec![commitment_tx];
Expand Down
3 changes: 3 additions & 0 deletions lightning/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ pub trait Watch<ChannelSigner: WriteableEcdsaChannelSigner> {
/// Update the outpoint funding the channel.
fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus;

/// Get the latest commitment transaction to broadcast
fn get_latest_holder_commitment_txn(&self, funding_txo: OutPoint) -> Result<Vec<bitcoin::Transaction>, ()>;

/// Returns any monitor events since the last call. Subsequent calls must only return new
/// events.
///
Expand Down
13 changes: 11 additions & 2 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,14 @@ pub struct ChannelTransactionParameters {
/// The late-bound counterparty channel transaction parameters.
/// These parameters are populated at the point in the protocol where the counterparty provides them.
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
/// The late-bound funding outpoint
/// The late-bound funding outpoint.
///
/// If it's a vanilla LN channel, this value corresponds to the actual funding outpoint that
/// goes on-chain when the channel is created.
///
/// If instead we're dealing with a split channel, this value corresponds to the output of a
/// glue transaction which sits in between the funding transaction and the commitment
/// transaction.
pub funding_outpoint: Option<chain::transaction::OutPoint>,
/// Are anchors (zero fee HTLC transaction variant) used for this channel. Boolean is
/// serialization backwards-compatible.
Expand All @@ -832,7 +839,9 @@ pub struct ChannelTransactionParameters {
/// It is intended merely for backwards compatibility with signers that need it.
/// There is no support for this feature in LDK channel negotiation.
pub opt_non_zero_fee_anchors: Option<()>,
///
/// This value always corresponds to the actual funding outpoint. This is different to
/// [`ChannelTransactionParameters::funding_outpoint`], which varies depending on the type
/// of Lightning channel we have.
pub original_funding_outpoint: Option<chain::transaction::OutPoint>,
}

Expand Down
48 changes: 47 additions & 1 deletion lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5119,8 +5119,54 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
return Ok((Some(channel_ready), announcement_sigs));
}
}

// If we have a vanilla LN channel, this checks if the transaction
// spends from the actual funding output. That could be either a
// commitment transaction or a mutual close transaction.
//
// If we have a split channel, this checks if the transaction spends
// from the glue output. That could only be a commitment
// transaction.
let is_funding_or_glue_txo = |prev_outpoint: &bitcoin::OutPoint| -> bool {
prev_outpoint == &funding_txo.into_bitcoin_outpoint()
};

// This check only runs if the check above returns `false`. We know
// that a vanilla LN channel can only be closed by spending from the
// original funding output, so in this check we are only considering
// split channels.
//
// The other ways in which a split channel could be closed are:
//
// - Through a mutual close of the _LN_ channel, which would spend
// directly from the original funding output.
//
// - Through the publication of a revoked commitment transaction
// spending from the original funding output!
//
// And that's exactly what we check here: whether the transaction
// spends from the original funding output and, if it does, whether
// the transaction is NOT the split transaction (the only other
// possible option).
//
// We do not announce the closing of the LN channel with the split
// transaction, because that is reserved to either mutual close or
// commitment transactions. LDK will only react to this announcement
// once, so we should not waste it on the split transaction, as this
// can lead to loss of funds.
let is_final_tx_spending_from_original_funding_txo = |prev_outpoint: &bitcoin::OutPoint, outputs: &[bitcoin::TxOut]| -> bool {
match self.get_original_funding_txo().map(|x| x.into_bitcoin_outpoint()) {
Some(original_funding_outpoint) => {
// Transaction spends from actual funding output.
prev_outpoint == &original_funding_outpoint &&
// Transaction is _not_ a split transaction.
!(outputs.len() == 2 && outputs[0].script_pubkey == outputs[1].script_pubkey)
}
None => false,
}
};
for inp in tx.input.iter() {
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
if is_funding_or_glue_txo(&inp.previous_output) || is_final_tx_spending_from_original_funding_txo(&inp.previous_output, &tx.output) {
log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.txid(), inp.previous_output.txid, inp.previous_output.vout, log_bytes!(self.channel_id()));
return Err(ClosureReason::CommitmentTxConfirmed);
}
Expand Down
16 changes: 16 additions & 0 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,17 @@ where
let chan = &mut channel_lock.channel;

chan.set_funding_outpoint(funding_outpoint, channel_value, own_balance, false, &self.logger);
if ChannelMonitorUpdateStatus::Completed != self.chain_monitor.update_channel_funding_txo(chan.get_original_funding_txo().unwrap(), *funding_outpoint, channel_value) {
log_error!(self.logger, "Error setting funding txo on chain monitor");
}
}

fn get_latest_holder_commitment_txn_internal(&self, channel_lock: &ChannelLock<<SP::Target as SignerProvider>::Signer>) -> Result<Vec<Transaction>, APIError> {
let chan = &channel_lock.channel;

self.chain_monitor.get_latest_holder_commitment_txn(
chan.get_original_funding_txo().ok_or_else(|| APIError::APIMisuseError { err: "Channel does not have a funding txo.".to_string() })?
).map_err(|_| APIError::APIMisuseError { err: "No channel monitor for channel".to_string() })
}

fn close_channel_internal(&self, channel_id: &[u8; 32], counterparty_node_id: &PublicKey, target_feerate_sats_per_1000_weight: Option<u32>) -> Result<(), APIError> {
Expand Down Expand Up @@ -2146,6 +2157,11 @@ where
self.set_funding_outpoint_internal(channel_lock, funding_output, channel_value_satoshis, value_to_self_msat);
}

///
pub fn get_latest_holder_commitment_txn(&self, channel_lock: &ChannelLock<<SP::Target as SignerProvider>::Signer>) -> Result<Vec<Transaction>, APIError> {
self.get_latest_holder_commitment_txn_internal(channel_lock)
}

#[inline]
fn finish_force_close_channel(&self, shutdown_res: ShutdownResult) {
let (monitor_update_option, mut failed_htlcs) = shutdown_res;
Expand Down
4 changes: 4 additions & 0 deletions lightning/src/util/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ impl<'a> chain::Watch<EnforcingSigner> for TestChainMonitor<'a> {
fn update_channel_funding_txo(&self, _: OutPoint, new_funding_txo: OutPoint, _: u64) -> chain::ChannelMonitorUpdateStatus {
todo!()
}

fn get_latest_holder_commitment_txn(&self, _funding_txo: OutPoint) -> Result<Vec<bitcoin::Transaction>, ()> {
todo!()
}
}

pub struct TestPersister {
Expand Down

0 comments on commit 420d961

Please sign in to comment.