Skip to content

Commit 9f3bb7d

Browse files
committed
Fix incorrect anchors counterparty_payment_script upon deserialization
1 parent 3299d88 commit 9f3bb7d

File tree

3 files changed

+119
-2
lines changed

3 files changed

+119
-2
lines changed

lightning/src/chain/channelmonitor.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,16 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
16961696
Vec::new()
16971697
}
16981698
}
1699+
1700+
#[cfg(test)]
1701+
pub fn get_counterparty_payment_script(&self) -> Script{
1702+
self.inner.lock().unwrap().counterparty_payment_script.clone()
1703+
}
1704+
1705+
#[cfg(test)]
1706+
pub fn set_counterparty_payment_script(&self, script: Script) {
1707+
self.inner.lock().unwrap().counterparty_payment_script = script;
1708+
}
16991709
}
17001710

17011711
impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
@@ -4146,7 +4156,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
41464156
1 => { None },
41474157
_ => return Err(DecodeError::InvalidValue),
41484158
};
4149-
let counterparty_payment_script = Readable::read(reader)?;
4159+
let mut counterparty_payment_script: Script = Readable::read(reader)?;
41504160
let shutdown_script = {
41514161
let script = <Script as Readable>::read(reader)?;
41524162
if script.is_empty() { None } else { Some(script) }
@@ -4347,6 +4357,17 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
43474357
(17, initial_counterparty_commitment_info, option),
43484358
});
43494359

