Skip to content

Commit 4e43bcd

Browse files
committed
Check confirmation of pending funding transactions
When transactions confirm or the best block is updated, check if any pending splice funding transactions have confirmed to an acceptable depth. If so, send a splice_locked message to the counterparty and -- if the counterparty has exchanged a splice_locked message for the same funding txid -- promote the corresponding FundingScope such that the new funding can be utilized.
1 parent b04a016 commit 4e43bcd

File tree

2 files changed

+304
-62
lines changed

2 files changed

+304
-62
lines changed

lightning/src/ln/channel.rs

Lines changed: 282 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,32 @@ impl FundingScope {
18531853
#[cfg(splicing)]
18541854
struct PendingSplice {
18551855
pub our_funding_contribution: i64,
1856+
sent_funding_txid: Option<Txid>,
1857+
received_funding_txid: Option<Txid>,
1858+
}
1859+
1860+
/// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
1861+
struct ConfirmedTransaction<'a> {
1862+
tx: &'a Transaction,
1863+
txid: Option<Txid>,
1864+
}
1865+
1866+
impl<'a> ConfirmedTransaction<'a> {
1867+
/// Returns the underlying [`Transaction`].
1868+
pub fn tx(&self) -> &'a Transaction {
1869+
self.tx
1870+
}
1871+
1872+
/// Returns the [`Txid`], computing and caching it if necessary.
1873+
pub fn txid(&mut self) -> Txid {
1874+
*self.txid.get_or_insert_with(|| self.tx.compute_txid())
1875+
}
1876+
}
1877+
1878+
impl<'a> From<&'a Transaction> for ConfirmedTransaction<'a> {
1879+
fn from(tx: &'a Transaction) -> Self {
1880+
ConfirmedTransaction { tx, txid: None }
1881+
}
18561882
}
18571883

18581884
/// Contains everything about the channel including state, and various flags.
@@ -4855,6 +4881,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48554881
self.get_initial_counterparty_commitment_signature(funding, logger)
48564882
}
48574883

4884+
#[cfg(splicing)]
4885+
fn check_get_splice_locked<L: Deref>(
4886+
&mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32,
4887+
logger: &L,
4888+
) -> Option<msgs::SpliceLocked>
4889+
where
4890+
L::Target: Logger,
4891+
{
4892+
if !self.check_funding_confirmations(funding, height) {
4893+
return None;
4894+
}
4895+
4896+
let confirmed_funding_txid = match funding.get_funding_txid() {
4897+
Some(funding_txid) => funding_txid,
4898+
None => {
4899+
debug_assert!(false);
4900+
return None;
4901+
},
4902+
};
4903+
4904+
match pending_splice.sent_funding_txid {
4905+
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
4906+
debug_assert!(false);
4907+
None
4908+
},
4909+
_ => {
4910+
Some(msgs::SpliceLocked {
4911+
channel_id: self.channel_id(),
4912+
splice_txid: confirmed_funding_txid,
4913+
})
4914+
},
4915+
}
4916+
}
4917+
48584918
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
48594919
let is_coinbase = funding
48604920
.funding_transaction
@@ -4888,6 +4948,107 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48884948

