Skip to content

Commit d4d0879

Browse files
committed
Store and check channel data in channel monitors
Add a new `safe_channels` feature flag that gates in-development work toward persisting channel monitors and channels atomically, preventing them from desynchronizing and causing force closures. This commit begins that transition by storing both pieces together and adding consistency checks during writes. These checks mirror what the channel manager currently validates only on reload, but performing them earlier increases coverage and surfaces inconsistencies sooner.
1 parent 377ca5f commit d4d0879

File tree

5 files changed

+304
-7
lines changed

5 files changed

+304
-7
lines changed

ci/ci-tests.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ cargo test -p lightning --verbose --color always --features dnssec
4646
cargo check -p lightning --verbose --color always --features dnssec
4747
cargo doc -p lightning --document-private-items --features dnssec
4848

49+
echo -e "\n\nChecking and testing lightning with safe_channels"
50+
cargo test -p lightning --verbose --color always --features safe_channels
51+
cargo check -p lightning --verbose --color always --features safe_channels
52+
cargo doc -p lightning --document-private-items --features safe_channels
53+
4954
echo -e "\n\nChecking and testing Block Sync Clients with features"
5055

5156
cargo test -p lightning-block-sync --verbose --color always --features rest-client

lightning/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ _externalize_tests = ["inventory", "_test_utils"]
2323
# Allow signing of local transactions that may have been revoked or will be revoked, for functional testing (e.g. justice tx handling).
2424
# This is unsafe to use in production because it may result in the counterparty publishing taking our funds.
2525
unsafe_revoked_tx_signing = []
26+
safe_channels = []
2627

2728
std = []
2829

lightning/src/chain/channelmonitor.rs

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ use crate::ln::chan_utils::{
5050
self, ChannelTransactionParameters, CommitmentTransaction, CounterpartyCommitmentSecrets,
5151
HTLCClaim, HTLCOutputInCommitment, HolderCommitmentTransaction,
5252
};
53+
#[cfg(feature = "safe_channels")]
54+
use crate::ln::channel::read_check_data;
5355
use crate::ln::channel::INITIAL_COMMITMENT_NUMBER;
5456
use crate::ln::channel_keys::{
5557
DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, HtlcKey, RevocationBasepoint,
@@ -111,6 +113,10 @@ pub struct ChannelMonitorUpdate {
111113
/// Will be `None` for `ChannelMonitorUpdate`s constructed on LDK versions prior to 0.0.121 and
112114
/// always `Some` otherwise.
113115
pub channel_id: Option<ChannelId>,
116+
117+
/// The encoded channel data associated with this ChannelMonitor, if any.
118+
#[cfg(feature = "safe_channels")]
119+
pub encoded_channel: Option<Vec<u8>>,
114120
}
115121

116122
impl ChannelMonitorUpdate {
@@ -156,9 +162,16 @@ impl Writeable for ChannelMonitorUpdate {
156162
for update_step in self.updates.iter() {
157163
update_step.write(w)?;
158164
}
165+
#[cfg(not(feature = "safe_channels"))]
166+
write_tlv_fields!(w, {
167+
// 1 was previously used to store `counterparty_node_id`
168+
(3, self.channel_id, option),
169+
});
170+
#[cfg(feature = "safe_channels")]
159171
write_tlv_fields!(w, {
160172
// 1 was previously used to store `counterparty_node_id`
161173
(3, self.channel_id, option),
174+
(5, self.encoded_channel, option)
162175
});
163176
Ok(())
164177
}
@@ -176,11 +189,24 @@ impl Readable for ChannelMonitorUpdate {
176189
}
177190
}
178191
let mut channel_id = None;
192+
#[cfg(not(feature = "safe_channels"))]
179193
read_tlv_fields!(r, {
180194
// 1 was previously used to store `counterparty_node_id`
181195
(3, channel_id, option),
182196
});
183-
Ok(Self { update_id, updates, channel_id })
197+
#[cfg(feature = "safe_channels")]
198+
let mut encoded_channel = None;
199+
#[cfg(feature = "safe_channels")]
200+
read_tlv_fields!(r, {
201+
// 1 was previously used to store `counterparty_node_id`
202+
(3, channel_id, option),
203+
(5, encoded_channel, option)
204+
});
205+
Ok(Self {
206+
update_id, updates, channel_id,
207+
#[cfg(feature = "safe_channels")]
208+
encoded_channel
209+
})
184210
}
185211
}
186212

