Skip to content

Commit 2f0ca90

Browse files
committed
WIP: Check all funding transactions
1 parent 7ccb545 commit 2f0ca90

File tree

2 files changed

+222
-63
lines changed

2 files changed

+222
-63
lines changed

lightning/src/ln/channel.rs

Lines changed: 213 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,8 @@ impl FundingScope {
18571857
#[cfg(splicing)]
18581858
struct PendingSplice {
18591859
pub our_funding_contribution: i64,
1860+
sent_funding_txid: Option<Txid>,
1861+
received_funding_txid: Option<Txid>,
18601862
}
18611863

18621864
/// Contains everything about the channel including state, and various flags.
@@ -4859,6 +4861,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48594861
self.get_initial_counterparty_commitment_signature(funding, logger)
48604862
}
48614863

4864+
#[cfg(splicing)]
4865+
fn check_get_splice_locked<L: Deref>(
4866+
&mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32,
4867+
logger: &L,
4868+
) -> Option<msgs::SpliceLocked>
4869+
where
4870+
L::Target: Logger,
4871+
{
4872+
if !self.check_funding_confirmations(funding, height) {
4873+
return None;
4874+
}
4875+
4876+
let confirmed_funding_txid = match funding.get_funding_txo().map(|txo| txo.txid) {
4877+
Some(funding_txid) => funding_txid,
4878+
None => {
4879+
debug_assert!(false);
4880+
return None;
4881+
},
4882+
};
4883+
4884+
match pending_splice.sent_funding_txid {
4885+
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
4886+
debug_assert!(false);
4887+
None
4888+
},
4889+
_ => {
4890+
Some(msgs::SpliceLocked {
4891+
channel_id: self.channel_id(),
4892+
splice_txid: confirmed_funding_txid,
4893+
})
4894+
},
4895+
}
4896+
}
4897+
48624898
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
48634899
let is_coinbase = funding
48644900
.funding_transaction
@@ -4892,6 +4928,85 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48924928

48934929
return true;
48944930
}
4931+
4932+
fn check_for_funding_tx<'a, L: Deref>(
4933+
&mut self, funding: &mut FundingScope, block_hash: &BlockHash, height: u32,
4934+
txdata: &'a TransactionData, logger: &L,
4935+
) -> Result<Option<&'a Transaction>, ClosureReason>
4936+
where
4937+
L::Target: Logger
4938+
{
4939+
let funding_txo = match funding.get_funding_txo() {
4940+
Some(funding_txo) => funding_txo,
4941+
None => {
4942+
debug_assert!(false);
4943+
return Ok(None);
4944+
},
4945+
};
4946+
4947+
let mut confirmed_funding_tx = None;
4948+
for &(index_in_block, tx) in txdata.iter() {
4949+
// Check if the transaction is the expected funding transaction, and if it is,
4950+
// check that it pays the right amount to the right script.
4951+
if funding.funding_tx_confirmation_height == 0 {
4952+
if tx.compute_txid() == funding_txo.txid {
4953+
let txo_idx = funding_txo.index as usize;
4954+
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != funding.get_funding_redeemscript().to_p2wsh() ||
4955+
tx.output[txo_idx].value.to_sat() != funding.get_value_satoshis() {
4956+
if funding.is_outbound() {
4957+
// If we generated the funding transaction and it doesn't match what it
4958+
// should, the client is really broken and we should just panic and
4959+
// tell them off. That said, because hash collisions happen with high
4960+
// probability in fuzzing mode, if we're fuzzing we just close the
4961+
// channel and move on.
4962+
#[cfg(not(fuzzing))]
4963+
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4964+
}
4965+
self.update_time_counter += 1;
4966+
let err_reason = "funding tx had wrong script/value or output index";
4967+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
4968+
} else {
4969+
if funding.is_outbound() {
4970+
if !tx.is_coinbase() {
4971+
for input in tx.input.iter() {
4972+
if input.witness.is_empty() {
4973+
// We generated a malleable funding transaction, implying we've
4974+
// just exposed ourselves to funds loss to our counterparty.
4975+
#[cfg(not(fuzzing))]
4976+
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4977+
}
4978+
}
4979+
}
4980+
}
4981+
4982+
// The acceptor of v1-established channels doesn't have the funding
4983+
// transaction until it is seen on chain. Set it so that minimum_depth
4984+
// checks can tell if the coinbase transaction was used.
4985+
if funding.funding_transaction.is_none() {
4986+
funding.funding_transaction = Some(tx.clone());
4987+
}
4988+
4989+
funding.funding_tx_confirmation_height = height;
4990+
funding.funding_tx_confirmed_in = Some(*block_hash);
4991+
funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
4992+
Ok(scid) => Some(scid),
4993+
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
4994+
};
4995+
}
4996+
4997+
confirmed_funding_tx = Some(tx);
4998+
}
4999+
}
5000+
for inp in tx.input.iter() {
5001+
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
5002+
log_info!(logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}", tx.compute_txid(), inp.previous_output.txid, inp.previous_output.vout, &self.channel_id());
5003+
return Err(ClosureReason::CommitmentTxConfirmed);
5004+
}
5005+
}
5006+
}
5007+
5008+
Ok(confirmed_funding_tx)
5009+
}
48955010
}
48965011

