Skip to content

Commit a61a209

Browse files
author
Antoine Riard
committed
Check if funding transaction is final for propagation
If the funding transaction is timelocked beyond the next block of our best known chain tip, return an APIError instead of silently failing at broadcast attempt.
1 parent 22dc964 commit a61a209

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

lightning/src/ln/channelmanager.rs

+14
Original file line numberDiff line numberDiff line change
@@ -2783,6 +2783,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27832783
/// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
27842784
/// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
27852785
///
2786+
/// Returns [`APIError::APIMisueError`] if the funding transaction is not final for propagation
2787+
/// across the p2p network.
2788+
///
27862789
/// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided
27872790
/// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`].
27882791
///
@@ -2810,6 +2813,17 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
28102813
});
28112814
}
28122815
}
2816+
{
2817+
let height = self.best_block.read().unwrap().height();
2818+
// Transactions are evaluated as final by network mempools at the next block. However, the modules
2819+
// constituting our Lightning node might not have perfect sync about their blockchain views. Thus, if
2820+
// the wallet module is in advance on the LDK view, allow one more block of headroom.
2821+
if funding_transaction.lock_time < 500_000_000 && funding_transaction.lock_time > height + 2 {
2822+
return Err(APIError::APIMisuseError {
2823+
err: "Funding transaction absolute timelock is non-final".to_owned()
2824+
});
2825+
}
2826+
}
28132827
self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, |chan, tx| {
28142828
let mut output_index = None;
28152829
let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();

lightning/src/ln/functional_tests.rs

+42
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use bitcoin::blockdata::script::Builder;
4141
use bitcoin::blockdata::opcodes;
4242
use bitcoin::blockdata::constants::genesis_block;
4343
use bitcoin::network::constants::Network;
44+
use bitcoin::{Transaction, TxOut};
4445

4546
use bitcoin::secp256k1::Secp256k1;
4647
use bitcoin::secp256k1::{PublicKey,SecretKey};
@@ -10329,3 +10330,44 @@ fn test_max_dust_htlc_exposure() {
1032910330
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, false);
1033010331
do_test_max_dust_htlc_exposure(false, ExposureEvent::AtUpdateFeeOutbound, true);
1033110332
}
10333+
10334+
#[test]
10335+
fn test_non_final_funding_tx() {
10336+
let chanmon_cfgs = create_chanmon_cfgs(2);
10337+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
10338+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
10339+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
10340+
10341+
let temp_channel_id = nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 42, None).unwrap();
10342+
let open_channel_message = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
10343+
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &open_channel_message);
10344+
let accept_channel_message = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
10345+
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &accept_channel_message);
10346+
10347+
let best_height = nodes[0].node.best_block.read().unwrap().height();
10348+
10349+
let chan_id = *nodes[0].network_chan_count.borrow();
10350+
let events = nodes[0].node.get_and_clear_pending_events();
10351+
assert_eq!(events.len(), 1);
10352+
let mut tx = match events[0] {
10353+
Event::FundingGenerationReady { ref channel_value_satoshis, ref output_script, .. } => {
10354+
// Timelock the transaction _beyond_ the best client height + 2.
10355+
Transaction { version: chan_id as i32, lock_time: best_height + 3, input: Vec::new(), output: vec![TxOut {
10356+
value: *channel_value_satoshis, script_pubkey: output_script.clone(),
10357+
}]}
10358+
},
10359+
_ => panic!("Unexpected event"),
10360+
};
10361+
// Transaction should fail as it's evaluated as non-final for propagation.
10362+
match nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()) {
10363+
Err(APIError::APIMisuseError { err }) => {
10364+
assert_eq!(format!("Funding transaction absolute timelock is non-final"), err);
10365+
},
10366+
_ => panic!()
10367+
}
10368+
10369+
// However, transaction should be accepted if it's in a +2 headroom from best block.
10370+
tx.lock_time -= 1;
10371+
assert!(nodes[0].node.funding_transaction_generated(&temp_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
10372+
nodes[0].node.get_and_clear_pending_msg_events();
10373+
}

0 commit comments

Comments
 (0)