Skip to content

Commit b72f6b1

Browse files
committed
Support future removal of redundant per-HTLC data in ChanMonUpds
`ChannelMonitorUpdate`s are our most size-sensitive objects - they are the minimal objects which need to be written to disk on each commitment update. Thus, we should be careful to ensure we don't pack too much extraneous information into each one. Here we add future support for removing the per-HTLC explicit `Option<Signature>` and `HTLCInCommitmentUpdate` for non-dust HTLCs in holder commitment tx updates, which are redundant with the `HolderCommitmentTransaction`. While we cannot remove them entirely as previous versions rely on them, adding support for filling in the in-memory structures from the redundant fields will let us remove them in a future version. We also add test-only generation logic to test the new derivation.
1 parent 85b573d commit b72f6b1

File tree

4 files changed

+129
-31
lines changed

4 files changed

+129
-31
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,12 @@ impl_writeable_tlv_based_enum_upgradable!(OnchainEvent,
493493
pub(crate) enum ChannelMonitorUpdateStep {
494494
LatestHolderCommitmentTXInfo {
495495
commitment_tx: HolderCommitmentTransaction,
496+
/// Note that LDK after 0.0.115 supports this only containing dust HTLCs (implying the
497+
/// `Signature` field is never filled in). At that point, non-dust HTLCs are implied by the
498+
/// HTLC fields in `commitment_tx` and the sources passed via `nondust_htlc_sources`.
496499
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
497500
claimed_htlcs: Vec<(SentHTLCId, PaymentPreimage)>,
501+
nondust_htlc_sources: Vec<HTLCSource>,
498502
},
499503
LatestCounterpartyCommitmentTXInfo {
500504
commitment_txid: Txid,
@@ -539,6 +543,7 @@ impl_writeable_tlv_based_enum_upgradable!(ChannelMonitorUpdateStep,
539543
(0, commitment_tx, required),
540544
(1, claimed_htlcs, vec_type),
541545
(2, htlc_outputs, vec_type),
546+
(4, nondust_htlc_sources, optional_vec),
542547
},
543548
(1, LatestCounterpartyCommitmentTXInfo) => {
544549
(0, commitment_txid, required),
@@ -1180,7 +1185,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
11801185
&self, holder_commitment_tx: HolderCommitmentTransaction,
11811186
htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>,
11821187
) -> Result<(), ()> {
1183-
self.inner.lock().unwrap().provide_latest_holder_commitment_tx(holder_commitment_tx, htlc_outputs, &Vec::new()).map_err(|_| ())
1188+
self.inner.lock().unwrap().provide_latest_holder_commitment_tx(holder_commitment_tx, htlc_outputs, &Vec::new(), Vec::new()).map_err(|_| ())
11841189
}
11851190

11861191
/// This is used to provide payment preimage(s) out-of-band during startup without updating the
@@ -2160,7 +2165,53 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
21602165
/// is important that any clones of this channel monitor (including remote clones) by kept
21612166
/// up-to-date as our holder commitment transaction is updated.
21622167
/// Panics if set_on_holder_tx_csv has never been called.
2163-
fn provide_latest_holder_commitment_tx(&mut self, holder_commitment_tx: HolderCommitmentTransaction, htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>, claimed_htlcs: &[(SentHTLCId, PaymentPreimage)]) -> Result<(), &'static str> {
2168+
fn provide_latest_holder_commitment_tx(&mut self, holder_commitment_tx: HolderCommitmentTransaction, mut htlc_outputs: Vec<(HTLCOutputInCommitment, Option<Signature>, Option<HTLCSource>)>, claimed_htlcs: &[(SentHTLCId, PaymentPreimage)], nondust_htlc_sources: Vec<HTLCSource>) -> Result<(), &'static str> {
2169+
if htlc_outputs.iter().any(|(_, s, _)| s.is_some()) {
2170+
// If we have non-dust HTLCs in htlc_outputs, ensure they match the HTLCs in the
2171+
// `holder_commitment_tx`. In the future, we'll no longer provide the redundant data
2172+
// and just pass in source data via `nondust_htlc_sources`.
2173+
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.trust().htlcs().len());
2174+
for (a, b) in htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).map(|(h, _, _)| h).zip(holder_commitment_tx.trust().htlcs().iter()) {
2175+
debug_assert_eq!(a, b);
2176+
}
2177+
debug_assert_eq!(htlc_outputs.iter().filter(|(_, s, _)| s.is_some()).count(), holder_commitment_tx.counterparty_htlc_sigs.len());
2178+
for (a, b) in htlc_outputs.iter().filter_map(|(_, s, _)| s.as_ref()).zip(holder_commitment_tx.counterparty_htlc_sigs.iter()) {
2179+
debug_assert_eq!(a, b);
2180+
}
2181+
debug_assert!(nondust_htlc_sources.is_empty());
2182+
} else {
2183+
// If we don't have any non-dust HTLCs in htlc_outputs, assume they were all passed via
2184+
// `nondust_htlc_sources`, building up the final htlc_outputs by combining
2185+
// `nondust_htlc_sources` and the `holder_commitment_tx`
2186+
#[cfg(debug_assertions)] {
2187+
let mut prev = -1;
2188+
for htlc in holder_commitment_tx.trust().htlcs().iter() {
2189+
assert!(htlc.transaction_output_index.unwrap() as i32 > prev);
2190+
prev = htlc.transaction_output_index.unwrap() as i32;
2191+
}
2192+
}
2193+
debug_assert!(htlc_outputs.iter().all(|(htlc, _, _)| htlc.transaction_output_index.is_none()));
2194+
debug_assert!(htlc_outputs.iter().all(|(_, sig_opt, _)| sig_opt.is_none()));
2195+
debug_assert_eq!(holder_commitment_tx.trust().htlcs().len(), holder_commitment_tx.counterparty_htlc_sigs.len());
2196+
2197+
let mut sources_iter = nondust_htlc_sources.into_iter();
2198+
2199+
for (htlc, counterparty_sig) in holder_commitment_tx.trust().htlcs().iter()
2200+
.zip(holder_commitment_tx.counterparty_htlc_sigs.iter())
2201+
{
2202+
if htlc.offered {
2203+
let source = sources_iter.next().expect("Non-dust HTLC sources didn't match commitment tx");
2204+
#[cfg(debug_assertions)] {
2205+
assert!(source.possibly_matches_output(htlc));
2206+
}
2207+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), Some(source)));
2208+
} else {
2209+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), None));
2210+
}
2211+
}
2212+
debug_assert!(sources_iter.next().is_none());
2213+
}
2214+
21642215
let trusted_tx = holder_commitment_tx.trust();
21652216
let txid = trusted_tx.txid();
21662217
let tx_keys = trusted_tx.keys();
@@ -2285,10 +2336,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
22852336
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&*fee_estimator);
22862337
for update in updates.updates.iter() {
22872338
match update {
2288-
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs } => {
2339+
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs, nondust_htlc_sources } => {
22892340
log_trace!(logger, "Updating ChannelMonitor with latest holder commitment transaction info");
22902341
if self.lockdown_from_offchain { panic!(); }
2291-
if let Err(e) = self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone(), &claimed_htlcs) {
2342+
if let Err(e) = self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone(), &claimed_htlcs, nondust_htlc_sources.clone()) {
22922343
log_error!(logger, "Providing latest holder commitment transaction failed/was refused:");
22932344
log_error!(logger, " {}", e);
22942345
ret = Err(());
@@ -4132,7 +4183,7 @@ mod tests {
41324183
}
41334184
}
41344185

4135-
macro_rules! preimages_slice_to_htlc_outputs {
4186+
macro_rules! preimages_slice_to_htlcs {
41364187
($preimages_slice: expr) => {
41374188
{
41384189
let mut res = Vec::new();
@@ -4143,21 +4194,20 @@ mod tests {
41434194
cltv_expiry: 0,
41444195
payment_hash: preimage.1.clone(),
41454196
transaction_output_index: Some(idx as u32),
4146-
}, None));
4197+
}, ()));
41474198
}
41484199
res
41494200
}
41504201
}
41514202
}
4152-
macro_rules! preimages_to_holder_htlcs {
4203+
macro_rules! preimages_slice_to_htlc_outputs {
41534204
($preimages_slice: expr) => {
4154-
{
4155-
let mut inp = preimages_slice_to_htlc_outputs!($preimages_slice);
4156-
let res: Vec<_> = inp.drain(..).map(|e| { (e.0, None, e.1) }).collect();
4157-
res
4158-
}
4205+
preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect()
41594206
}
41604207
}
4208+
let dummy_sig = crate::util::crypto::sign(&secp_ctx,
4209+
&bitcoin::secp256k1::Message::from_slice(&[42; 32]).unwrap(),
4210+
&SecretKey::from_slice(&[42; 32]).unwrap());
41614211

41624212
macro_rules! test_preimages_exist {
41634213
($preimages_slice: expr, $monitor: expr) => {
@@ -4204,13 +4254,15 @@ mod tests {
42044254
let shutdown_pubkey = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
42054255
let best_block = BestBlock::from_network(Network::Testnet);
42064256
let monitor = ChannelMonitor::new(Secp256k1::new(), keys,
4207-
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &Script::new(),
4208-
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
4209-
&channel_parameters,
4210-
Script::new(), 46, 0,
4211-
HolderCommitmentTransaction::dummy(), best_block, dummy_key);
4212-
4213-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..10])).unwrap();
4257+
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &Script::new(),
4258+
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
4259+
&channel_parameters, Script::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()),
4260+
best_block, dummy_key);
4261+
4262+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]);
4263+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4264+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
4265+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42144266
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"1").into_inner()),
42154267
preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
42164268
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"2").into_inner()),
@@ -4243,15 +4295,21 @@ mod tests {
42434295

42444296
// Now update holder commitment tx info, pruning only element 18 as we still care about the
42454297
// previous commitment tx's preimages too
4246-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..5])).unwrap();
4298+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..5]);
4299+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4300+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
4301+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42474302
secret[0..32].clone_from_slice(&hex::decode("2273e227a5b7449b6e70f1fb4652864038b1cbf9cd7c043a7d6456b7fc275ad8").unwrap());
42484303
monitor.provide_secret(281474976710653, secret.clone()).unwrap();
42494304
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 12);
42504305
test_preimages_exist!(&preimages[0..10], monitor);
42514306
test_preimages_exist!(&preimages[18..20], monitor);
42524307