48894949
return true;
48904950
}
4951+
4952+
fn check_for_funding_tx_confirmed<L: Deref>(
4953+
&mut self, funding: &mut FundingScope, block_hash: &BlockHash, height: u32,
4954+
index_in_block: usize, tx: &mut ConfirmedTransaction, logger: &L,
4955+
) -> Result<bool, ClosureReason>
4956+
where
4957+
L::Target: Logger
4958+
{
4959+
let funding_txo = match funding.get_funding_txo() {
4960+
Some(funding_txo) => funding_txo,
4961+
None => {
4962+
debug_assert!(false);
4963+
return Ok(false);
4964+
},
4965+
};
4966+
4967+
let mut is_funding_tx_confirmed = false;
4968+
4969+
// Check if the transaction is the expected funding transaction, and if it is,
4970+
// check that it pays the right amount to the right script.
4971+
if funding.funding_tx_confirmation_height == 0 {
4972+
if tx.txid() == funding_txo.txid {
4973+
let tx = tx.tx();
4974+
let txo_idx = funding_txo.index as usize;
4975+
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != funding.get_funding_redeemscript().to_p2wsh() ||
4976+
tx.output[txo_idx].value.to_sat() != funding.get_value_satoshis() {
4977+
if funding.is_outbound() {
4978+
// If we generated the funding transaction and it doesn't match what it
4979+
// should, the client is really broken and we should just panic and
4980+
// tell them off. That said, because hash collisions happen with high
4981+
// probability in fuzzing mode, if we're fuzzing we just close the
4982+
// channel and move on.
4983+
#[cfg(not(fuzzing))]
4984+
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4985+
}
4986+
self.update_time_counter += 1;
4987+
let err_reason = "funding tx had wrong script/value or output index";
4988+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
4989+
} else {
4990+
if funding.is_outbound() {
4991+
if !tx.is_coinbase() {
4992+
for input in tx.input.iter() {
4993+
if input.witness.is_empty() {
4994+
// We generated a malleable funding transaction, implying we've
4995+
// just exposed ourselves to funds loss to our counterparty.
4996+
#[cfg(not(fuzzing))]
4997+
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4998+
}
4999+
}
5000+
}
5001+
}
5002+
5003+
// The acceptor of v1-established channels doesn't have the funding
5004+
// transaction until it is seen on chain. Set it so that minimum_depth
5005+
// checks can tell if the coinbase transaction was used.
5006+
if funding.funding_transaction.is_none() {
5007+
funding.funding_transaction = Some(tx.clone());
5008+
}
5009+
5010+
funding.funding_tx_confirmation_height = height;
5011+
funding.funding_tx_confirmed_in = Some(*block_hash);
5012+
funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
5013+
Ok(scid) => Some(scid),
5014+
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
5015+
};
5016+
}
5017+
5018+
is_funding_tx_confirmed = true;
5019+
}
5020+
}
5021+
5022+
Ok(is_funding_tx_confirmed)
5023+
}
5024+
5025+
fn check_for_funding_tx_spent<L: Deref>(
5026+
&mut self, funding: &FundingScope, tx: &Transaction, logger: &L,
5027+
) -> Result<(), ClosureReason>
5028+
where
5029+
L::Target: Logger
5030+
{
5031+
let funding_txo = match funding.get_funding_txo() {
5032+
Some(funding_txo) => funding_txo,
5033+
None => {
5034+
debug_assert!(false);
5035+
return Ok(());
5036+
},
5037+
};
5038+
5039+
for input in tx.input.iter() {
5040+
if input.previous_output == funding_txo.into_bitcoin_outpoint() {
5041+
log_info!(
5042+
logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}",
5043+
tx.compute_txid(), input.previous_output.txid, input.previous_output.vout,
5044+
&self.channel_id(),
5045+
);
5046+
return Err(ClosureReason::CommitmentTxConfirmed);
5047+
}
5048+
}
5049+
5050+
Ok(())
5051+
}
48915052
}
48925053

48935054
// Internal utility functions for channels
@@ -5068,6 +5229,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
50685229
pending_splice: Option<PendingSplice>,
50695230
}
50705231

