Skip to content

Commit a94ac4c

Browse files
committed
Introduce RenegotiatedFunding monitor update variant
This is a new `ChannelMonitorUpdateStep` variant intended to be used whenever a new funding transaction for the channel has been negotiated via the `InteractiveTxConstructor`. This commit primarily focuses on its use for splices, but future work will expand where needed to support RBFs (both for the initial dual funding transaction, and splice transactions). To draw a parallel to channel open, we generally want to have the commitment transactions negotiated for the funding transaction and committed to the respective `ChannelMonitor` before attempting to sign the funding transaction itself. This monitor update fulfills this need for a newly negotiated splice; it includes both the new holder and counterparty commitment transactions, and the new set of applicable `ChannelTransactionParameters`. Once the monitor update has been applied to the monitor and persisted, we allow the release of our `tx_signatures` for the splice transaction to wait for its confirmation.
1 parent db165ad commit a94ac4c

File tree

3 files changed

+373
-6
lines changed

3 files changed

+373
-6
lines changed

lightning/src/chain/chainmonitor.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,24 @@ where
11801180
panic!("{}", err_str);
11811181
},
11821182
}
1183+
1184+
// We may need to start monitoring for any alternative funding transactions.
1185+
if let Some(ref chain_source) = self.chain_source {
1186+
for (funding_outpoint, funding_script) in update.renegotiated_funding_data() {
1187+
log_trace!(
1188+
logger,
1189+
"Registering renegotiated funding outpoint {} with the filter to monitor confirmations and spends",
1190+
funding_outpoint
1191+
);
1192+
chain_source.register_tx(&funding_outpoint.txid, &funding_script);
1193+
chain_source.register_output(WatchedOutput {
1194+
block_hash: None,
1195+
outpoint: funding_outpoint,
1196+
script_pubkey: funding_script,
1197+
});
1198+
}
1199+
}
1200+
11831201
if update_res.is_err() {
11841202
ChannelMonitorUpdateStatus::InProgress
11851203
} else {

lightning/src/chain/channelmonitor.rs

Lines changed: 251 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,37 @@ pub struct ChannelMonitorUpdate {
113113
pub channel_id: Option<ChannelId>,
114114
}
115115

116+
impl ChannelMonitorUpdate {
117+
fn internal_renegotiated_funding_data(
118+
&self,
119+
) -> impl Iterator<Item = (OutPoint, ScriptBuf)> + '_ {
120+
self.updates.iter().filter_map(|update| match update {
121+
ChannelMonitorUpdateStep::RenegotiatedFunding { channel_parameters, .. } => {
122+
let funding_outpoint = channel_parameters
123+
.funding_outpoint
124+
.expect("Renegotiated funding must always have known outpoint");
125+
let funding_script = channel_parameters.make_funding_redeemscript().to_p2wsh();
126+
Some((funding_outpoint, funding_script))
127+
},
128+
_ => None,
129+
})
130+
}
131+
132+
/// Returns an iterator of new (funding outpoint, funding script) to monitor the chain for as
133+
/// a result of a renegotiated funding transaction.
134+
#[cfg(c_bindings)]
135+
pub fn renegotiated_funding_data(&self) -> Vec<(OutPoint, ScriptBuf)> {
136+
self.internal_renegotiated_funding_data().collect()
137+
}
138+
139+
/// Returns an iterator of new (funding outpoint, funding script) to monitor the chain for as
140+
/// a result of a renegotiated funding transaction.
141+
#[cfg(not(c_bindings))]
142+
pub fn renegotiated_funding_data(&self) -> impl Iterator<Item = (OutPoint, ScriptBuf)> + '_ {
143+
self.internal_renegotiated_funding_data()
144+
}
145+
}
146+
116147
/// LDK prior to 0.1 used this constant as the [`ChannelMonitorUpdate::update_id`] for any
117148
/// [`ChannelMonitorUpdate`]s which were generated after the channel was closed.
118149
const LEGACY_CLOSED_CHANNEL_UPDATE_ID: u64 = u64::MAX;
@@ -640,6 +671,11 @@ pub(crate) enum ChannelMonitorUpdateStep {
640671
ShutdownScript {
641672
scriptpubkey: ScriptBuf,
642673
},
674+
RenegotiatedFunding {
675+
channel_parameters: ChannelTransactionParameters,
676+
holder_commitment_tx: HolderCommitmentTransaction,
677+
counterparty_commitment_tx: CommitmentTransaction,
678+
},
643679
}
644680

