Skip to content

Commit b0367ec

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 23c1b46 commit b0367ec

File tree

4 files changed

+127
-31
lines changed

4 files changed

+127
-31
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 77 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+
(3, nondust_htlc_sources, vec_type),
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,51 @@ 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+
debug_assert!(source.possibly_matches_output(htlc));
2205+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), Some(source)));
2206+
} else {
2207+
htlc_outputs.push((htlc.clone(), Some(counterparty_sig.clone()), None));
2208+
}
2209+
}
2210+
debug_assert!(sources_iter.next().is_none());
2211+
}
2212+
21642213
let trusted_tx = holder_commitment_tx.trust();
21652214
let txid = trusted_tx.txid();
21662215
let tx_keys = trusted_tx.keys();
@@ -2285,10 +2334,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
22852334
let bounded_fee_estimator = LowerBoundedFeeEstimator::new(&*fee_estimator);
22862335
for update in updates.updates.iter() {
22872336
match update {
2288-
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs } => {
2337+
ChannelMonitorUpdateStep::LatestHolderCommitmentTXInfo { commitment_tx, htlc_outputs, claimed_htlcs, nondust_htlc_sources } => {
22892338
log_trace!(logger, "Updating ChannelMonitor with latest holder commitment transaction info");
22902339
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) {
2340+
if let Err(e) = self.provide_latest_holder_commitment_tx(commitment_tx.clone(), htlc_outputs.clone(), &claimed_htlcs, nondust_htlc_sources.clone()) {
22922341
log_error!(logger, "Providing latest holder commitment transaction failed/was refused:");
22932342
log_error!(logger, " {}", e);
22942343
ret = Err(());
@@ -4132,7 +4181,7 @@ mod tests {
41324181
}
41334182
}
41344183

4135-
macro_rules! preimages_slice_to_htlc_outputs {
4184+
macro_rules! preimages_slice_to_htlcs {
41364185
($preimages_slice: expr) => {
41374186
{
41384187
let mut res = Vec::new();
@@ -4143,21 +4192,20 @@ mod tests {
41434192
cltv_expiry: 0,
41444193
payment_hash: preimage.1.clone(),
41454194
transaction_output_index: Some(idx as u32),
4146-
}, None));
4195+
}, ()));
41474196
}
41484197
res
41494198
}
41504199
}
41514200
}
4152-
macro_rules! preimages_to_holder_htlcs {
4201+
macro_rules! preimages_slice_to_htlc_outputs {
41534202
($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-
}
4203+
preimages_slice_to_htlcs!($preimages_slice).into_iter().map(|(htlc, _)| (htlc, None)).collect()
41594204
}
41604205
}
4206+
let dummy_sig = crate::util::crypto::sign(&secp_ctx,
4207+
&bitcoin::secp256k1::Message::from_slice(&[42; 32]).unwrap(),
4208+
&SecretKey::from_slice(&[42; 32]).unwrap());
41614209

41624210
macro_rules! test_preimages_exist {
41634211
($preimages_slice: expr, $monitor: expr) => {
@@ -4204,13 +4252,15 @@ mod tests {
42044252
let shutdown_pubkey = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
42054253
let best_block = BestBlock::from_network(Network::Testnet);
42064254
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();
4255+
Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &Script::new(),
4256+
(OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, Script::new()),
4257+
&channel_parameters, Script::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()),
4258+
best_block, dummy_key);
4259+
4260+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]);
4261+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4262+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx.clone(),
4263+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42144264
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"1").into_inner()),
42154265
preimages_slice_to_htlc_outputs!(preimages[5..15]), 281474976710655, dummy_key, &logger);
42164266
monitor.provide_latest_counterparty_commitment_tx(Txid::from_inner(Sha256::hash(b"2").into_inner()),
@@ -4243,15 +4293,21 @@ mod tests {
42434293

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

42534306
// 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();
4307+
let mut htlcs = preimages_slice_to_htlcs!(preimages[0..3]);
4308+
let dummy_commitment_tx = HolderCommitmentTransaction::dummy(&mut htlcs);
4309+
monitor.provide_latest_holder_commitment_tx(dummy_commitment_tx,
4310+
htlcs.into_iter().map(|(htlc, _)| (htlc, Some(dummy_sig), None)).collect()).unwrap();
42554311
secret[0..32].clone_from_slice(&hex::decode("27cddaa5624534cb6cb9d7da077cf2b22ab21e9b506fd4998a51d54502e99116").unwrap());
42564312
monitor.provide_secret(281474976710652, secret.clone()).unwrap();
42574313
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)