42534308
// But if we do it again, we'll prune 5-10
4254-
monitor.provide_latest_holder_commitment_tx(HolderCommitmentTransaction::dummy(), preimages_to_holder_htlcs!(preimages[0..3])).unwrap();
4309+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..3]);
4310+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4311+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
4312+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42554313
secret[0..32].clone_from_slice(&hex::decode("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116").unwrap());
42564314
monitor.provide_secret(281474976710652, secret.clone()).unwrap();
42574315
assert_eq!(monitor.inner.lock().unwrap().payment_preimages.len(), 5);

lightning/src/ln/chan_utils.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ impl_writeable_tlv_based!(HolderCommitmentTransaction, {
984984

985985
impl HolderCommitmentTransaction {
986986
#[cfg(test)]
987-
pub fn dummy() -> Self {
987+
pub fn dummy(htlcs: &mut Vec<(HTLCOutputInCommitment, ())>) -> Self {
988988
let secp_ctx = Secp256k1::new();
989989
let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
990990
let dummy_sig = sign(&secp_ctx, &secp256k1::Message::from_slice(&[42; 32]).unwrap(), &SecretKey::from_slice(&[42; 32]).unwrap());
@@ -1012,12 +1012,16 @@ impl HolderCommitmentTransaction {
10121012
opt_anchors: None,
10131013
opt_non_zero_fee_anchors: None,
10141014
};
1015-
let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
1016-
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable());
1015+
let mut counterparty_htlc_sigs = Vec::new();
1016+
for _ in 0..htlcs.len() {
1017+
counterparty_htlc_sigs.push(dummy_sig);
1018+
}
1019+
let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable());
1020+
htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index);
10171021
HolderCommitmentTransaction {
10181022
inner,
10191023
counterparty_sig: dummy_sig,
1020-
counterparty_htlc_sigs: Vec::new(),
1024+
counterparty_htlc_sigs,
10211025
holder_sig_first: false
10221026
}
10231027
}

lightning/src/ln/channel.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3122,9 +3122,24 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
31223122
return Err(ChannelError::Close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_stats.num_nondust_htlcs)));
31233123
}
31243124