645681
impl ChannelMonitorUpdateStep {
@@ -653,6 +689,7 @@ impl ChannelMonitorUpdateStep {
653689
ChannelMonitorUpdateStep::CommitmentSecret { .. } => "CommitmentSecret",
654690
ChannelMonitorUpdateStep::ChannelForceClosed { .. } => "ChannelForceClosed",
655691
ChannelMonitorUpdateStep::ShutdownScript { .. } => "ShutdownScript",
692+
ChannelMonitorUpdateStep::RenegotiatedFunding { .. } => "RenegotiatedFunding",
656693
}
657694
}
658695
}
@@ -691,6 +728,11 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
691728
(0, htlc_outputs, required_vec),
692729
(2, commitment_tx, required),
693730
},
731+
(10, RenegotiatedFunding) => {
732+
(1, channel_parameters, (required: ReadableArgs, None)),
733+
(3, holder_commitment_tx, required),
734+
(5, counterparty_commitment_tx, required),
735+
},
694736
);
695737

696738
/// Indicates whether the balance is derived from a cooperative close, a force-close
@@ -1024,9 +1066,69 @@ struct FundingScope {
10241066
prev_holder_commitment_tx: Option<HolderCommitmentTransaction>,
10251067
}
10261068

1069+
impl FundingScope {
1070+
fn funding_outpoint(&self) -> OutPoint {
1071+
let funding_outpoint = self.channel_parameters.funding_outpoint.as_ref();
1072+
*funding_outpoint.expect("Funding outpoint must be set for active monitor")
1073+
}
1074+
1075+
fn funding_txid(&self) -> Txid {
1076+
self.funding_outpoint().txid
1077+
}
1078+
}
1079+
1080+
impl Writeable for FundingScope {
1081+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
1082+
write_tlv_fields!(w, {
1083+
(1, self.channel_parameters, (required: ReadableArgs, None)),
1084+
(3, self.current_counterparty_commitment_txid, required),
1085+
(5, self.prev_counterparty_commitment_txid, option),
1086+
(7, self.current_holder_commitment_tx, required),
1087+
(9, self.prev_holder_commitment_tx, option),
1088+
(11, self.counterparty_claimable_outpoints, required),
1089+
});
1090+
Ok(())
1091+
}
1092+
}
1093+
1094+
impl Readable for FundingScope {
1095+
fn read<R: io::Read>(r: &mut R) -> Result<Self, DecodeError> {
1096+
let mut channel_parameters = RequiredWrapper(None);
1097+
let mut current_counterparty_commitment_txid = RequiredWrapper(None);
1098+
let mut prev_counterparty_commitment_txid = None;
1099+
let mut current_holder_commitment_tx = RequiredWrapper(None);
1100+
let mut prev_holder_commitment_tx = None;
1101+
let mut counterparty_claimable_outpoints = RequiredWrapper(None);
1102+
1103+
read_tlv_fields!(r, {
1104+
(1, channel_parameters, (required: ReadableArgs, None)),
1105+
(3, current_counterparty_commitment_txid, required),
1106+
(5, prev_counterparty_commitment_txid, option),
1107+
(7, current_holder_commitment_tx, required),
1108+
(9, prev_holder_commitment_tx, option),
1109+
(11, counterparty_claimable_outpoints, required),
1110+
});
1111+
1112+
let channel_parameters: ChannelTransactionParameters = channel_parameters.0.unwrap();
1113+
let redeem_script = channel_parameters.make_funding_redeemscript();
1114+
1115+
Ok(Self {
1116+
script_pubkey: redeem_script.to_p2wsh(),
1117+
redeem_script,
1118+
channel_parameters,
1119+
current_counterparty_commitment_txid: current_counterparty_commitment_txid.0.unwrap(),
1120+
prev_counterparty_commitment_txid,
1121+
current_holder_commitment_tx: current_holder_commitment_tx.0.unwrap(),
1122+
prev_holder_commitment_tx,
1123+
counterparty_claimable_outpoints: counterparty_claimable_outpoints.0.unwrap(),
1124+
})
1125+
}
1126+
}
1127+
10271128
#[derive(Clone, PartialEq)]
10281129
pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
10291130
funding: FundingScope,
1131+
pending_funding: Vec<FundingScope>,
10301132