5232+
#[cfg(splicing)]
5233+
macro_rules! promote_splice_funding {
5234+
($self: expr, $funding: expr) => {
5235+
core::mem::swap(&mut $self.funding, $funding);
5236+
$self.pending_splice = None;
5237+
$self.pending_funding.clear();
5238+
$self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
5239+
}
5240+
}
5241+
50715242
#[cfg(any(test, fuzzing))]
50725243
struct CommitmentTxInfoCached {
50735244
fee: u64,
@@ -8198,75 +8369,80 @@ impl<SP: Deref> FundedChannel<SP> where
81988369
NS::Target: NodeSigner,
81998370
L::Target: Logger
82008371
{
8201-
let mut msgs = (None, None);
8202-
if let Some(funding_txo) = self.funding.get_funding_txo() {
8203-
for &(index_in_block, tx) in txdata.iter() {
8204-
// Check if the transaction is the expected funding transaction, and if it is,
8205-
// check that it pays the right amount to the right script.
8206-
if self.funding.funding_tx_confirmation_height == 0 {
8207-
if tx.compute_txid() == funding_txo.txid {
8208-
let txo_idx = funding_txo.index as usize;
8209-
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.funding.get_funding_redeemscript().to_p2wsh() ||
8210-
tx.output[txo_idx].value.to_sat() != self.funding.get_value_satoshis() {
8211-
if self.funding.is_outbound() {
8212-
// If we generated the funding transaction and it doesn't match what it
8213-
// should, the client is really broken and we should just panic and
8214-
// tell them off. That said, because hash collisions happen with high
8215-
// probability in fuzzing mode, if we're fuzzing we just close the
8216-
// channel and move on.
8217-
#[cfg(not(fuzzing))]
8218-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8219-
}
8220-
self.context.update_time_counter += 1;
8221-
let err_reason = "funding tx had wrong script/value or output index";
8222-
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8223-
} else {
8224-
if self.funding.is_outbound() {
8225-
if !tx.is_coinbase() {
8226-
for input in tx.input.iter() {
8227-
if input.witness.is_empty() {
8228-
// We generated a malleable funding transaction, implying we've
8229-
// just exposed ourselves to funds loss to our counterparty.
8230-
#[cfg(not(fuzzing))]
8231-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8232-
}
8233-
}
8234-
}
8235-
}
8372+
for &(index_in_block, tx) in txdata.iter() {
8373+
let mut confirmed_tx = ConfirmedTransaction::from(tx);
8374+
8375+
// If we allow 1-conf funding, we may need to check for channel_ready or splice_locked here
8376+
// and send it immediately instead of waiting for a best_block_updated call (which may have
8377+
// already happened for this block).
8378+
let is_funding_tx_confirmed = self.context.check_for_funding_tx_confirmed(
8379+
&mut self.funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8380+
)?;
82368381

8237-
// The acceptor of v1-established channels doesn't have the funding
8238-
// transaction until it is seen on chain. Set it so that minimum_depth
8239-
// checks can tell if the coinbase transaction was used.
8240-
if self.funding.funding_transaction.is_none() {
8241-
self.funding.funding_transaction = Some(tx.clone());
8242-
}
8382+
if is_funding_tx_confirmed {
8383+
for &(_, tx) in txdata.iter() {
8384+
self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8385+
}
82438386

8244-
self.funding.funding_tx_confirmation_height = height;
8245-
self.funding.funding_tx_confirmed_in = Some(*block_hash);
8246-
self.funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
8247-
Ok(scid) => Some(scid),
8248-
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
8249-
}
8250-
}
8251-
}
8252-
// If we allow 1-conf funding, we may need to check for channel_ready here and
8253-
// send it immediately instead of waiting for a best_block_updated call (which
8254-
// may have already happened for this block).
8255-
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8256-
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8257-
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8258-
msgs = (Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs);
8387+
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8388+
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8389+
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8390+
return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs));
8391+
}
8392+
}
8393+
8394+
#[cfg(splicing)]
8395+
let mut confirmed_funding = None;
8396+
#[cfg(splicing)]
8397+
for funding in self.pending_funding.iter_mut() {
8398+
if self.context.check_for_funding_tx_confirmed(
8399+
funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8400+
)? {
8401+
if confirmed_funding.is_some() {
8402+
let err_reason = "splice tx of another pending funding already confirmed";
8403+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
82598404
}
8405+
8406+
confirmed_funding = Some(funding);
8407+
}
8408+
}
8409+
8410+
#[cfg(splicing)]
8411+
if let Some(funding) = confirmed_funding {
8412+
let pending_splice = match self.pending_splice.as_mut() {
8413+
Some(pending_splice) => pending_splice,
8414+
None => {
8415+
// TODO: Move pending_funding into pending_splice?
8416+
debug_assert!(false);
8417+
// TODO: Error instead?
8418+
return Ok((None, None));
8419+
},
8420+
};
8421+
8422+
for &(_, tx) in txdata.iter() {
8423+
self.context.check_for_funding_tx_spent(funding, tx, logger)?;
82608424
}
8261-
for inp in tx.input.iter() {
8262-
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
8263-
log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.compute_txid(), inp.previous_output.txid, inp.previous_output.vout, &self.context.channel_id());
8264-
return Err(ClosureReason::CommitmentTxConfirmed);
8425+
8426+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8427+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8428+
8429+
pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8430+
if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8431+
promote_splice_funding!(self, funding);
8432+
8433+
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8434+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs));
82658435
}
8436+
8437+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), None));
82668438
}
82678439
}
8440+
8441+
self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8442+
82688443
}
8269-
Ok(msgs)
8444+
8445+
Ok((None, None))
82708446
}
82718447

82728448
/// When a new block is connected, we check the height of the block against outbound holding
@@ -8359,6 +8535,49 @@ impl<SP: Deref> FundedChannel<SP> where
83598535
return Err(ClosureReason::FundingTimedOut);
83608536
}
83618537

8538+
#[cfg(splicing)]
8539+
let mut confirmed_funding = None;
8540+
#[cfg(splicing)]
8541+
for funding in self.pending_funding.iter_mut() {
8542+
if confirmed_funding.is_some() {
8543+
let err_reason = "splice tx of another pending funding already confirmed";
8544+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8545+
}
8546+
8547+
confirmed_funding = Some(funding);
8548+
}
8549+
8550+
#[cfg(splicing)]
8551+
if let Some(funding) = confirmed_funding {
8552+
let pending_splice = match self.pending_splice.as_mut() {
8553+
Some(pending_splice) => pending_splice,
8554+
None => {
8555+
// TODO: Move pending_funding into pending_splice?
8556+
debug_assert!(false);
8557+
// TODO: Error instead?
8558+
return Ok((None, timed_out_htlcs, None));
8559+
},
8560+
};
8561+
8562+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8563+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8564+
8565+
pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8566+
if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8567+
promote_splice_funding!(self, funding);
8568+
8569+
let mut announcement_sigs = None;
8570+
if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8571+
announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8572+
}
8573+
8574+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, announcement_sigs));
8575+
}
8576+
8577+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, None));
8578+
}
8579+
}
8580+
83628581
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
83638582
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
83648583
} else { None };
@@ -8690,6 +8909,8 @@ impl<SP: Deref> FundedChannel<SP> where
86908909

86918910
self.pending_splice = Some(PendingSplice {
86928911
our_funding_contribution: our_funding_contribution_satoshis,
8912+
sent_funding_txid: None,
8913+
received_funding_txid: None,
86938914
});
86948915

86958916
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);

0 commit comments

Comments
 (0)