3125-
// TODO: Sadly, we pass HTLCs twice to ChannelMonitor: once via the HolderCommitmentTransaction and once via the update
3125+
// Up to LDK 0.0.115, HTLC information was required to be duplicated in the
3126+
// `htlcs_and_sigs` vec and in the `holder_commitment_tx` itself, both of which were passed
3127+
// in the `ChannelMonitorUpdate`. In 0.0.115, support for having a separate set of
3128+
// outbound-non-dust-HTLCSources in the `ChannelMonitorUpdate` was added, however for
3129+
// backwards compatibility, we never use it in production. To provide test coverage, here,
3130+
// we randomly decide (in test/fuzzing builds) to use the new vec sometimes.
3131+
#[allow(unused_assignments, unused_mut)]
3132+
let mut separate_nondust_htlc_sources = false;
3133+
#[cfg(all(feature = "std", any(test, fuzzing)))] {
3134+
use core::hash::{BuildHasher, Hasher};
3135+
// Get a random value using the only std API to do so - the DefaultHasher
3136+
let rand_val = std::collections::hash_map::RandomState::new().build_hasher().finish();
3137+
separate_nondust_htlc_sources = rand_val % 2 == 0;
3138+
}
3139+
3140+
let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len());
31263141
let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len());
3127-
for (idx, (htlc, source)) in htlcs_cloned.drain(..).enumerate() {
3142+
for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() {
31283143
if let Some(_) = htlc.transaction_output_index {
31293144
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw,
31303145
self.get_counterparty_selected_contest_delay().unwrap(), &htlc, self.opt_anchors(),
@@ -3139,10 +3154,18 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
31393154
if let Err(_) = self.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &keys.countersignatory_htlc_key) {
31403155
return Err(ChannelError::Close("Invalid HTLC tx signature from peer".to_owned()));
31413156
}
3142-
htlcs_and_sigs.push((htlc, Some(msg.htlc_signatures[idx]), source));
3157+
if !separate_nondust_htlc_sources {
3158+
htlcs_and_sigs.push((htlc, Some(msg.htlc_signatures[idx]), source_opt.take()));
3159+
}
31433160
} else {
3144-
htlcs_and_sigs.push((htlc, None, source));
3161+
htlcs_and_sigs.push((htlc, None, source_opt.take()));
3162+
}
3163+
if separate_nondust_htlc_sources {
3164+
if let Some(source) = source_opt.take() {
3165+
nondust_htlc_sources.push(source);
3166+
}
31453167
}
3168+
debug_assert!(source_opt.is_none(), "HTLCSource should have been put somewhere");
31463169
}
31473170