10311133
latest_update_id: u64,
10321134
commitment_transaction_number_obscure_factor: u64,
@@ -1467,6 +1569,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
14671569
(27, self.first_confirmed_funding_txo, required),
14681570
(29, self.initial_counterparty_commitment_tx, option),
14691571
(31, self.funding.channel_parameters, required),
1572+
(32, self.pending_funding, optional_vec),
14701573
});
14711574

14721575
Ok(())
@@ -1636,6 +1739,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
16361739
current_holder_commitment_tx: initial_holder_commitment_tx,
16371740
prev_holder_commitment_tx: None,
16381741
},
1742+
pending_funding: vec![],
16391743

16401744
latest_update_id: 0,
16411745
commitment_transaction_number_obscure_factor,
@@ -1862,14 +1966,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
18621966
{
18631967
let lock = self.inner.lock().unwrap();
18641968
let logger = WithChannelMonitor::from_impl(logger, &*lock, None);
1865-
log_trace!(&logger, "Registering funding outpoint {}", &lock.get_funding_txo());
1866-
let funding_outpoint = lock.get_funding_txo();
1867-
filter.register_tx(&funding_outpoint.txid, &lock.funding.script_pubkey);
1969+
for funding in core::iter::once(&lock.funding).chain(&lock.pending_funding) {
1970+
let funding_outpoint = funding.funding_outpoint();
1971+
log_trace!(&logger, "Registering funding outpoint {} with the filter to monitor confirmations", &funding_outpoint);
1972+
filter.register_tx(&funding_outpoint.txid, &funding.script_pubkey);
1973+
}
18681974
for (txid, outputs) in lock.get_outputs_to_watch().iter() {
18691975
for (index, script_pubkey) in outputs.iter() {
18701976
assert!(*index <= u16::MAX as u32);
18711977
let outpoint = OutPoint { txid: *txid, index: *index as u16 };
1872-
log_trace!(logger, "Registering outpoint {} with the filter for monitoring spends", outpoint);
1978+
log_trace!(logger, "Registering outpoint {} with the filter to monitor spend", outpoint);
18731979
filter.register_output(WatchedOutput {
18741980
block_hash: None,
18751981
outpoint,
@@ -3453,6 +3559,128 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34533559
);
34543560
}
34553561

3562+
fn renegotiated_funding<L: Deref>(
3563+
&mut self, logger: &WithChannelMonitor<L>,
3564+
channel_parameters: &ChannelTransactionParameters,
3565+
alternative_holder_commitment_tx: &HolderCommitmentTransaction,
3566+
alternative_counterparty_commitment_tx: &CommitmentTransaction,
3567+
) -> Result<(), ()>
3568+
where
3569+
L::Target: Logger,
3570+
{
3571+
let redeem_script = channel_parameters.make_funding_redeemscript();
3572+
let script_pubkey = redeem_script.to_p2wsh();
3573+
let alternative_counterparty_commitment_txid =
3574+
alternative_counterparty_commitment_tx.trust().txid();
3575+
3576+
// Both the current counterparty commitment and the alternative one share the same set of
3577+
// non-dust and dust HTLCs in the same order, though the index of each non-dust HTLC may be
3578+
// different.
3579+
//
3580+
// We clone all HTLCs and their sources to use in the alternative funding scope, and update
3581+
// each non-dust HTLC with their corresponding index in the alternative counterparty
3582+
// commitment.
3583+
let current_counterparty_commitment_htlcs =
3584+
if let Some(txid) = &self.funding.current_counterparty_commitment_txid {
3585+
self.funding.counterparty_claimable_outpoints.get(txid).unwrap()
3586+
} else {
3587+
debug_assert!(false);
3588+
log_error!(
3589+
logger,
3590+
"Received funding renegotiation while initial funding negotiation is still pending"
3591+
);
3592+
return Err(());
3593+
};
3594+
let mut htlcs_with_sources = current_counterparty_commitment_htlcs.clone();
3595+
let alternative_htlcs = alternative_counterparty_commitment_tx.nondust_htlcs();
3596+
3597+
let expected_non_dust_htlc_count = htlcs_with_sources
3598+
.iter()
3599+
// Non-dust HTLCs always come first, so the position of the first dust HTLC is equal to
3600+
// our non-dust HTLC count.
3601+
.position(|(htlc, _)| htlc.transaction_output_index.is_none())
3602+
.unwrap_or(htlcs_with_sources.len());
3603+
if alternative_htlcs.len() != expected_non_dust_htlc_count {
3604+
log_error!(
3605+
logger,
3606+
"Received alternative counterparty commitment with HTLC count mismatch"
3607+
);
3608+
return Err(());
3609+
}
3610+
3611+
for (alternative_htlc, (htlc, _)) in
3612+
alternative_htlcs.iter().zip(htlcs_with_sources.iter_mut())
3613+
{
3614+
debug_assert!(htlc.transaction_output_index.is_some());
3615+
debug_assert!(alternative_htlc.transaction_output_index.is_some());
3616+
if !alternative_htlc.is_data_equal(htlc) {
3617+
log_error!(
3618+
logger,
3619+
"Received alternative counterparty commitment with non-dust HTLC mismatch"
3620+
);
3621+
return Err(());
3622+
}
3623+
htlc.transaction_output_index = alternative_htlc.transaction_output_index;
3624+
}
3625+
3626+
let mut counterparty_claimable_outpoints = new_hash_map();
3627+
counterparty_claimable_outpoints
3628+
.insert(alternative_counterparty_commitment_txid, htlcs_with_sources);
3629+
3630+
// TODO(splicing): Enforce any necessary RBF validity checks.
3631+
let alternative_funding = FundingScope {
3632+
script_pubkey: script_pubkey.clone(),
3633+
redeem_script,
3634+
channel_parameters: channel_parameters.clone(),
3635+
current_counterparty_commitment_txid: Some(alternative_counterparty_commitment_txid),
3636+
prev_counterparty_commitment_txid: None,
3637+
counterparty_claimable_outpoints,
3638+
current_holder_commitment_tx: alternative_holder_commitment_tx.clone(),
3639+
prev_holder_commitment_tx: None,
3640+
};
3641+
let alternative_funding_outpoint = alternative_funding.funding_outpoint();
3642+
3643+
if self
3644+
.pending_funding
3645+
.iter()
3646+
.any(|funding| funding.funding_txid() == alternative_funding_outpoint.txid)
3647+
{
3648+
log_error!(
3649+
logger,
3650+
"Renegotiated funding transaction with a duplicate funding txid {}",
3651+
alternative_funding_outpoint.txid
3652+
);
3653+
return Err(());
3654+
}
3655+
3656+
if let Some(parent_funding_txid) = channel_parameters.splice_parent_funding_txid.as_ref() {
3657+
// Only one splice can be negotiated at a time after we've exchanged `channel_ready`
3658+
// (implying our funding is confirmed) that spends our currently locked funding.
3659+
if !self.pending_funding.is_empty() {
3660+
log_error!(
3661+
logger,
3662+
"Negotiated splice while channel is pending channel_ready/splice_locked"
3663+
);
3664+
return Err(());
3665+
}
3666+
if *parent_funding_txid != self.funding.funding_txid() {
3667+
log_error!(
3668+
logger,
3669+
"Negotiated splice that does not spend currently locked funding transaction"
3670+
);
3671+
return Err(());
3672+
}
3673+
}
3674+
3675+
self.outputs_to_watch.insert(
3676+
alternative_funding_outpoint.txid,
3677+
vec![(alternative_funding_outpoint.index as u32, script_pubkey)],
3678+
);
3679+
self.pending_funding.push(alternative_funding);
3680+
3681+
Ok(())
3682+
}
3683+
34563684
#[rustfmt::skip]
34573685
fn update_monitor<B: Deref, F: Deref, L: Deref>(
34583686
&mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: &F, logger: &WithChannelMonitor<L>
@@ -3532,6 +3760,17 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35323760
ret = Err(());
35333761
}
35343762
},
3763+
ChannelMonitorUpdateStep::RenegotiatedFunding {
3764+
channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
3765+
} => {
3766+
log_trace!(logger, "Updating ChannelMonitor with alternative holder and counterparty commitment transactions for funding txid {}",
3767+
channel_parameters.funding_outpoint.unwrap().txid);
3768+
if let Err(_) = self.renegotiated_funding(
3769+
logger, channel_parameters, holder_commitment_tx, counterparty_commitment_tx,
3770+
) {
3771+
ret = Err(());
3772+
}
3773+
},
35353774
ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => {
35363775
log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast);
35373776
self.lockdown_from_offchain = true;
@@ -3583,7 +3822,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35833822
|ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTXInfo { .. }
35843823
|ChannelMonitorUpdateStep::LatestCounterpartyCommitmentTX { .. }
35853824
|ChannelMonitorUpdateStep::ShutdownScript { .. }
3586-
|ChannelMonitorUpdateStep::CommitmentSecret { .. } =>
3825+
|ChannelMonitorUpdateStep::CommitmentSecret { .. }
3826+
|ChannelMonitorUpdateStep::RenegotiatedFunding { .. } =>
35873827
is_pre_close_update = true,
35883828
// After a channel is closed, we don't communicate with our peer about it, so the
35893829
// only things we will update is getting a new preimage (from a different channel)
@@ -3765,6 +4005,9 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37654005
} => {
37664006
Some(commitment_tx.clone())
37674007
},
4008+
&ChannelMonitorUpdateStep::RenegotiatedFunding { ref counterparty_commitment_tx, .. } => {
4009+
Some(counterparty_commitment_tx.clone())
4010+
},
37684011
_ => None,
37694012
}
37704013
}).collect()
@@ -5438,6 +5681,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
54385681
let mut payment_preimages_with_info: Option<HashMap<_, _>> = None;
54395682
let mut first_confirmed_funding_txo = RequiredWrapper(None);
54405683
let mut channel_parameters = None;
5684+
let mut pending_funding = None;
54415685
read_tlv_fields!(reader, {
54425686
(1, funding_spend_confirmed, option),
54435687
(3, htlcs_resolved_on_chain, optional_vec),
@@ -5455,6 +5699,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
54555699
(27, first_confirmed_funding_txo, (default_value, outpoint)),
54565700
(29, initial_counterparty_commitment_tx, option),
54575701
(31, channel_parameters, (option: ReadableArgs, None)),
5702+
(32, pending_funding, optional_vec),
54585703
});
54595704
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
54605705
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -5570,6 +5815,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
55705815
current_holder_commitment_tx,
55715816
prev_holder_commitment_tx,
55725817
},
5818+
pending_funding: pending_funding.unwrap_or(vec![]),
55735819

55745820
latest_update_id,
55755821
commitment_transaction_number_obscure_factor,

0 commit comments

Comments
 (0)