@@ -1402,6 +1428,11 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
14021428
/// make deciding whether to do so simple, here we track whether this monitor was last written
14031429
/// prior to 0.1.
14041430
written_by_0_1_or_later: bool,
1431+
1432+
/// The serialized channel state as provided via the last `ChannelMonitorUpdate` or via a call to
1433+
/// [`ChannelMonitor::update_encoded_channel`].
1434+
#[cfg(feature = "safe_channels")]
1435+
encoded_channel: Option<Vec<u8>>,
14051436
}
14061437

14071438
// Returns a `&FundingScope` for the one we are currently observing/handling commitment transactions
@@ -1521,6 +1552,12 @@ const MIN_SERIALIZATION_VERSION: u8 = 1;
15211552
pub(crate) fn write_chanmon_internal<Signer: EcdsaChannelSigner, W: Writer>(
15221553
channel_monitor: &ChannelMonitorImpl<Signer>, _is_stub: bool, writer: &mut W,
15231554
) -> Result<(), Error> {
1555+
// Check that the encoded channel (if present) is consistent with the rest of the monitor. This sets an invariant
1556+
// for the safe_channels feature.
1557+
#[cfg(feature = "safe_channels")]
1558+
if let Some(ref encoded_channel) = channel_monitor.encoded_channel {
1559+
channel_monitor.check_encoded_channel_consistency(encoded_channel);
1560+
}
15241561
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
15251562

15261563
channel_monitor.latest_update_id.write(writer)?;
@@ -1733,6 +1770,7 @@ pub(crate) fn write_chanmon_internal<Signer: EcdsaChannelSigner, W: Writer>(
17331770
_ => channel_monitor.pending_monitor_events.clone(),
17341771
};
17351772

1773+
#[cfg(not(feature = "safe_channels"))]
17361774
write_tlv_fields!(writer, {
17371775
(1, channel_monitor.funding_spend_confirmed, option),
17381776
(3, channel_monitor.htlcs_resolved_on_chain, required_vec),
@@ -1757,6 +1795,32 @@ pub(crate) fn write_chanmon_internal<Signer: EcdsaChannelSigner, W: Writer>(
17571795
(37, channel_monitor.funding_seen_onchain, required),
17581796
});
17591797

1798+
#[cfg(feature = "safe_channels")]
1799+
write_tlv_fields!(writer, {
1800+
(1, channel_monitor.funding_spend_confirmed, option),
1801+
(3, channel_monitor.htlcs_resolved_on_chain, required_vec),
1802+
(5, pending_monitor_events, required_vec),
1803+
(7, channel_monitor.funding_spend_seen, required),
1804+
(9, channel_monitor.counterparty_node_id, required),
1805+
(11, channel_monitor.confirmed_commitment_tx_counterparty_output, option),
1806+
(13, channel_monitor.spendable_txids_confirmed, required_vec),
1807+
(15, channel_monitor.counterparty_fulfilled_htlcs, required),
1808+
(17, channel_monitor.initial_counterparty_commitment_info, option),
1809+
(19, channel_monitor.channel_id, required),
1810+
(21, channel_monitor.balances_empty_height, option),
1811+
(23, channel_monitor.holder_pays_commitment_tx_fee, option),
1812+
(25, channel_monitor.payment_preimages, required),
1813+
(27, channel_monitor.first_negotiated_funding_txo, required),
1814+
(29, channel_monitor.initial_counterparty_commitment_tx, option),
1815+
(31, channel_monitor.funding.channel_parameters, required),
1816+
(32, channel_monitor.pending_funding, optional_vec),
1817+
(33, channel_monitor.htlcs_resolved_to_user, required),
1818+
(34, channel_monitor.alternative_funding_confirmed, option),
1819+
(35, channel_monitor.is_manual_broadcast, required),
1820+
(37, channel_monitor.funding_seen_onchain, required),
1821+
(39, channel_monitor.encoded_channel, option),
1822+
});
1823+
17601824
Ok(())
17611825
}
17621826

@@ -1994,6 +2058,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
19942058
alternative_funding_confirmed: None,
19952059

19962060
written_by_0_1_or_later: true,
2061+
#[cfg(feature = "safe_channels")]
2062+
encoded_channel: None,
19972063
})
19982064
}
19992065

