Skip to content

Commit e533446

Browse files
authored
Merge pull request #1531 from ariard/2022-06-fee-sniping
Funding_tx: add anti-fee sniping recommendation and check if final
2 parents c180ddd + 69344fa commit e533446

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

lightning/src/ln/channelmanager.rs

+20
Original file line numberDiff line numberDiff line change
@@ -2777,6 +2777,9 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27772777
/// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
27782778
/// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
27792779
///
2780+
/// Returns [`APIError::APIMisuseError`] if the funding transaction is not final for propagation
2781+
/// across the p2p network.
2782+
///
27802783
/// Returns [`APIError::ChannelUnavailable`] if a funding transaction has already been provided
27812784
/// for the channel or if the channel has been closed as indicated by [`Event::ChannelClosed`].
27822785
///
@@ -2792,6 +2795,11 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27922795
/// not currently support replacing a funding transaction on an existing channel. Instead,
27932796
/// create a new channel with a conflicting funding transaction.
27942797
///
2798+
/// Note to keep the miner incentives aligned in moving the blockchain forward, we recommend
2799+
/// the wallet software generating the funding transaction to apply anti-fee sniping as
2800+
/// implemented by Bitcoin Core wallet. See <https://bitcoinops.org/en/topics/fee-sniping/>
2801+
/// for more details.
2802+
///
27952803
/// [`Event::FundingGenerationReady`]: crate::util::events::Event::FundingGenerationReady
27962804
/// [`Event::ChannelClosed`]: crate::util::events::Event::ChannelClosed
27972805
pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
@@ -2804,6 +2812,18 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
28042812
});
28052813
}
28062814
}
2815+
{
2816+
let height = self.best_block.read().unwrap().height();
2817+
// Transactions are evaluated as final by network mempools at the next block. However, the modules
2818+
// constituting our Lightning node might not have perfect sync about their blockchain views. Thus, if
2819+
// the wallet module is in advance on the LDK view, allow one more block of headroom.
2820+
// TODO: updated if/when https://github.com/rust-bitcoin/rust-bitcoin/pull/994 landed and rust-bitcoin bumped.
2821+
if !funding_transaction.input.iter().all(|input| input.sequence == 0xffffffff) && 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+
}
28072827
self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, |chan, tx| {
28082828
let mut output_index = None;
28092829
let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();

lightning/src/ln/functional_tests.rs

+44
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ 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, TxIn, TxOut, Witness};
45+
use bitcoin::OutPoint as BitcoinOutPoint;
4446

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

0 commit comments

Comments
 (0)