@@ -1853,6 +1853,32 @@ impl FundingScope {
1853
1853
#[cfg(splicing)]
1854
1854
struct PendingSplice {
1855
1855
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
+ }
1856
1882
}
1857
1883
1858
1884
/// Contains everything about the channel including state, and various flags.
@@ -4855,6 +4881,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4855
4881
self.get_initial_counterparty_commitment_signature(funding, logger)
4856
4882
}
4857
4883
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
+
4858
4918
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
4859
4919
let is_coinbase = funding
4860
4920
.funding_transaction
@@ -4888,6 +4948,107 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4888
4948
4889
4949
return true;
4890
4950
}
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
+ }
4891
5052
}
4892
5053
4893
5054
// Internal utility functions for channels
@@ -5068,6 +5229,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
5068
5229
pending_splice: Option<PendingSplice>,
5069
5230
}
5070
5231
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
+
5071
5242
#[cfg(any(test, fuzzing))]
5072
5243
struct CommitmentTxInfoCached {
5073
5244
fee: u64,
@@ -8198,75 +8369,80 @@ impl<SP: Deref> FundedChannel<SP> where
8198
8369
NS::Target: NodeSigner,
8199
8370
L::Target: Logger
8200
8371
{
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
+ )?;
8236
8381
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
+ }
8243
8386
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() });
8259
8404
}
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)?;
8260
8424
}
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));
8265
8435
}
8436
+
8437
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), None));
8266
8438
}
8267
8439
}
8440
+
8441
+ self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8442
+
8268
8443
}
8269
- Ok(msgs)
8444
+
8445
+ Ok((None, None))
8270
8446
}
8271
8447
8272
8448
/// 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
8359
8535
return Err(ClosureReason::FundingTimedOut);
8360
8536
}
8361
8537
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
+
8362
8581
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8363
8582
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
8364
8583
} else { None };
@@ -8690,6 +8909,8 @@ impl<SP: Deref> FundedChannel<SP> where
8690
8909
8691
8910
self.pending_splice = Some(PendingSplice {
8692
8911
our_funding_contribution: our_funding_contribution_satoshis,
8912
+ sent_funding_txid: None,
8913
+ received_funding_txid: None,
8693
8914
});
8694
8915
8695
8916
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);
0 commit comments