Skip to content

Commit 2931b05

Browse files
channelmonitor: Persist force-close broadcast preference
Add allow_automated_broadcast flag to ChannelMonitor to prevent automatic commitment transaction broadcasting when channel was previously force-closed with should_broadcast=false. Fixes unsafe broadcast on startup when ChannelManager finds orphaned monitors that were intentionally closed without broadcasting.
1 parent dbd8451 commit 2931b05

File tree

4 files changed

+171
-22
lines changed

4 files changed

+171
-22
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,14 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
11661166
/// The node_id of our counterparty
11671167
counterparty_node_id: PublicKey,
11681168

1169+
/// Controls whether the monitor is allowed to automatically broadcast the latest holder commitment transaction.
1170+
///
1171+
/// This flag is set to `false` when a channel is force-closed with `should_broadcast: false`,
1172+
/// indicating that broadcasting the latest holder commitment transaction would be unsafe.
1173+
///
1174+
/// Default: `true`.
1175+
allow_automated_broadcast: bool,
1176+
11691177
/// Initial counterparty commmitment data needed to recreate the commitment tx
11701178
/// in the persistence pipeline for third-party watchtowers. This will only be present on
11711179
/// monitors created after 0.0.117.
@@ -1467,6 +1475,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
14671475
(27, self.first_confirmed_funding_txo, required),
14681476
(29, self.initial_counterparty_commitment_tx, option),
14691477
(31, self.funding.channel_parameters, required),
1478+
(33, self.allow_automated_broadcast, required),
14701479
});
14711480

14721481
Ok(())
@@ -1684,6 +1693,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
16841693

16851694
best_block,
16861695
counterparty_node_id: counterparty_node_id,
1696+
allow_automated_broadcast: true,
16871697
initial_counterparty_commitment_info: None,
16881698
initial_counterparty_commitment_tx: None,
16891699
balances_empty_height: None,
@@ -2038,7 +2048,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
20382048
/// may be to contact the other node operator out-of-band to coordinate other options available
20392049
/// to you.
20402050
#[rustfmt::skip]
2041-
pub fn broadcast_latest_holder_commitment_txn<B: Deref, F: Deref, L: Deref>(
2051+
pub fn force_broadcast_latest_holder_commitment_txn_unsafe<B: Deref, F: Deref, L: Deref>(
20422052
&self, broadcaster: &B, fee_estimator: &F, logger: &L
20432053
)
20442054
where
@@ -3453,6 +3463,32 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
34533463
);
34543464
}
34553465

