Skip to content

Commit 71e27a4

Browse files
committed
WIP: Check all funding transactions
1 parent 6bbbce8 commit 71e27a4

File tree

2 files changed

+224
-64
lines changed

2 files changed

+224
-64
lines changed

lightning/src/ln/channel.rs

+215-64
Original file line numberDiff line numberDiff line change
@@ -1864,6 +1864,8 @@ impl FundingScope {
18641864
#[cfg(splicing)]
18651865
struct PendingSplice {
18661866
pub our_funding_contribution: i64,
1867+
sent_funding_txid: Option<Txid>,
1868+
received_funding_txid: Option<Txid>,
18671869
}
18681870

18691871
/// Contains everything about the channel including state, and various flags.
@@ -4865,6 +4867,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48654867
self.get_initial_counterparty_commitment_signature(funding, logger)
48664868
}
48674869

4870+
#[cfg(splicing)]
4871+
fn check_get_splice_locked<L: Deref>(
4872+
&mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32,
4873+
logger: &L,
4874+
) -> Option<msgs::SpliceLocked>
4875+
where
4876+
L::Target: Logger,
4877+
{
4878+
if !self.check_funding_confirmations(funding, height) {
4879+
return None;
4880+
}
4881+
4882+
let confirmed_funding_txid = match funding.get_funding_txo().map(|txo| txo.txid) {
4883+
Some(funding_txid) => funding_txid,
4884+
None => {
4885+
debug_assert!(false);
4886+
return None;
4887+
},
4888+
};
4889+
4890+
match pending_splice.sent_funding_txid {
4891+
Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
4892+
debug_assert!(false);
4893+
None
4894+
},
4895+
_ => {
4896+
Some(msgs::SpliceLocked {
4897+
channel_id: self.channel_id(),
4898+
splice_txid: confirmed_funding_txid,
4899+
})
4900+
},
4901+
}
4902+
}
4903+
48684904
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
48694905
if funding.funding_tx_confirmation_height == 0 && funding.minimum_depth != Some(0) {
48704906
return false;
@@ -4881,6 +4917,78 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
48814917

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

48864994
// Internal utility functions for channels
@@ -5061,6 +5169,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
50615169
pending_splice: Option<PendingSplice>,
50625170
}
50635171