31483171
let holder_commitment_tx = HolderCommitmentTransaction::new(
@@ -3205,6 +3228,7 @@ impl<Signer: WriteableEcdsaChannelSigner> Channel<Signer> {
32053228
commitment_tx: holder_commitment_tx,
32063229
htlc_outputs: htlcs_and_sigs,
32073230
claimed_htlcs,
3231+
nondust_htlc_sources,
32083232
}]
32093233
};
32103234

lightning/src/ln/channelmanager.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,9 @@ impl core::hash::Hash for HTLCSource {
302302
}
303303
}
304304
}
305-
#[cfg(not(feature = "grind_signatures"))]
306-
#[cfg(test)]
307305
impl HTLCSource {
306+
#[cfg(not(feature = "grind_signatures"))]
307+
#[cfg(test)]
308308
pub fn dummy() -> Self {
309309
HTLCSource::OutboundRoute {
310310
path: Vec::new(),
@@ -315,6 +315,18 @@ impl HTLCSource {
315315
payment_params: None,
316316
}
317317
}
318+
319+
#[cfg(debug_assertions)]
320+
/// Checks whether this HTLCSource could possibly match the given HTLC output in a commitment
321+
/// transaction. Useful to ensure different datastructures match up.
322+
pub(crate) fn possibly_matches_output(&self, htlc: &super::chan_utils::HTLCOutputInCommitment) -> bool {
323+
if let HTLCSource::OutboundRoute { first_hop_htlc_msat, .. } = self {
324+
*first_hop_htlc_msat == htlc.amount_msat
325+
} else {
326+
// There's nothing we can check for forwarded HTLCs
327+
true
328+
}
329+
}
318330
}
319331

320332
struct ReceiveError {

0 commit comments

Comments
 (0)