Skip to content

Commit 24c38f5

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 15724ec commit 24c38f5

File tree

4 files changed

+169
-22
lines changed

4 files changed

+169
-22
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,14 @@ pub(crate) struct ChannelMonitorImpl<Signer: EcdsaChannelSigner> {
12681268
/// The node_id of our counterparty
12691269
counterparty_node_id: PublicKey,
12701270

1271+
/// Controls whether the monitor is allowed to automatically broadcast the latest holder commitment transaction.
1272+
///
1273+
/// This flag is set to `false` when a channel is force-closed with `should_broadcast: false`,
1274+
/// indicating that broadcasting the latest holder commitment transaction would be unsafe.
1275+
///
1276+
/// Default: `true`.
1277+
allow_automated_broadcast: bool,
1278+
12711279
/// Initial counterparty commmitment data needed to recreate the commitment tx
12721280
/// in the persistence pipeline for third-party watchtowers. This will only be present on
12731281
/// monitors created after 0.0.117.
@@ -1569,6 +1577,7 @@ impl<Signer: EcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signer> {
15691577
(27, self.first_confirmed_funding_txo, required),
15701578
(29, self.initial_counterparty_commitment_tx, option),
15711579
(31, self.funding.channel_parameters, required),
1580+
(33, self.allow_automated_broadcast, required),
15721581
(32, self.pending_funding, optional_vec),
15731582
});
15741583

@@ -1788,6 +1797,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
17881797

17891798
best_block,
17901799
counterparty_node_id: counterparty_node_id,
1800+
allow_automated_broadcast: true,
17911801
initial_counterparty_commitment_info: None,
17921802
initial_counterparty_commitment_tx: None,
17931803
balances_empty_height: None,
@@ -2144,7 +2154,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
21442154
/// may be to contact the other node operator out-of-band to coordinate other options available
21452155
/// to you.
21462156
#[rustfmt::skip]
2147-
pub fn broadcast_latest_holder_commitment_txn<B: Deref, F: Deref, L: Deref>(
2157+
pub fn force_broadcast_latest_holder_commitment_txn_unsafe<B: Deref, F: Deref, L: Deref>(
21482158
&self, broadcaster: &B, fee_estimator: &F, logger: &L
21492159
)
21502160
where
@@ -3681,6 +3691,32 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
36813691
Ok(())
36823692
}
36833693

3694+
fn maybe_broadcast_latest_holder_commitment_txn<B: Deref, F: Deref, L: Deref>(
3695+
&mut self, broadcaster: &B, fee_estimator: &LowerBoundedFeeEstimator<F>,
3696+
logger: &WithChannelMonitor<L>,
3697+
) where
3698+
B::Target: BroadcasterInterface,
3699+
F::Target: FeeEstimator,
3700+
L::Target: Logger,
3701+
{
3702+
if !self.allow_automated_broadcast {
3703+
return;
3704+
}
3705+
let detected_funding_spend = self.funding_spend_confirmed.is_some()
3706+
|| self
3707+
.onchain_events_awaiting_threshold_conf
3708+
.iter()
3709+
.any(|event| matches!(event.event, OnchainEvent::FundingSpendConfirmation { .. }));
3710+
if detected_funding_spend {
3711+
log_trace!(
3712+
logger,
3713+
"Avoiding commitment broadcast, already detected confirmed spend onchain"
3714+
);
3715+
return;
3716+
}
3717+
self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, fee_estimator, logger);
3718+
}
3719+
36843720
#[rustfmt::skip]
36853721
fn update_monitor<B: Deref, F: Deref, L: Deref>(
36863722
&mut self, updates: &ChannelMonitorUpdate, broadcaster: &B, fee_estimator: &F, logger: &WithChannelMonitor<L>
@@ -3774,28 +3810,14 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
37743810
ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast } => {
37753811
log_trace!(logger, "Updating ChannelMonitor: channel force closed, should broadcast: {}", should_broadcast);
37763812
self.lockdown_from_offchain = true;
3777-
if *should_broadcast {
3778-
// There's no need to broadcast our commitment transaction if we've seen one
3779-
// confirmed (even with 1 confirmation) as it'll be rejected as
3780-
// duplicate/conflicting.
3781-
let detected_funding_spend = self.funding_spend_confirmed.is_some() ||
3782-
self.onchain_events_awaiting_threshold_conf.iter().any(
3783-
|event| matches!(event.event, OnchainEvent::FundingSpendConfirmation { .. }));
3784-
if detected_funding_spend {
3785-
log_trace!(logger, "Avoiding commitment broadcast, already detected confirmed spend onchain");
3786-
continue;
3787-
}
3788-
self.queue_latest_holder_commitment_txn_for_broadcast(broadcaster, &bounded_fee_estimator, logger);
3789-
} else if !self.holder_tx_signed {
3790-
log_error!(logger, "WARNING: You have a potentially-unsafe holder commitment transaction available to broadcast");
3791-
log_error!(logger, " in channel monitor for channel {}!", &self.channel_id());
3792-
log_error!(logger, " Read the docs for ChannelMonitor::broadcast_latest_holder_commitment_txn to take manual action!");
3793-
} else {
3813+
self.allow_automated_broadcast = *should_broadcast;
3814+
if !*should_broadcast && self.holder_tx_signed {
37943815
// If we generated a MonitorEvent::HolderForceClosed, the ChannelManager
37953816
// will still give us a ChannelForceClosed event with !should_broadcast, but we
37963817
// shouldn't print the scary warning above.
37973818
log_info!(logger, "Channel off-chain state closed after we broadcasted our latest commitment transaction.");
37983819
}
3820+
self.maybe_broadcast_latest_holder_commitment_txn(broadcaster, &bounded_fee_estimator, logger);
37993821
},
38003822
ChannelMonitorUpdateStep::ShutdownScript { scriptpubkey } => {
38013823
log_trace!(logger, "Updating ChannelMonitor with shutdown script");
@@ -5682,6 +5704,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
56825704
let mut first_confirmed_funding_txo = RequiredWrapper(None);
56835705
let mut channel_parameters = None;
56845706
let mut pending_funding = None;
5707+
let mut allow_automated_broadcast = None;
56855708
read_tlv_fields!(reader, {
56865709
(1, funding_spend_confirmed, option),
56875710
(3, htlcs_resolved_on_chain, optional_vec),
@@ -5700,6 +5723,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
57005723
(29, initial_counterparty_commitment_tx, option),
57015724
(31, channel_parameters, (option: ReadableArgs, None)),
57025725
(32, pending_funding, optional_vec),
5726+
(33, allow_automated_broadcast, option),
57035727
});
57045728
if let Some(payment_preimages_with_info) = payment_preimages_with_info {
57055729
if payment_preimages_with_info.len() != payment_preimages.len() {
@@ -5864,6 +5888,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
58645888

58655889
best_block,
58665890
counterparty_node_id: counterparty_node_id.unwrap(),
5891+
allow_automated_broadcast: allow_automated_broadcast.unwrap_or(true),
58675892
initial_counterparty_commitment_info,
58685893
initial_counterparty_commitment_tx,
58695894
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)