3466+
fn maybe_broadcast_latest_holder_commitment_txn<B: Deref, F: Deref, L: Deref>(
3467+
&mut self, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>,
3468+
logger: &WithChannelMonitor<L>,
3469+
) where
3470+
B::Target: BroadcasterInterface,
3471+
F::Target: FeeEstimator,
3472+
L::Target: Logger,
3473+
{
3474+
if !self.allow_automated_broadcast {
3475+
return;
3476+
}
3477+
let detected_funding_spend = self.funding_spend_confirmed.is_some()
3478+
|| self
3479+
.onchain_events_awaiting_threshold_conf
3480+
.iter()
3481+
.any(|event| matches!(event.event, OnchainEvent::FundingSpendConfirmation { .. }));
3482+
if detected_funding_spend {
3483+
log_trace!(
3484+
logger,
3485+
"Avoiding commitment broadcast, already detected confirmed spend onchain"
3486+
);
3487+
return;
3488+
}
3489+
self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, fee_estimator, logger);
3490+
}
3491+
34563492
#[rustfmt::skip]
34573493
fn update_monitor<B: Deref, F: Deref, L: Deref>(
34583494
&mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: &F, logger: &WithChannelMonitor<L>
@@ -3535,28 +3571,16 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35353571
ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => {
35363572
log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast);
35373573
self.lockdown_from_offchain = true;
3538-
if *should_broadcast {
3539-
// There's no need to broadcast our commitment transaction if we've seen one
3540-
// confirmed (even with 1 confirmation) as it'll be rejected as
3541-
// duplicate/conflicting.
3542-
let detected_funding_spend = self.funding_spend_confirmed.is_some() ||
3543-
self.onchain_events_awaiting_threshold_conf.iter().any(
3544-
|event| matches!(event.event, OnchainEvent::FundingSpendConfirmation { .. }));
3545-
if detected_funding_spend {
3546-
log_trace!(logger, "Avoiding commitment broadcast, already detected confirmed spend onchain");
3547-
continue;
3548-
}
3549-
self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &bounded_fee_estimator, logger);
3550-
} else if !self.holder_tx_signed {
3551-
log_error!(logger, "WARNING: You have a potentially-unsafe holder commitment transaction available to broadcast");
3552-
log_error!(logger, " in channel monitor for channel {}!", &self.channel_id());
3553-
log_error!(logger, " Read the docs for ChannelMonitor::broadcast_latest_holder_commitment_txn to take manual action!");
3554-
} else {
3574+
if self.allow_automated_broadcast {
3575+
self.allow_automated_broadcast = *should_broadcast;
3576+
}
3577+
if !*should_broadcast && self.holder_tx_signed {
35553578
// If we generated a MonitorEvent::HolderForceClosed, the ChannelManager
35563579
// will still give us a ChannelForceClosed event with !should_broadcast, but we
35573580
// shouldn't print the scary warning above.
35583581
log_info!(logger, "Channel off-chain state closed after we broadcasted our latest commitment transaction.");
35593582
}
3583+
self.maybe_broadcast_latest_holder_commitment_txn(broadcaster, &bounded_fee_estimator, logger);
35603584
},
35613585
ChannelMonitorUpdateStep::ShutdownScript { scriptpubkey } => {
35623586
log_trace!(logger, "Updating ChannelMonitor with shutdown script");
@@ -5438,6 +5462,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
54385462
let mut payment_preimages_with_info: Option<HashMap<_, _>> = None;
54395463
let mut first_confirmed_funding_txo = RequiredWrapper(None);
54405464
let mut channel_parameters = None;
5465+
let mut allow_automated_broadcast = None;
54415466
read_tlv_fields!(reader, {
54425467
(1, funding_spend_confirmed, option),
54435468
(3, htlcs_resolved_on_chain, optional_vec),
@@ -5455,6 +5480,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
54555480
(27, first_confirmed_funding_txo, (default_value, outpoint)),
54565481
(29, initial_counterparty_commitment_tx, option),
54575482
(31, channel_parameters, (option: ReadableArgs, None)),
5483+
(33, allow_automated_broadcast, option),
54585484
});
54595485
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
54605486
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -5618,6 +5644,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
56185644

56195645
best_block,
56205646
counterparty_node_id: counterparty_node_id.unwrap(),
5647+
allow_automated_broadcast: allow_automated_broadcast.unwrap_or(true),
56215648
initial_counterparty_commitment_info,
56225649
initial_counterparty_commitment_tx,
56235650
balances_empty_height,

lightning/src/ln/channelmanager.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4467,7 +4467,7 @@ where
44674467
/// Fails if `channel_id` is unknown to the manager, or if the
44684468
/// `counterparty_node_id` isn't the counterparty of the corresponding channel.
44694469
/// You can always broadcast the latest local transaction(s) via
4470-
/// [`ChannelMonitor::broadcast_latest_holder_commitment_txn`].
4470+
/// [`ChannelMonitor::force_broadcast_latest_holder_commitment_txn_unsafe`].
44714471
pub fn force_close_without_broadcasting_txn(
44724472
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, error_message: String,
44734473
) -> Result<(), APIError> {
@@ -7655,7 +7655,8 @@ where
76557655
ComplFunc: FnOnce(
76567656
Option<u64>,
76577657
bool,
7658-
) -> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
7658+
)
7659+
-> (Option<MonitorUpdateCompletionAction>, Option<RAAMonitorUpdateBlockingAction>),
76597660
>(
76607661
&self, prev_hop: HTLCPreviousHopData, payment_preimage: PaymentPreimage,
76617662
payment_info: Option<PaymentClaimDetails>, completion_action: ComplFunc,
@@ -14144,7 +14145,7 @@ where
1414414145
// LDK versions prior to 0.0.116 wrote the `pending_background_events`
1414514146
// `MonitorUpdateRegeneratedOnStartup`s here, however there was never a reason to do so -
1414614147
// the closing monitor updates were always effectively replayed on startup (either directly
14147-
// by calling `broadcast_latest_holder_commitment_txn` on a `ChannelMonitor` during
14148+
// by calling `force_broadcast_latest_holder_commitment_txn_unsafe` on a `ChannelMonitor` during
1414814149
// deserialization or, in 0.0.115, by regenerating the monitor update itself).
1414914150
0u64.write(writer)?;
1415014151

lightning/src/ln/monitor_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3151,7 +3151,7 @@ fn do_test_monitor_claims_with_random_signatures(anchors: bool, confirm_counterp
31513151
(&nodes[0], &nodes[1])
31523152
};
31533153

3154-
get_monitor!(closing_node, chan_id).broadcast_latest_holder_commitment_txn(
3154+
get_monitor!(closing_node, chan_id).force_broadcast_latest_holder_commitment_txn_unsafe(
31553155
&closing_node.tx_broadcaster, &closing_node.fee_estimator, &closing_node.logger
31563156
);
31573157

lightning/src/ln/reload_tests.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,3 +1368,124 @@ fn test_htlc_localremoved_persistence() {
13681368
let htlc_fail_msg_after_reload = msgs.2.unwrap().update_fail_htlcs[0].clone();
13691369
assert_eq!(htlc_fail_msg, htlc_fail_msg_after_reload);
13701370
}
1371+
1372+
#[test]
1373+
fn test_deserialize_monitor_force_closed_without_broadcasting_txn() {
1374+
let chanmon_cfgs = create_chanmon_cfgs(2);
1375+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1376+
let logger;
1377+
let fee_estimator;
1378+
let persister;
1379+
let new_chain_monitor;
1380+
let deserialized_chanmgr;
1381+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1382+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1383+
1384+
let (_, _, channel_id, funding_tx) = create_announced_chan_between_nodes(&nodes, 0, 1);
1385+
1386+
// send a ChannelMonitorUpdateStep::ChannelForceClosed with { should_broadcast: false } to the monitor.
1387+
// this should persist the { should_broadcast: false } on the monitor.
1388+
nodes[0]
1389+
.node
1390+
.force_close_without_broadcasting_txn(
1391+
&channel_id,
1392+
&nodes[1].node.get_our_node_id(),
1393+
"test".to_string(),
1394+
)
1395+
.unwrap();
1396+
check_closed_event!(
1397+
nodes[0],
1398+
1,
1399+
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
1400+
[nodes[1].node.get_our_node_id()],
1401+
100000
1402+
);
1403+
1404+
// Serialize monitor and node
1405+
let mut mon_writer = test_utils::TestVecWriter(Vec::new());
1406+
get_monitor!(nodes[0], channel_id).write(&mut mon_writer).unwrap();
1407+
let monitor_bytes = mon_writer.0;
1408+
let manager_bytes = nodes[0].node.encode();
1409+
1410+
let keys_manager = &chanmon_cfgs[0].keys_manager;
1411+
logger = test_utils::TestLogger::new();
1412+
fee_estimator = test_utils::TestFeeEstimator::new(253);
1413+
persister = test_utils::TestPersister::new();
1414+
new_chain_monitor = test_utils::TestChainMonitor::new(
1415+
Some(nodes[0].chain_source),
1416+
nodes[0].tx_broadcaster,
1417+
&logger,
1418+
&fee_estimator,
1419+
&persister,
1420+
keys_manager,
1421+
);
1422+
nodes[0].chain_monitor = &new_chain_monitor;
1423+
1424+
// Deserialize
1425+
let mut mon_read = &monitor_bytes[..];
1426+
let (_, mut monitor) = <(BlockHash, ChannelMonitor<TestChannelSigner>)>::read(
1427+
&mut mon_read,
1428+
(keys_manager, keys_manager),
1429+
)
1430+
.unwrap();
1431+
assert!(mon_read.is_empty());
1432+
1433+
let mut mgr_read = &manager_bytes[..];
1434+
let mut channel_monitors = new_hash_map();
1435+
1436+
// insert a channel monitor without its corresponding channel (it was force-closed before)
1437+
// so when the channel manager deserializes, it replays the ChannelForceClosed with { should_broadcast: true }.
1438+
channel_monitors.insert(monitor.channel_id(), &monitor);
1439+
let (_, deserialized_chanmgr_tmp) = <(
1440+
BlockHash,
1441+
ChannelManager<
1442+
&test_utils::TestChainMonitor,
1443+
&test_utils::TestBroadcaster,
1444+
&test_utils::TestKeysInterface,
1445+
&test_utils::TestKeysInterface,
1446+
&test_utils::TestKeysInterface,
1447+
&test_utils::TestFeeEstimator,
1448+
&test_utils::TestRouter,
1449+
&test_utils::TestMessageRouter,
1450+
&test_utils::TestLogger,
1451+
>,
1452+
)>::read(
1453+
&mut mgr_read,
1454+
ChannelManagerReadArgs {
1455+
default_config: UserConfig::default(),
1456+
entropy_source: keys_manager,
1457+
node_signer: keys_manager,
1458+
signer_provider: keys_manager,
1459+
fee_estimator: &fee_estimator,
1460+
router: nodes[0].router,
1461+
message_router: &nodes[0].message_router,
1462+
chain_monitor: &new_chain_monitor,
1463+
tx_broadcaster: nodes[0].tx_broadcaster,
1464+
logger: &logger,
1465+
channel_monitors,
1466+
},
1467+
)
1468+
.unwrap();
1469+
deserialized_chanmgr = deserialized_chanmgr_tmp;
1470+
nodes[0].node = &deserialized_chanmgr;
1471+
1472+
{
1473+
let txs = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
1474+
assert!(txs.is_empty(), "Expected no transactions to be broadcasted after deserialization, because the should_broadcast was persisted as false");
1475+
}
1476+
1477+
monitor.force_broadcast_latest_holder_commitment_txn_unsafe(
1478+
&nodes[0].tx_broadcaster,
1479+
&&fee_estimator,
1480+
&&logger,
1481+
);
1482+
{
1483+
let txs = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
1484+
assert_eq!(txs.len(), 1, "Expected one transaction to be broadcasted after force_broadcast_latest_holder_commitment_txn_unsafe");
1485+
check_spends!(txs[0], funding_tx);
1486+
assert_eq!(txs[0].input[0].previous_output.txid, funding_tx.compute_txid());
1487+
}
1488+
1489+
assert!(nodes[0].chain_monitor.watch_channel(monitor.channel_id(), monitor).is_ok());
1490+
check_added_monitors!(nodes[0], 1);
1491+
}

0 commit comments

Comments
 (0)