@@ -1857,6 +1857,32 @@ impl FundingScope {
1857
1857
#[cfg(splicing)]
1858
1858
struct PendingSplice {
1859
1859
pub our_funding_contribution: i64,
1860
+ sent_funding_txid: Option<Txid>,
1861
+ received_funding_txid: Option<Txid>,
1862
+ }
1863
+
1864
+ /// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
1865
+ struct ConfirmedTransaction<'a> {
1866
+ tx: &'a Transaction,
1867
+ txid: Option<Txid>,
1868
+ }
1869
+
1870
+ impl<'a> ConfirmedTransaction<'a> {
1871
+ /// Returns the underlying [`Transaction`].
1872
+ pub fn tx(&self) -> &'a Transaction {
1873
+ self.tx
1874
+ }
1875
+
1876
+ /// Returns the [`Txid`], computing and caching it if necessary.
1877
+ pub fn txid(&mut self) -> Txid {
1878
+ *self.txid.get_or_insert_with(|| self.tx.compute_txid())
1879
+ }
1880
+ }
1881
+
1882
+ impl<'a> From<&'a Transaction> for ConfirmedTransaction<'a> {
1883
+ fn from(tx: &'a Transaction) -> Self {
1884
+ ConfirmedTransaction { tx, txid: None }
1885
+ }
1860
1886
}
1861
1887
1862
1888
/// Contains everything about the channel including state, and various flags.
@@ -4859,6 +4885,40 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4859
4885
self.get_initial_counterparty_commitment_signature(funding, logger)
4860
4886
}
4861
4887
4888
+ #[cfg(splicing)]
4889
+ fn check_get_splice_locked<L: Deref>(
4890
+ &mut self, pending_splice: &PendingSplice, funding: &mut FundingScope, height: u32,
4891
+ logger: &L,
4892
+ ) -> Option<msgs::SpliceLocked>
4893
+ where
4894
+ L::Target: Logger,
4895
+ {
4896
+ if !self.check_funding_confirmations(funding, height) {
4897
+ return None;
4898
+ }
4899
+
4900
+ let confirmed_funding_txid = match funding.get_funding_txid() {
4901
+ Some(funding_txid) => funding_txid,
4902
+ None => {
4903
+ debug_assert!(false);
4904
+ return None;
4905
+ },
4906
+ };
4907
+
4908
+ match pending_splice.sent_funding_txid {
4909
+ Some(sent_funding_txid) if confirmed_funding_txid == sent_funding_txid => {
4910
+ debug_assert!(false);
4911
+ None
4912
+ },
4913
+ _ => {
4914
+ Some(msgs::SpliceLocked {
4915
+ channel_id: self.channel_id(),
4916
+ splice_txid: confirmed_funding_txid,
4917
+ })
4918
+ },
4919
+ }
4920
+ }
4921
+
4862
4922
fn check_funding_confirmations(&self, funding: &mut FundingScope, height: u32) -> bool {
4863
4923
let is_coinbase = funding
4864
4924
.funding_transaction
@@ -4892,6 +4952,107 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
4892
4952
4893
4953
return true;
4894
4954
}
4955
+
4956
+ fn check_for_funding_tx_confirmed<L: Deref>(
4957
+ &mut self, funding: &mut FundingScope, block_hash: &BlockHash, height: u32,
4958
+ index_in_block: usize, tx: &mut ConfirmedTransaction, logger: &L,
4959
+ ) -> Result<bool, ClosureReason>
4960
+ where
4961
+ L::Target: Logger
4962
+ {
4963
+ let funding_txo = match funding.get_funding_txo() {
4964
+ Some(funding_txo) => funding_txo,
4965
+ None => {
4966
+ debug_assert!(false);
4967
+ return Ok(false);
4968
+ },
4969
+ };
4970
+
4971
+ let mut is_funding_tx_confirmed = false;
4972
+
4973
+ // Check if the transaction is the expected funding transaction, and if it is,
4974
+ // check that it pays the right amount to the right script.
4975
+ if funding.funding_tx_confirmation_height == 0 {
4976
+ if tx.txid() == funding_txo.txid {
4977
+ let tx = tx.tx();
4978
+ let txo_idx = funding_txo.index as usize;
4979
+ if txo_idx >= tx.output.len() || tx.output[txo_idx].script_pubkey != funding.get_funding_redeemscript().to_p2wsh() ||
4980
+ tx.output[txo_idx].value.to_sat() != funding.get_value_satoshis() {
4981
+ if funding.is_outbound() {
4982
+ // If we generated the funding transaction and it doesn't match what it
4983
+ // should, the client is really broken and we should just panic and
4984
+ // tell them off. That said, because hash collisions happen with high
4985
+ // probability in fuzzing mode, if we're fuzzing we just close the
4986
+ // channel and move on.
4987
+ #[cfg(not(fuzzing))]
4988
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4989
+ }
4990
+ self.update_time_counter += 1;
4991
+ let err_reason = "funding tx had wrong script/value or output index";
4992
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
4993
+ } else {
4994
+ if funding.is_outbound() {
4995
+ if !tx.is_coinbase() {
4996
+ for input in tx.input.iter() {
4997
+ if input.witness.is_empty() {
4998
+ // We generated a malleable funding transaction, implying we've
4999
+ // just exposed ourselves to funds loss to our counterparty.
5000
+ #[cfg(not(fuzzing))]
5001
+ panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
5002
+ }
5003
+ }
5004
+ }
5005
+ }
5006
+
5007
+ // The acceptor of v1-established channels doesn't have the funding
5008
+ // transaction until it is seen on chain. Set it so that minimum_depth
5009
+ // checks can tell if the coinbase transaction was used.
5010
+ if funding.funding_transaction.is_none() {
5011
+ funding.funding_transaction = Some(tx.clone());
5012
+ }
5013
+
5014
+ funding.funding_tx_confirmation_height = height;
5015
+ funding.funding_tx_confirmed_in = Some(*block_hash);
5016
+ funding.short_channel_id = match scid_from_parts(height as u64, index_in_block as u64, txo_idx as u64) {
5017
+ Ok(scid) => Some(scid),
5018
+ Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
5019
+ };
5020
+ }
5021
+
5022
+ is_funding_tx_confirmed = true;
5023
+ }
5024
+ }
5025
+
5026
+ Ok(is_funding_tx_confirmed)
5027
+ }
5028
+
5029
+ fn check_for_funding_tx_spent<L: Deref>(
5030
+ &mut self, funding: &FundingScope, tx: &Transaction, logger: &L,
5031
+ ) -> Result<(), ClosureReason>
5032
+ where
5033
+ L::Target: Logger
5034
+ {
5035
+ let funding_txo = match funding.get_funding_txo() {
5036
+ Some(funding_txo) => funding_txo,
5037
+ None => {
5038
+ debug_assert!(false);
5039
+ return Ok(());
5040
+ },
5041
+ };
5042
+
5043
+ for input in tx.input.iter() {
5044
+ if input.previous_output == funding_txo.into_bitcoin_outpoint() {
5045
+ log_info!(
5046
+ logger, "Detected channel-closing tx {} spending {}:{}, closing channel {}",
5047
+ tx.compute_txid(), input.previous_output.txid, input.previous_output.vout,
5048
+ &self.channel_id(),
5049
+ );
5050
+ return Err(ClosureReason::CommitmentTxConfirmed);
5051
+ }
5052
+ }
5053
+
5054
+ Ok(())
5055
+ }
4895
5056
}
4896
5057
4897
5058
// Internal utility functions for channels
@@ -5072,6 +5233,16 @@ pub(super) struct FundedChannel<SP: Deref> where SP::Target: SignerProvider {
5072
5233
pending_splice: Option<PendingSplice>,
5073
5234
}
5074
5235
5236
+ #[cfg(splicing)]
5237
+ macro_rules! promote_splice_funding {
5238
+ ($self: expr, $funding: expr) => {
5239
+ core::mem::swap(&mut $self.funding, $funding);
5240
+ $self.pending_splice = None;
5241
+ $self.pending_funding.clear();
5242
+ $self.context.announcement_sigs_state = AnnouncementSigsState::NotSent;
5243
+ }
5244
+ }
5245
+
5075
5246
#[cfg(any(test, fuzzing))]
5076
5247
struct CommitmentTxInfoCached {
5077
5248
fee: u64,
@@ -8202,75 +8373,80 @@ impl<SP: Deref> FundedChannel<SP> where
8202
8373
NS::Target: NodeSigner,
8203
8374
L::Target: Logger
8204
8375
{
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
- }
8376
+ for &(index_in_block, tx) in txdata.iter() {
8377
+ let mut confirmed_tx = ConfirmedTransaction::from(tx);
8378
+
8379
+ // If we allow 1-conf funding, we may need to check for channel_ready or splice_locked here
8380
+ // and send it immediately instead of waiting for a best_block_updated call (which may have
8381
+ // already happened for this block).
8382
+ let is_funding_tx_confirmed = self.context.check_for_funding_tx_confirmed(
8383
+ &mut self.funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8384
+ )?;
8240
8385
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
- }
8386
+ if is_funding_tx_confirmed {
8387
+ for &(_, tx) in txdata.iter() {
8388
+ self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8389
+ }
8247
8390
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);
8391
+ if let Some(channel_ready) = self.check_get_channel_ready(height, logger) {
8392
+ log_info!(logger, "Sending a channel_ready to our peer for channel {}", &self.context.channel_id);
8393
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8394
+ return Ok((Some(FundingConfirmedMessage::Establishment(channel_ready)), announcement_sigs));
8395
+ }
8396
+ }
8397
+
8398
+ #[cfg(splicing)]
8399
+ let mut confirmed_funding = None;
8400
+ #[cfg(splicing)]
8401
+ for funding in self.pending_funding.iter_mut() {
8402
+ if self.context.check_for_funding_tx_confirmed(
8403
+ funding, block_hash, height, index_in_block, &mut confirmed_tx, logger,
8404
+ )? {
8405
+ if confirmed_funding.is_some() {
8406
+ let err_reason = "splice tx of another pending funding already confirmed";
8407
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8263
8408
}
8409
+
8410
+ confirmed_funding = Some(funding);
8411
+ }
8412
+ }
8413
+
8414
+ #[cfg(splicing)]
8415
+ if let Some(funding) = confirmed_funding {
8416
+ let pending_splice = match self.pending_splice.as_mut() {
8417
+ Some(pending_splice) => pending_splice,
8418
+ None => {
8419
+ // TODO: Move pending_funding into pending_splice?
8420
+ debug_assert!(false);
8421
+ // TODO: Error instead?
8422
+ return Ok((None, None));
8423
+ },
8424
+ };
8425
+
8426
+ for &(_, tx) in txdata.iter() {
8427
+ self.context.check_for_funding_tx_spent(funding, tx, logger)?;
8264
8428
}
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);
8429
+
8430
+ if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8431
+ log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8432
+
8433
+ pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8434
+ if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8435
+ promote_splice_funding!(self, funding);
8436
+
8437
+ let announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8438
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), announcement_sigs));
8269
8439
}
8440
+
8441
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), None));
8270
8442
}
8271
8443
}
8444
+
8445
+ self.context.check_for_funding_tx_spent(&self.funding, tx, logger)?;
8446
+
8272
8447
}
8273
- Ok(msgs)
8448
+
8449
+ Ok((None, None))
8274
8450
}
8275
8451
8276
8452
/// When a new block is connected, we check the height of the block against outbound holding
@@ -8363,6 +8539,49 @@ impl<SP: Deref> FundedChannel<SP> where
8363
8539
return Err(ClosureReason::FundingTimedOut);
8364
8540
}
8365
8541
8542
+ #[cfg(splicing)]
8543
+ let mut confirmed_funding = None;
8544
+ #[cfg(splicing)]
8545
+ for funding in self.pending_funding.iter_mut() {
8546
+ if confirmed_funding.is_some() {
8547
+ let err_reason = "splice tx of another pending funding already confirmed";
8548
+ return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
8549
+ }
8550
+
8551
+ confirmed_funding = Some(funding);
8552
+ }
8553
+
8554
+ #[cfg(splicing)]
8555
+ if let Some(funding) = confirmed_funding {
8556
+ let pending_splice = match self.pending_splice.as_mut() {
8557
+ Some(pending_splice) => pending_splice,
8558
+ None => {
8559
+ // TODO: Move pending_funding into pending_splice?
8560
+ debug_assert!(false);
8561
+ // TODO: Error instead?
8562
+ return Ok((None, timed_out_htlcs, None));
8563
+ },
8564
+ };
8565
+
8566
+ if let Some(splice_locked) = self.context.check_get_splice_locked(pending_splice, funding, height, logger) {
8567
+ log_info!(logger, "Sending a splice_locked to our peer for channel {}", &self.context.channel_id);
8568
+
8569
+ pending_splice.sent_funding_txid = Some(splice_locked.splice_txid);
8570
+ if pending_splice.sent_funding_txid == pending_splice.received_funding_txid {
8571
+ promote_splice_funding!(self, funding);
8572
+
8573
+ let mut announcement_sigs = None;
8574
+ if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8575
+ announcement_sigs = self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger);
8576
+ }
8577
+
8578
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, announcement_sigs));
8579
+ }
8580
+
8581
+ return Ok((Some(FundingConfirmedMessage::Splice(splice_locked)), timed_out_htlcs, None));
8582
+ }
8583
+ }
8584
+
8366
8585
let announcement_sigs = if let Some((chain_hash, node_signer, user_config)) = chain_node_signer {
8367
8586
self.get_announcement_sigs(node_signer, chain_hash, user_config, height, logger)
8368
8587
} else { None };
@@ -8694,6 +8913,8 @@ impl<SP: Deref> FundedChannel<SP> where
8694
8913
8695
8914
self.pending_splice = Some(PendingSplice {
8696
8915
our_funding_contribution: our_funding_contribution_satoshis,
8916
+ sent_funding_txid: None,
8917
+ received_funding_txid: None,
8697
8918
});
8698
8919
8699
8920
let msg = self.get_splice_init(our_funding_contribution_satoshis, funding_feerate_per_kw, locktime);
0 commit comments