48975012
// Internal utility functions for channels
@@ -5072,6 +5187,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
50725187
pending_splice: Option<PendingSplice>,
50735188
}
50745189

5190+
#[cfg(splicing)]
5191+
macro_rules! promote_splice_funding {
5192+
($self: expr, $funding: expr) => {
5193+
core::mem::swap(&mut $self.funding, $funding);
5194+
$self.pending_splice = None;
5195+
$self.pending_funding.clear();
5196+
$self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
5197+
}
5198+
}
5199+
50755200
#[cfg(any(test, fuzzing))]
50765201
struct CommitmentTxInfoCached {
50775202
fee: u64,
@@ -8202,75 +8327,61 @@ impl<SP: Deref> FundedChannel<SP> where
82028327
NS::Target: NodeSigner,
82038328
L::Target: Logger
82048329
{
8205-
let mut msgs = (None, None);
8206-
if let Some(funding_txo) = self.funding.get_funding_txo() {
8207-
for &(index_in_block, tx) in txdata.iter() {
8208-
// Check if the transaction is the expected funding transaction, and if it is,
8209-
// check that it pays the right amount to the right script.
8210-
if self.funding.funding_tx_confirmation_height == 0 {
8211-
if tx.compute_txid() == funding_txo.txid {
8212-
let txo_idx = funding_txo.index as usize;
8213-
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.funding.get_funding_redeemscript().to_p2wsh() ||
8214-
tx.output[txo_idx].value.to_sat() != self.funding.get_value_satoshis() {
8215-
if self.funding.is_outbound() {
8216-
// If we generated the funding transaction and it doesn't match what it
8217-
// should, the client is really broken and we should just panic and
8218-
// tell them off. That said, because hash collisions happen with high
8219-
// probability in fuzzing mode, if we're fuzzing we just close the
8220-
// channel and move on.
8221-
#[cfg(not(fuzzing))]
8222-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8223-
}
8224-
self.context.update_time_counter += 1;
8225-
let err_reason = "funding tx had wrong script/value or output index";
8226-
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8227-
} else {
8228-
if self.funding.is_outbound() {
8229-
if !tx.is_coinbase() {
8230-
for input in tx.input.iter() {
8231-
if input.witness.is_empty() {
8232-
// We generated a malleable funding transaction, implying we've
8233-
// just exposed ourselves to funds loss to our counterparty.
8234-
#[cfg(not(fuzzing))]
8235-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8236-
}
8237-
}
8238-
}
8239-
}
8330+
// If we allow 1-conf funding, we may need to check for channel_ready or splice_locked here
8331+
// and send it immediately instead of waiting for a best_block_updated call (which may have
8332+
// already happened for this block).
8333+
let confirmed_funding_tx = self.context.check_for_funding_tx(
8334+
&mut self.funding, block_hash, height, txdata, logger,
8335+
)?;
82408336

8241-
// The acceptor of v1-established channels doesn't have the funding
8242-
// transaction until it is seen on chain. Set it so that minimum_depth
8243-
// checks can tell if the coinbase transaction was used.
8244-
if self.funding.funding_transaction.is_none() {
8245-
self.funding.funding_transaction = Some(tx.clone());
8246-
}
8337+
if let Some(funding_tx) = confirmed_funding_tx {
8338+
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8339+
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8340+
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8341+
return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs));
8342+
}
8343+
}
82478344

8248-
self.funding.funding_tx_confirmation_height = height;
8249-
self.funding.funding_tx_confirmed_in = Some(*block_hash);
8250-
self.funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
8251-
Ok(scid) => Some(scid),
8252-
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
8253-
}
8254-
}
8255-
}
8256-
// If we allow 1-conf funding, we may need to check for channel_ready here and
8257-
// send it immediately instead of waiting for a best_block_updated call (which
8258-
// may have already happened for this block).
8259-
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8260-
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8261-
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8262-
msgs = (Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs);
8263-
}
8345+
#[cfg(splicing)]
8346+
let mut confirmed_funding = None;
8347+
#[cfg(splicing)]
8348+
for funding in self.pending_funding.iter_mut() {
8349+
if self.context.check_for_funding_tx(funding, block_hash, height, txdata, logger)?.is_some() {
8350+
if confirmed_funding.is_some() {
8351+
let err_reason = "splice tx of another pending funding already confirmed";
8352+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
82648353
}
8265-
for inp in tx.input.iter() {
8266-
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
8267-
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());
8268-
return Err(ClosureReason::CommitmentTxConfirmed);
8269-
}
8354+
8355+
confirmed_funding = Some(funding);
8356+
}
8357+
}
8358+
8359+
#[cfg(splicing)]
8360+
if let Some(funding) = confirmed_funding {
8361+
let pending_splice = match self.pending_splice.as_mut() {
8362+
Some(pending_splice) => pending_splice,
8363+
None => {
8364+
// TODO: Move pending_funding into pending_splice?
8365+
debug_assert!(false);
8366+
// TODO: Error instead?
8367+
return Ok((None, None));
8368+
},
8369+
};
8370+
8371+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8372+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8373+
8374+
let mut announcement_sigs = None;
8375+
if Some(splice_locked.splice_txid) == pending_splice.received_funding_txid {
8376+
promote_splice_funding!(self, funding);
8377+
announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
82708378
}
8379+
8380+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs));
82718381
}
82728382
}
8273-
Ok(msgs)
8383+
8384+
Ok((None, None))
82748385
}
82758386