4360+
// Monitors for anchor outputs channels opened in v0.0.116 suffered from a bug in which the
4361+
// wrong `counterparty_payment_script` was being tracked. Fix it now on deserialization to
4362+
// give them a chance to recognize the spendable output.
4363+
if onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx() &&
4364+
counterparty_payment_script.is_v0_p2wpkh()
4365+
{
4366+
let payment_point = onchain_tx_handler.channel_transaction_parameters.holder_pubkeys.payment_point;
4367+
counterparty_payment_script =
4368+
chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point).to_v0_p2wsh();
4369+
}
4370+
43504371
Ok((best_block.block_hash(), ChannelMonitor::from_impl(ChannelMonitorImpl {
43514372
latest_update_id,
43524373
commitment_transaction_number_obscure_factor,

lightning/src/ln/monitor_tests.rs

+87
Original file line numberDiff line numberDiff line change
@@ -2318,3 +2318,90 @@ fn test_anchors_aggregated_revoked_htlc_tx() {
23182318
// revoked commitment which Bob has the preimage for.
23192319
assert_eq!(nodes[1].chain_monitor.chain_monitor.get_claimable_balances(&[]).len(), 6);
23202320
}
2321+
2322+
fn do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(confirm_commitment_before_reload: bool) {
2323+
// Tests that we'll fix a ChannelMonitor's `counterparty_payment_script` for an anchor outputs
2324+
// channel upon deserialization.
2325+
let chanmon_cfgs = create_chanmon_cfgs(2);
2326+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
2327+
let persister;
2328+
let chain_monitor;
2329+
let mut user_config = test_default_channel_config();
2330+
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
2331+
user_config.manually_accept_inbound_channels = true;
2332+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(user_config), Some(user_config)]);
2333+
let node_deserialized;
2334+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
2335+
2336+
let (_, _, chan_id, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100_000, 50_000_000);
2337+
2338+
// Set the monitor's `counterparty_payment_script` to a dummy P2WPKH script.
2339+
let secp = Secp256k1::new();
2340+
let privkey = bitcoin::PrivateKey::from_slice(&[1; 32], bitcoin::Network::Testnet).unwrap();
2341+
let pubkey = bitcoin::PublicKey::from_private_key(&secp, &privkey);
2342+
let p2wpkh_script = Script::new_v0_p2wpkh(&pubkey.wpubkey_hash().unwrap());
2343+
get_monitor!(nodes[1], chan_id).set_counterparty_payment_script(p2wpkh_script.clone());
2344+
assert_eq!(get_monitor!(nodes[1], chan_id).get_counterparty_payment_script(), p2wpkh_script);
2345+
2346+
// Confirm the counterparty's commitment and reload the monitor (either before or after) such
2347+
// that we arrive at the correct `counterparty_payment_script` after the reload.
2348+
nodes[0].node.force_close_broadcasting_latest_txn(&chan_id, &nodes[1].node.get_our_node_id()).unwrap();
2349+
check_added_monitors(&nodes[0], 1);
2350+
check_closed_broadcast(&nodes[0], 1, true);
2351+
check_closed_event!(&nodes[0], 1, ClosureReason::HolderForceClosed, false,
2352+
[nodes[1].node.get_our_node_id()], 100000);
2353+
2354+
let commitment_tx = {
2355+
let mut txn = nodes[0].tx_broadcaster.unique_txn_broadcast();
2356+
assert_eq!(txn.len(), 1);
2357+
assert_eq!(txn[0].output.len(), 4);
2358+
check_spends!(txn[0], funding_tx);
2359+
txn.pop().unwrap()
2360+
};
2361+
2362+
mine_transaction(&nodes[0], &commitment_tx);
2363+
let commitment_tx_conf_height = if confirm_commitment_before_reload {
2364+
// We should expect our round trip serialization check to fail as we're writing the monitor
2365+
// with the incorrect P2WPKH script but reading it with the correct P2WSH script.
2366+
*nodes[1].chain_monitor.expect_monitor_round_trip_fail.lock().unwrap() = Some(chan_id);
2367+
let commitment_tx_conf_height = block_from_scid(&mine_transaction(&nodes[1], &commitment_tx));
2368+
let serialized_monitor = get_monitor!(nodes[1], chan_id).encode();
2369+
reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized);
2370+
commitment_tx_conf_height
2371+
} else {
2372+
let serialized_monitor = get_monitor!(nodes[1], chan_id).encode();
2373+
reload_node!(nodes[1], user_config, &nodes[1].node.encode(), &[&serialized_monitor], persister, chain_monitor, node_deserialized);
2374+
let commitment_tx_conf_height = block_from_scid(&mine_transaction(&nodes[1], &commitment_tx));
2375+
check_added_monitors(&nodes[1], 1);
2376+
check_closed_broadcast(&nodes[1], 1, true);
2377+
commitment_tx_conf_height
2378+
};
2379+
check_closed_event!(&nodes[1], 1, ClosureReason::CommitmentTxConfirmed, false,
2380+
[nodes[0].node.get_our_node_id()], 100000);
2381+
assert!(get_monitor!(nodes[1], chan_id).get_counterparty_payment_script().is_v0_p2wsh());
2382+
2383+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
2384+
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
2385+
2386+
if confirm_commitment_before_reload {
2387+
// If we saw the commitment before our `counterparty_payment_script` was fixed, we'll never
2388+
// get the spendable output event for the `to_remote` output, so we'll need to get it
2389+
// manually via `get_spendable_outputs`.
2390+
check_added_monitors(&nodes[1], 1);
2391+
let outputs = get_monitor!(nodes[1], chan_id).get_spendable_outputs(&commitment_tx, commitment_tx_conf_height);
2392+
assert_eq!(outputs.len(), 1);
2393+
let spend_tx = nodes[1].keys_manager.backing.spend_spendable_outputs(
2394+
&[&outputs[0]], Vec::new(), Builder::new().push_opcode(opcodes::all::OP_RETURN).into_script(),
2395+
253, None, &secp
2396+
).unwrap();
2397+
check_spends!(spend_tx, &commitment_tx);
2398+
} else {
2399+
test_spendable_output(&nodes[1], &commitment_tx);
2400+
}
2401+
}
2402+
2403+
#[test]
2404+
fn test_anchors_monitor_fixes_counterparty_payment_script_on_reload() {
2405+
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(false);
2406+
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(true);
2407+
}

lightning/src/util/test_utils.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ pub struct TestChainMonitor<'a> {
207207
/// ChannelForceClosed event for the given channel_id with should_broadcast set to the given
208208
/// boolean.
209209
pub expect_channel_force_closed: Mutex<Option<(ChannelId, bool)>>,
210+
/// If this is set to Some(), the next round trip serialization check will not hold after an
211+
/// update_channel call (not watch_channel) for the given channel_id.
212+
pub expect_monitor_round_trip_fail: Mutex<Option<ChannelId>>,
210213
}
211214
impl<'a> TestChainMonitor<'a> {
212215
pub fn new(chain_source: Option<&'a TestChainSource>, broadcaster: &'a chaininterface::BroadcasterInterface, logger: &'a TestLogger, fee_estimator: &'a TestFeeEstimator, persister: &'a chainmonitor::Persist<TestChannelSigner>, keys_manager: &'a TestKeysInterface) -> Self {
@@ -217,6 +220,7 @@ impl<'a> TestChainMonitor<'a> {
217220
chain_monitor: chainmonitor::ChainMonitor::new(chain_source, broadcaster, logger, fee_estimator, persister),
218221
keys_manager,
219222
expect_channel_force_closed: Mutex::new(None),
223+
expect_monitor_round_trip_fail: Mutex::new(None),
220224
}
221225
}
222226

@@ -267,7 +271,12 @@ impl<'a> chain::Watch<TestChannelSigner> for TestChainMonitor<'a> {
267271
monitor.write(&mut w).unwrap();
268272
let new_monitor = <(BlockHash, channelmonitor::ChannelMonitor<TestChannelSigner>)>::read(
269273
&mut io::Cursor::new(&w.0), (self.keys_manager, self.keys_manager)).unwrap().1;
270-
assert!(new_monitor == *monitor);
274+
if let Some(chan_id) = self.expect_monitor_round_trip_fail.lock().unwrap().take() {
275+
assert_eq!(chan_id, funding_txo.to_channel_id());
276+
assert!(new_monitor != *monitor);
277+
} else {
278+
assert!(new_monitor == *monitor);
279+
}
271280
self.added_monitors.lock().unwrap().push((funding_txo, new_monitor));
272281
update_res
273282
}

0 commit comments

Comments
 (0)