@@ -2114,6 +2180,19 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
21142180
inner.update_monitor(updates, broadcaster, fee_estimator, &logger)
21152181
}
21162182

2183+
/// Gets the encoded channel data, if any, associated with this ChannelMonitor.
2184+
#[cfg(feature = "safe_channels")]
2185+
pub fn get_encoded_channel(&self) -> Option<Vec<u8>> {
2186+
self.inner.lock().unwrap().encoded_channel.clone()
2187+
}
2188+
2189+
/// Updates the encoded channel data associated with this ChannelMonitor. To clear the encoded channel data (for
2190+
/// example after shut down of a channel), pass an empty vector.
2191+
#[cfg(feature = "safe_channels")]
2192+
pub fn update_encoded_channel(&self, encoded: Vec<u8>) {
2193+
self.inner.lock().unwrap().update_encoded_channel(encoded);
2194+
}
2195+
21172196
/// Gets the update_id from the latest ChannelMonitorUpdate which was applied to this
21182197
/// ChannelMonitor.
21192198
///
@@ -2719,6 +2798,55 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
27192798
}
27202799

27212800
impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
2801+
#[cfg(feature = "safe_channels")]
2802+
fn check_encoded_channel_consistency(&self, encoded: &[u8]) {
2803+
let encoded_channel_reader = &mut &encoded[..];
2804+
let check_res = read_check_data(encoded_channel_reader);
2805+
if let Ok(check_data) = check_res {
2806+
debug_assert!(
2807+
check_data.cur_holder_commitment_transaction_number
2808+
<= self.get_cur_holder_commitment_number(),
2809+
"cur_holder_commitment_transaction_number - channel: {} vs monitor: {}",
2810+
check_data.cur_holder_commitment_transaction_number,
2811+
self.get_cur_holder_commitment_number()
2812+
);
2813+
debug_assert!(
2814+
check_data.revoked_counterparty_commitment_transaction_number
2815+
<= self.get_min_seen_secret(),
2816+
"revoked_counterparty_commitment_transaction_number - channel: {} vs monitor: {}",
2817+
check_data.revoked_counterparty_commitment_transaction_number,
2818+
self.get_min_seen_secret()
2819+
);
2820+
debug_assert!(
2821+
check_data.cur_counterparty_commitment_transaction_number
2822+
<= self.get_cur_counterparty_commitment_number(),
2823+
"cur_counterparty_commitment_transaction_number - channel: {} vs monitor: {}",
2824+
check_data.cur_counterparty_commitment_transaction_number,
2825+
self.get_cur_counterparty_commitment_number()
2826+
);
2827+
debug_assert!(
2828+
check_data.latest_monitor_update_id >= self.get_latest_update_id(),
2829+
"latest_monitor_update_id - channel: {} vs monitor: {}",
2830+
check_data.latest_monitor_update_id,
2831+
self.get_latest_update_id()
2832+
);
2833+
} else {
2834+
debug_assert!(false, "Failed to read check data from encoded channel");
2835+
}
2836+
}
2837+
2838+
#[cfg(feature = "safe_channels")]
2839+
fn update_encoded_channel(&mut self, encoded: Vec<u8>) {
2840+
if encoded.len() > 0 {
2841+
// Check that the encoded channel is consistent with the rest of the monitor. This sets an invariant for the
2842+
// safe_channels feature.
2843+
self.check_encoded_channel_consistency(&encoded);
2844+
self.encoded_channel = Some(encoded);
2845+
} else {
2846+
self.encoded_channel = None;
2847+
}
2848+
}
2849+
27222850
/// Helper for get_claimable_balances which does the work for an individual HTLC, generating up
27232851
/// to one `Balance` for the HTLC.
27242852
#[rustfmt::skip]
@@ -4405,6 +4533,13 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
44054533
}
44064534
}
44074535