82768387
/// When a new block is connected, we check the height of the block against outbound holding
@@ -8363,6 +8474,43 @@ impl<SP: Deref> FundedChannel<SP> where
83638474
return Err(ClosureReason::FundingTimedOut);
83648475
}
83658476

8477+
#[cfg(splicing)]
8478+
let mut confirmed_funding = None;
8479+
#[cfg(splicing)]
8480+
for funding in self.pending_funding.iter_mut() {
8481+
if confirmed_funding.is_some() {
8482+
let err_reason = "splice tx of another pending funding already confirmed";
8483+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8484+
}
8485+
8486+
confirmed_funding = Some(funding);
8487+
}
8488+
8489+
#[cfg(splicing)]
8490+
if let Some(funding) = confirmed_funding {
8491+
let pending_splice = match self.pending_splice.as_mut() {
8492+
Some(pending_splice) => pending_splice,
8493+
None => {
8494+
// TODO: Move pending_funding into pending_splice?
8495+
debug_assert!(false);
8496+
// TODO: Error instead?
8497+
return Ok((None, timed_out_htlcs, None));
8498+
},
8499+
};
8500+
8501+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8502+
let mut announcement_sigs = None;
8503+
if Some(splice_locked.splice_txid) == pending_splice.received_funding_txid {
8504+
promote_splice_funding!(self, funding);
8505+
if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8506+
announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8507+
}
8508+
}
8509+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8510+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, announcement_sigs));
8511+
}
8512+
}
8513+
83668514
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
83678515
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
83688516
} else { None };
@@ -8694,6 +8842,8 @@ impl<SP: Deref> FundedChannel<SP> where
86948842

86958843
self.pending_splice = Some(PendingSplice {
86968844
our_funding_contribution: our_funding_contribution_satoshis,
8845+
sent_funding_txid: None,
8846+
received_funding_txid: None,
86978847
});
86988848

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

lightning/src/ln/channelmanager.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11664,6 +11664,8 @@ where
1166411664

1166511665
pub(super) enum FundingConfirmedMessage {
1166611666
Establishment(msgs::ChannelReady),
11667+
#[cfg(splicing)]
11668+
Splice(msgs::SpliceLocked),
1166711669
}
1166811670

1166911671
impl<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
@@ -11725,6 +11727,13 @@ where
1172511727
log_trace!(logger, "Sending channel_ready WITHOUT channel_update for {}", funded_channel.context.channel_id());
1172611728
}
1172711729
},
11730+
#[cfg(splicing)]
11731+
Some(FundingConfirmedMessage::Splice(splice_locked)) => {
11732+
pending_msg_events.push(MessageSendEvent::SendSpliceLocked {
11733+
node_id: funded_channel.context.get_counterparty_node_id(),
11734+
msg: splice_locked,
11735+
});
11736+
},
1172811737
None => {},
1172911738
}
1173011739

0 commit comments

Comments
 (0)