5172+
#[cfg(splicing)]
5173+
macro_rules! promote_splice_funding {
5174+
($self: expr, $funding: expr) => {
5175+
core::mem::swap(&mut $self.funding, $funding);
5176+
$self.pending_splice = None;
5177+
$self.pending_funding.clear();
5178+
$self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
5179+
}
5180+
}
5181+
50645182
#[cfg(any(test, fuzzing))]
50655183
struct CommitmentTxInfoCached {
50665184
fee: u64,
@@ -8191,75 +8309,69 @@ impl<SP: Deref> FundedChannel<SP> where
81918309
NS::Target: NodeSigner,
81928310
L::Target: Logger
81938311
{
8194-
let mut msgs = (None, None);
8195-
if let Some(funding_txo) = self.funding.get_funding_txo() {
8196-
for &(index_in_block, tx) in txdata.iter() {
8197-
// Check if the transaction is the expected funding transaction, and if it is,
8198-
// check that it pays the right amount to the right script.
8199-
if self.funding.funding_tx_confirmation_height == 0 {
8200-
if tx.compute_txid() == funding_txo.txid {
8201-
let txo_idx = funding_txo.index as usize;
8202-
if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.funding.get_funding_redeemscript().to_p2wsh() ||
8203-
tx.output[txo_idx].value.to_sat() != self.funding.get_value_satoshis() {
8204-
if self.funding.is_outbound() {
8205-
// If we generated the funding transaction and it doesn't match what it
8206-
// should, the client is really broken and we should just panic and
8207-
// tell them off. That said, because hash collisions happen with high
8208-
// probability in fuzzing mode, if we're fuzzing we just close the
8209-
// channel and move on.
8210-
#[cfg(not(fuzzing))]
8211-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8212-
}
8213-
self.context.update_time_counter += 1;
8214-
let err_reason = "funding tx had wrong script/value or output index";
8215-
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8216-
} else {
8217-
if self.funding.is_outbound() {
8218-
if !tx.is_coinbase() {
8219-
for input in tx.input.iter() {
8220-
if input.witness.is_empty() {
8221-
// We generated a malleable funding transaction, implying we've
8222-
// just exposed ourselves to funds loss to our counterparty.
8223-
#[cfg(not(fuzzing))]
8224-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8225-
}
8226-
}
8227-
}
8228-
}
8312+
// If we allow 1-conf funding, we may need to check for channel_ready or splice_locked here
8313+
// and send it immediately instead of waiting for a best_block_updated call (which may have
8314+
// already happened for this block).
8315+
let confirmed_funding_tx = self.context.check_for_funding_tx(
8316+
&mut self.funding, block_hash, height, txdata, logger,
8317+
)?;
82298318

8230-
self.funding.funding_tx_confirmation_height = height;
8231-
self.funding.funding_tx_confirmed_in = Some(*block_hash);
8232-
self.funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
8233-
Ok(scid) => Some(scid),
8234-
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
8235-
}
8236-
}
8237-
// If this is a coinbase transaction and not a 0-conf channel
8238-
// we should update our min_depth to 100 to handle coinbase maturity
8239-
if tx.is_coinbase() &&
8240-
self.funding.minimum_depth.unwrap_or(0) > 0 &&
8241-
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
8242-
self.funding.minimum_depth = Some(COINBASE_MATURITY);
8243-
}
8244-
}
8245-
// If we allow 1-conf funding, we may need to check for channel_ready here and
8246-
// send it immediately instead of waiting for a best_block_updated call (which
8247-
// may have already happened for this block).
8248-
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8249-
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8250-
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8251-
msgs = (Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs);
8252-
}
8319+
if let Some(funding_tx) = confirmed_funding_tx {
8320+
// If this is a coinbase transaction and not a 0-conf channel
8321+
// we should update our min_depth to 100 to handle coinbase maturity
8322+
if funding_tx.is_coinbase() &&
8323+
self.funding.minimum_depth.unwrap_or(0) > 0 &&
8324+
self.funding.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
8325+
self.funding.minimum_depth = Some(COINBASE_MATURITY);
8326+
}
8327+
8328+
if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8329+
log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8330+
let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8331+
return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs));
8332+
}
8333+
}
8334+
8335+
#[cfg(splicing)]
8336+
let mut confirmed_funding = None;
8337+
#[cfg(splicing)]
8338+
for funding in self.pending_funding.iter_mut() {
8339+
if self.context.check_for_funding_tx(funding, block_hash, height, txdata, logger)?.is_some() {
8340+
if confirmed_funding.is_some() {
8341+
let err_reason = "splice tx of another pending funding already confirmed";
8342+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
82538343
}
8254-
for inp in tx.input.iter() {
8255-
if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
8256-
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());
8257-
return Err(ClosureReason::CommitmentTxConfirmed);
8258-
}
8344+
8345+
confirmed_funding = Some(funding);
8346+
}
8347+
}
8348+
8349+
#[cfg(splicing)]
8350+
if let Some(funding) = confirmed_funding {
8351+
let pending_splice = match self.pending_splice.as_mut() {
8352+
Some(pending_splice) => pending_splice,
8353+
None => {
8354+
// TODO: Move pending_funding into pending_splice?
8355+
debug_assert!(false);
8356+
// TODO: Error instead?
8357+
return Ok((None, None));
8358+
},
8359+
};
8360+
8361+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8362+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8363+
8364+
let mut announcement_sigs = None;
8365+
if Some(splice_locked.splice_txid) == pending_splice.received_funding_txid {
8366+
promote_splice_funding!(self, funding);
8367+
announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
82598368
}
8369+
8370+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs));
82608371
}
82618372
}
8262-
Ok(msgs)
8373+
8374+
Ok((None, None))
82638375
}
82648376

82658377
/// When a new block is connected, we check the height of the block against outbound holding
@@ -8352,6 +8464,43 @@ impl<SP: Deref> FundedChannel<SP> where
83528464
return Err(ClosureReason::FundingTimedOut);
83538465
}
83548466

8467+
#[cfg(splicing)]
8468+
let mut confirmed_funding = None;
8469+
#[cfg(splicing)]
8470+
for funding in self.pending_funding.iter_mut() {
8471+
if confirmed_funding.is_some() {
8472+
let err_reason = "splice tx of another pending funding already confirmed";
8473+
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8474+
}
8475+
8476+
confirmed_funding = Some(funding);
8477+
}
8478+
8479+
#[cfg(splicing)]
8480+
if let Some(funding) = confirmed_funding {
8481+
let pending_splice = match self.pending_splice.as_mut() {
8482+
Some(pending_splice) => pending_splice,
8483+
None => {
8484+
// TODO: Move pending_funding into pending_splice?
8485+
debug_assert!(false);
8486+
// TODO: Error instead?
8487+
return Ok((None, timed_out_htlcs, None));
8488+
},
8489+
};
8490+
8491+
if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8492+
let mut announcement_sigs = None;
8493+
if Some(splice_locked.splice_txid) == pending_splice.received_funding_txid {
8494+
promote_splice_funding!(self, funding);
8495+
if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8496+
announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8497+
}
8498+
}
8499+
log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8500+
return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, announcement_sigs));
8501+
}
8502+
}
8503+
83558504
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
83568505
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
83578506
} else { None };
@@ -8683,6 +8832,8 @@ impl<SP: Deref> FundedChannel<SP> where
86838832

86848833
self.pending_splice = Some(PendingSplice {
86858834
our_funding_contribution: our_funding_contribution_satoshis,
8835+
sent_funding_txid: None,
8836+
received_funding_txid: None,
86868837
});
86878838

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

lightning/src/ln/channelmanager.rs

+9
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)