4536+
// Assume that if the update contains no encoded channel, that the channel remained unchanged. We
4537+
// therefore do not update the monitor.
4538+
#[cfg(feature="safe_channels")]
4539+
if let Some(encoded_channel) = updates.encoded_channel.as_ref() {
4540+
self.update_encoded_channel(encoded_channel.clone());
4541+
}
4542+
44084543
if ret.is_ok() && self.no_further_updates_allowed() && is_pre_close_update {
44094544
log_error!(logger, "Refusing Channel Monitor Update as counterparty attempted to update commitment after funding was spent");
44104545
Err(())
@@ -6644,6 +6779,33 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
66446779
let mut alternative_funding_confirmed = None;
66456780
let mut is_manual_broadcast = RequiredWrapper(None);
66466781
let mut funding_seen_onchain = RequiredWrapper(None);
6782+
#[cfg(not(feature="safe_channels"))]
6783+
read_tlv_fields!(reader, {
6784+
(1, funding_spend_confirmed, option),
6785+
(3, htlcs_resolved_on_chain, optional_vec),
6786+
(5, pending_monitor_events, optional_vec),
6787+
(7, funding_spend_seen, option),
6788+
(9, counterparty_node_id, option),
6789+
(11, confirmed_commitment_tx_counterparty_output, option),
6790+
(13, spendable_txids_confirmed, optional_vec),
6791+
(15, counterparty_fulfilled_htlcs, option),
6792+
(17, initial_counterparty_commitment_info, option),
6793+
(19, channel_id, option),
6794+
(21, balances_empty_height, option),
6795+
(23, holder_pays_commitment_tx_fee, option),
6796+
(25, payment_preimages_with_info, option),
6797+
(27, first_negotiated_funding_txo, (default_value, outpoint)),
6798+
(29, initial_counterparty_commitment_tx, option),
6799+
(31, channel_parameters, (option: ReadableArgs, None)),
6800+
(32, pending_funding, optional_vec),
6801+
(33, htlcs_resolved_to_user, option),
6802+
(34, alternative_funding_confirmed, option),
6803+
(35, is_manual_broadcast, (default_value, false)),
6804+
(37, funding_seen_onchain, (default_value, true)),
6805+
});
6806+
#[cfg(feature="safe_channels")]
6807+
let mut encoded_channel = None;
6808+
#[cfg(feature="safe_channels")]
66476809
read_tlv_fields!(reader, {
66486810
(1, funding_spend_confirmed, option),
66496811
(3, htlcs_resolved_on_chain, optional_vec),
@@ -6666,6 +6828,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
66666828
(34, alternative_funding_confirmed, option),
66676829
(35, is_manual_broadcast, (default_value, false)),
66686830
(37, funding_seen_onchain, (default_value, true)),
6831+
(39, encoded_channel, option),
66696832
});
66706833
// Note that `payment_preimages_with_info` was added (and is always written) in LDK 0.1, so
66716834
// we can use it to determine if this monitor was last written by LDK 0.1 or later.
@@ -6843,6 +7006,8 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
68437006
alternative_funding_confirmed,
68447007

68457008
written_by_0_1_or_later,
7009+
#[cfg(feature="safe_channels")]
7010+
encoded_channel,
68467011
});
68477012

68487013
if counterparty_node_id.is_none() {

0 commit comments

Comments
 (0)