@@ -1852,6 +1852,32 @@ impl FundingScope {
1852
1852
#[cfg(splicing)]
1853
1853
struct PendingSplice {
1854
1854
pub our_funding_contribution: i64,
1855
+ sent_funding_txid: Option<Txid>,
1856
+ received_funding_txid: Option<Txid>,
1857
+ }
1858
+
1859
+ /// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
1860
+ struct ConfirmedTransaction<'a> {
1861
+ tx: &'a Transaction,
1862
+ txid: Option<Txid>,
1863
+ }
1864
+
1865
+ impl<'a> ConfirmedTransaction<'a> {
1866
+ /// Returns the underlying [`Transaction`].
1867
+ pub fn tx(&self) -> &'a Transaction {
1868
+ self.tx
1869
+ }
1870
+
1871
+ /// Returns the [`Txid`], computing and caching it if necessary.
1872
+ pub fn txid(&mut self) -> Txid {
1873
+ *self.txid.get_or_insert_with(|| self.tx.compute_txid())
1874
+ }
1875
+ }
1876
+
1877
+ impl<'a> From<&'a Transaction> for ConfirmedTransaction<'a> {
1878
+ fn from(tx: &'a Transaction) -> Self {
1879
+ ConfirmedTransaction { tx, txid: None }
1880
+ }
1855
1881
}
1856
1882
1857
1883
/// Contains everything about the channel including state, and various flags.
@@ -4854,6 +4880,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4854
4880
self.get_initial_counterparty_commitment_signature(funding, logger)
4855
4881
}
4856
4882
4883
+ #[cfg(splicing)]
4884
+ fn check_get_splice_locked<L: Deref>(
4885
+ &mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32,
4886
+ logger: &L,
4887
+ ) -> Option<msgs::SpliceLocked>
4888
+ where
4889
+ L::Target: Logger,
4890
+ {
4891
+ if !self.check_funding_confirmations(funding, height) {
4892
+ return None;
4893
+ }
4894
+
4895
+ let confirmed_funding_txid = match funding.get_funding_txid() {
4896
+ Some(funding_txid) => funding_txid,
4897
+ None => {
4898
+ debug_assert!(false);
4899
+ return None;
4900
+ },
4901
+ };
4902
+
4903
+ match pending_splice.sent_funding_txid {
4904
+ Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
4905
+ debug_assert!(false);
4906
+ None
4907
+ },
4908
+ _ => {
4909
+ Some(msgs::SpliceLocked {
4910
+ channel_id: self.channel_id(),
4911
+ splice_txid: confirmed_funding_txid,
4912
+ })
4913
+ },
4914
+ }
4915
+ }
4916
+
4857
4917
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
4858
4918
let is_coinbase = funding
4859
4919
.funding_transaction
@@ -4887,6 +4947,107 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4887
4947
4888
4948
return true;
4889
4949
}
4950
+
4951
+ fn check_for_funding_tx_confirmed<L: Deref>(
4952
+ &mut self, funding: &mut FundingScope, block_hash: &BlockHash, height: u32,
4953
+ index_in_block: usize, tx: &mut ConfirmedTransaction, logger: &L,
4954
+ ) -> Result<bool, ClosureReason>
4955
+ where
4956
+ L::Target: Logger
4957
+ {
4958
+ let funding_txo = match funding.get_funding_txo() {
4959
+ Some(funding_txo) => funding_txo,
4960
+ None => {
4961
+ debug_assert!(false);
4962
+ return Ok(false);
4963
+ },
4964
+ };
4965
+
4966
+ let mut is_funding_tx_confirmed = false;
4967
+
4968
+ // Check if the transaction is the expected funding transaction, and if it is,
4969
+ // check that it pays the right amount to the right script.
4970
+ if funding.funding_tx_confirmation_height == 0 {
4971
+ if tx.txid() == funding_txo.txid {
4972
+ let tx = tx.tx();
4973
+ let txo_idx = funding_txo.index as usize;
4974
+ if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != funding.get_funding_redeemscript().to_p2wsh() ||
4975
+ tx.output[txo_idx].value.to_sat() != funding.get_value_satoshis() {
4976
+ if funding.is_outbound() {
4977
+ // If we generated the funding transaction and it doesn't match what it
4978
+ // should, the client is really broken and we should just panic and
4979
+ // tell them off. That said, because hash collisions happen with high
4980
+ // probability in fuzzing mode, if we're fuzzing we just close the
4981
+ // channel and move on.
4982
+ #[cfg(not(fuzzing))]
4983
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4984
+ }
4985
+ self.update_time_counter += 1;
4986
+ let err_reason = "funding tx had wrong script/value or output index";
4987
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
4988
+ } else {
4989
+ if funding.is_outbound() {
4990
+ if !tx.is_coinbase() {
4991
+ for input in tx.input.iter() {
4992
+ if input.witness.is_empty() {
4993
+ // We generated a malleable funding transaction, implying we've
4994
+ // just exposed ourselves to funds loss to our counterparty.
4995
+ #[cfg(not(fuzzing))]
4996
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4997
+ }
4998
+ }
4999
+ }
5000
+ }
5001
+
5002
+ // The acceptor of v1-established channels doesn't have the funding
5003
+ // transaction until it is seen on chain. Set it so that minimum_depth
5004
+ // checks can tell if the coinbase transaction was used.
5005
+ if funding.funding_transaction.is_none() {
5006
+ funding.funding_transaction = Some(tx.clone());
5007
+ }
5008
+
5009
+ funding.funding_tx_confirmation_height = height;
5010
+ funding.funding_tx_confirmed_in = Some(*block_hash);
5011
+ funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
5012
+ Ok(scid) => Some(scid),
5013
+ Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
5014
+ };
5015
+ }
5016
+
5017
+ is_funding_tx_confirmed = true;
5018
+ }
5019
+ }
5020
+
5021
+ Ok(is_funding_tx_confirmed)
5022
+ }
5023
+
5024
+ fn check_for_funding_tx_spent<L: Deref>(
5025
+ &mut self, funding: &FundingScope, tx: &Transaction, logger: &L,
5026
+ ) -> Result<(), ClosureReason>
5027
+ where
5028
+ L::Target: Logger
5029
+ {
5030
+ let funding_txo = match funding.get_funding_txo() {
5031
+ Some(funding_txo) => funding_txo,
5032
+ None => {
5033
+ debug_assert!(false);
5034
+ return Ok(());
5035
+ },
5036
+ };
5037
+
5038
+ for input in tx.input.iter() {
5039
+ if input.previous_output == funding_txo.into_bitcoin_outpoint() {
5040
+ log_info!(
5041
+ logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}",
5042
+ tx.compute_txid(), input.previous_output.txid, input.previous_output.vout,
5043
+ &self.channel_id(),
5044
+ );
5045
+ return Err(ClosureReason::CommitmentTxConfirmed);
5046
+ }
5047
+ }
5048
+
5049
+ Ok(())
5050
+ }
4890
5051
}
4891
5052
4892
5053
// Internal utility functions for channels
@@ -5067,6 +5228,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
5067
5228
pending_splice: Option<PendingSplice>,
5068
5229
}
5069
5230
5231
+ #[cfg(splicing)]
5232
+ macro_rules! promote_splice_funding {
5233
+ ($self: expr, $funding: expr) => {
5234
+ core::mem::swap(&mut $self.funding, $funding);
5235
+ $self.pending_splice = None;
5236
+ $self.pending_funding.clear();
5237
+ $self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
5238
+ }
5239
+ }
5240
+
5070
5241
#[cfg(any(test, fuzzing))]
5071
5242
struct CommitmentTxInfoCached {
5072
5243
fee: u64,
@@ -8197,75 +8368,80 @@ impl<SP: Deref> FundedChannel<SP> where
8197
8368
NS::Target: NodeSigner,
8198
8369
L::Target: Logger
8199
8370
{
8200
- let mut msgs = (None, None);
8201
- if let Some(funding_txo) = self.funding.get_funding_txo() {
8202
- for &(index_in_block, tx) in txdata.iter() {
8203
- // Check if the transaction is the expected funding transaction, and if it is,
8204
- // check that it pays the right amount to the right script.
8205
- if self.funding.funding_tx_confirmation_height == 0 {
8206
- if tx.compute_txid() == funding_txo.txid {
8207
- let txo_idx = funding_txo.index as usize;
8208
- if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != self.funding.get_funding_redeemscript().to_p2wsh() ||
8209
- tx.output[txo_idx].value.to_sat() != self.funding.get_value_satoshis() {
8210
- if self.funding.is_outbound() {
8211
- // If we generated the funding transaction and it doesn't match what it
8212
- // should, the client is really broken and we should just panic and
8213
- // tell them off. That said, because hash collisions happen with high
8214
- // probability in fuzzing mode, if we're fuzzing we just close the
8215
- // channel and move on.
8216
- #[cfg(not(fuzzing))]
8217
- panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8218
- }
8219
- self.context.update_time_counter += 1;
8220
- let err_reason = "funding tx had wrong script/value or output index";
8221
- return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8222
- } else {
8223
- if self.funding.is_outbound() {
8224
- if !tx.is_coinbase() {
8225
- for input in tx.input.iter() {
8226
- if input.witness.is_empty() {
8227
- // We generated a malleable funding transaction, implying we've
8228
- // just exposed ourselves to funds loss to our counterparty.
8229
- #[cfg(not(fuzzing))]
8230
- panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
8231
- }
8232
- }
8233
- }
8234
- }
8371
+ for &(index_in_block, tx) in txdata.iter() {
8372
+ let mut confirmed_tx = ConfirmedTransaction::from(tx);
8373
+
8374
+ // If we allow 1-conf funding, we may need to check for channel_ready or splice_locked here
8375
+ // and send it immediately instead of waiting for a best_block_updated call (which may have
8376
+ // already happened for this block).
8377
+ let is_funding_tx_confirmed = self.context.check_for_funding_tx_confirmed(
8378
+ &mut self.funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8379
+ )?;
8235
8380
8236
- // The acceptor of v1-established channels doesn't have the funding
8237
- // transaction until it is seen on chain. Set it so that minimum_depth
8238
- // checks can tell if the coinbase transaction was used.
8239
- if self.funding.funding_transaction.is_none() {
8240
- self.funding.funding_transaction = Some(tx.clone());
8241
- }
8381
+ if is_funding_tx_confirmed {
8382
+ for &(_, tx) in txdata.iter() {
8383
+ self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8384
+ }
8242
8385
8243
- self.funding.funding_tx_confirmation_height = height;
8244
- self.funding.funding_tx_confirmed_in = Some(*block_hash);
8245
- self.funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
8246
- Ok(scid) => Some(scid),
8247
- Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
8248
- }
8249
- }
8250
- }
8251
- // If we allow 1-conf funding, we may need to check for channel_ready here and
8252
- // send it immediately instead of waiting for a best_block_updated call (which
8253
- // may have already happened for this block).
8254
- if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8255
- log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8256
- let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8257
- msgs = (Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs);
8386
+ if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8387
+ log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8388
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8389
+ return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs));
8390
+ }
8391
+ }
8392
+
8393
+ #[cfg(splicing)]
8394
+ let mut confirmed_funding = None;
8395
+ #[cfg(splicing)]
8396
+ for funding in self.pending_funding.iter_mut() {
8397
+ if self.context.check_for_funding_tx_confirmed(
8398
+ funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8399
+ )? {
8400
+ if confirmed_funding.is_some() {
8401
+ let err_reason = "splice tx of another pending funding already confirmed";
8402
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8258
8403
}
8404
+
8405
+ confirmed_funding = Some(funding);
8406
+ }
8407
+ }
8408
+
8409
+ #[cfg(splicing)]
8410
+ if let Some(funding) = confirmed_funding {
8411
+ let pending_splice = match self.pending_splice.as_mut() {
8412
+ Some(pending_splice) => pending_splice,
8413
+ None => {
8414
+ // TODO: Move pending_funding into pending_splice?
8415
+ debug_assert!(false);
8416
+ // TODO: Error instead?
8417
+ return Ok((None, None));
8418
+ },
8419
+ };
8420
+
8421
+ for &(_, tx) in txdata.iter() {
8422
+ self.context.check_for_funding_tx_spent(funding, tx, logger)?;
8259
8423
}
8260
- for inp in tx.input.iter() {
8261
- if inp.previous_output == funding_txo.into_bitcoin_outpoint() {
8262
- 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());
8263
- return Err(ClosureReason::CommitmentTxConfirmed);
8424
+
8425
+ if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8426
+ log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8427
+
8428
+ pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8429
+ if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8430
+ promote_splice_funding!(self, funding);
8431
+
8432
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8433
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs));
8264
8434
}
8435
+
8436
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), None));
8265
8437
}
8266
8438
}
8439
+
8440
+ self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8441
+
8267
8442
}
8268
- Ok(msgs)
8443
+
8444
+ Ok((None, None))
8269
8445
}
8270
8446
8271
8447
/// When a new block is connected, we check the height of the block against outbound holding
@@ -8358,6 +8534,49 @@ impl<SP: Deref> FundedChannel<SP> where
8358
8534
return Err(ClosureReason::FundingTimedOut);
8359
8535
}
8360
8536
8537
+ #[cfg(splicing)]
8538
+ let mut confirmed_funding = None;
8539
+ #[cfg(splicing)]
8540
+ for funding in self.pending_funding.iter_mut() {
8541
+ if confirmed_funding.is_some() {
8542
+ let err_reason = "splice tx of another pending funding already confirmed";
8543
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8544
+ }
8545
+
8546
+ confirmed_funding = Some(funding);
8547
+ }
8548
+
8549
+ #[cfg(splicing)]
8550
+ if let Some(funding) = confirmed_funding {
8551
+ let pending_splice = match self.pending_splice.as_mut() {
8552
+ Some(pending_splice) => pending_splice,
8553
+ None => {
8554
+ // TODO: Move pending_funding into pending_splice?
8555
+ debug_assert!(false);
8556
+ // TODO: Error instead?
8557
+ return Ok((None, timed_out_htlcs, None));
8558
+ },
8559
+ };
8560
+
8561
+ if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8562
+ log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8563
+
8564
+ pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8565
+ if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8566
+ promote_splice_funding!(self, funding);
8567
+
8568
+ let mut announcement_sigs = None;
8569
+ if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8570
+ announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8571
+ }
8572
+
8573
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, announcement_sigs));
8574
+ }
8575
+
8576
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, None));
8577
+ }
8578
+ }
8579
+
8361
8580
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8362
8581
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
8363
8582
} else { None };
@@ -8689,6 +8908,8 @@ impl<SP: Deref> FundedChannel<SP> where
8689
8908
8690
8909
self.pending_splice = Some(PendingSplice {
8691
8910
our_funding_contribution: our_funding_contribution_satoshis,
8911
+ sent_funding_txid: None,
8912
+ received_funding_txid: None,
8692
8913
});
8693
8914
8694
8915
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);
0 commit comments