Skip to content

Commit eb44d99

Browse files
authored
Merge pull request #1924 from benthecarman/handle-coinbase-funding-channel
Handle if funding output is in a coinbase transaction
2 parents 1d01e47 + b28bf0b commit eb44d99

File tree

4 files changed

+124
-15
lines changed

4 files changed

+124
-15
lines changed

lightning/src/ln/channel.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,9 @@ pub(crate) const DISCONNECT_PEER_AWAITING_RESPONSE_TICKS: usize = 2;
594594
/// exceeding this age limit will be force-closed and purged from memory.
595595
pub(crate) const UNFUNDED_CHANNEL_AGE_LIMIT_TICKS: usize = 60;
596596

597+
/// Number of blocks needed for an output from a coinbase transaction to be spendable.
598+
pub(crate) const COINBASE_MATURITY: u32 = 100;
599+
597600
struct PendingChannelMonitorUpdate {
598601
update: ChannelMonitorUpdate,
599602
}
@@ -4734,12 +4737,14 @@ impl<SP: Deref> Channel<SP> where
47344737
return Err(ClosureReason::ProcessingError { err: err_reason.to_owned() });
47354738
} else {
47364739
if self.context.is_outbound() {
4737-
for input in tx.input.iter() {
4738-
if input.witness.is_empty() {
4739-
// We generated a malleable funding transaction, implying we've
4740-
// just exposed ourselves to funds loss to our counterparty.
4741-
#[cfg(not(fuzzing))]
4742-
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4740+
if !tx.is_coin_base() {
4741+
for input in tx.input.iter() {
4742+
if input.witness.is_empty() {
4743+
// We generated a malleable funding transaction, implying we've
4744+
// just exposed ourselves to funds loss to our counterparty.
4745+
#[cfg(not(fuzzing))]
4746+
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
4747+
}
47434748
}
47444749
}
47454750
}
@@ -4750,6 +4755,13 @@ impl<SP: Deref> Channel<SP> where
47504755
Err(_) => panic!("Block was bogus - either height was > 16 million, had > 16 million transactions, or had > 65k outputs"),
47514756
}
47524757
}
4758+
// If this is a coinbase transaction and not a 0-conf channel
4759+
// we should update our min_depth to 100 to handle coinbase maturity
4760+
if tx.is_coin_base() &&
4761+
self.context.minimum_depth.unwrap_or(0) > 0 &&
4762+
self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
4763+
self.context.minimum_depth = Some(COINBASE_MATURITY);
4764+
}
47534765
}
47544766
// If we allow 1-conf funding, we may need to check for channel_ready here and
47554767
// send it immediately instead of waiting for a best_block_updated call (which
@@ -5821,6 +5833,15 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {
58215833

58225834
self.context.channel_state = ChannelState::FundingCreated as u32;
58235835
self.context.channel_id = funding_txo.to_channel_id();
5836+
5837+
// If the funding transaction is a coinbase transaction, we need to set the minimum depth to 100.
5838+
// We can skip this if it is a zero-conf channel.
5839+
if funding_transaction.is_coin_base() &&
5840+
self.context.minimum_depth.unwrap_or(0) > 0 &&
5841+
self.context.minimum_depth.unwrap_or(0) < COINBASE_MATURITY {
5842+
self.context.minimum_depth = Some(COINBASE_MATURITY);
5843+
}
5844+
58245845
self.context.funding_transaction = Some(funding_transaction);
58255846

58265847
let channel = Channel {

lightning/src/ln/channelmanager.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3561,11 +3561,13 @@ where
35613561
pub fn funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
35623562
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
35633563

3564-
for inp in funding_transaction.input.iter() {
3565-
if inp.witness.is_empty() {
3566-
return Err(APIError::APIMisuseError {
3567-
err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
3568-
});
3564+
if !funding_transaction.is_coin_base() {
3565+
for inp in funding_transaction.input.iter() {
3566+
if inp.witness.is_empty() {
3567+
return Err(APIError::APIMisuseError {
3568+
err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
3569+
});
3570+
}
35693571
}
35703572
}
35713573
{

lightning/src/ln/functional_test_utils.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use alloc::rc::Rc;
4646
use crate::sync::{Arc, Mutex, LockTestExt, RwLock};
4747
use core::mem;
4848
use core::iter::repeat;
49-
use bitcoin::{PackedLockTime, TxMerkleNode};
49+
use bitcoin::{PackedLockTime, TxIn, TxMerkleNode};
5050

5151
pub const CHAN_CONFIRM_DEPTH: u32 = 10;
5252

@@ -1005,7 +1005,23 @@ macro_rules! reload_node {
10051005
};
10061006
}
10071007

1008-
pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128) -> (ChannelId, Transaction, OutPoint) {
1008+
pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
1009+
expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
1010+
-> (ChannelId, Transaction, OutPoint)
1011+
{
1012+
internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, false)
1013+
}
1014+
1015+
pub fn create_coinbase_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
1016+
expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128)
1017+
-> (ChannelId, Transaction, OutPoint)
1018+
{
1019+
internal_create_funding_transaction(node, expected_counterparty_node_id, expected_chan_value, expected_user_chan_id, true)
1020+
}
1021+
1022+
fn internal_create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>,
1023+
expected_counterparty_node_id: &PublicKey, expected_chan_value: u64, expected_user_chan_id: u128,
1024+
coinbase: bool) -> (ChannelId, Transaction, OutPoint) {
10091025
let chan_id = *node.network_chan_count.borrow();
10101026

10111027
let events = node.node.get_and_clear_pending_events();
@@ -1016,7 +1032,16 @@ pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_
10161032
assert_eq!(*channel_value_satoshis, expected_chan_value);
10171033
assert_eq!(user_channel_id, expected_user_chan_id);
10181034

1019-
let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: vec![TxOut {
1035+
let input = if coinbase {
1036+
vec![TxIn {
1037+
previous_output: bitcoin::OutPoint::null(),
1038+
..Default::default()
1039+
}]
1040+
} else {
1041+
Vec::new()
1042+
};
1043+
1044+
let tx = Transaction { version: chan_id as i32, lock_time: PackedLockTime::ZERO, input, output: vec![TxOut {
10201045
value: *channel_value_satoshis, script_pubkey: output_script.clone(),
10211046
}]};
10221047
let funding_outpoint = OutPoint { txid: tx.txid(), index: 0 };
@@ -1025,6 +1050,7 @@ pub fn create_funding_transaction<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, expected_
10251050
_ => panic!("Unexpected event"),
10261051
}
10271052
}
1053+
10281054
pub fn sign_funding_transaction<'a, 'b, 'c>(node_a: &Node<'a, 'b, 'c>, node_b: &Node<'a, 'b, 'c>, channel_value: u64, expected_temporary_channel_id: ChannelId) -> Transaction {
10291055
let (temporary_channel_id, tx, funding_output) = create_funding_transaction(node_a, &node_b.node.get_our_node_id(), channel_value, 42);
10301056
assert_eq!(temporary_channel_id, expected_temporary_channel_id);

lightning/src/ln/functional_tests.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::chain::transaction::OutPoint;
2020
use crate::sign::{ChannelSigner, EcdsaChannelSigner, EntropySource, SignerProvider};
2121
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
2222
use crate::ln::{ChannelId, PaymentPreimage, PaymentSecret, PaymentHash};
23-
use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel};
23+
use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY};
2424
use crate::ln::channelmanager::{self, PaymentId, RAACommitmentOrder, PaymentSendFailure, RecipientOnionFields, BREAKDOWN_TIMEOUT, ENABLE_GOSSIP_TICKS, DISABLE_GOSSIP_TICKS, MIN_CLTV_EXPIRY_DELTA};
2525
use crate::ln::channel::{DISCONNECT_PEER_AWAITING_RESPONSE_TICKS, ChannelError};
2626
use crate::ln::{chan_utils, onion_utils};
@@ -9133,6 +9133,66 @@ fn test_invalid_funding_tx() {
91339133
mine_transaction(&nodes[1], &spend_tx);
91349134
}
91359135

9136+
#[test]
9137+
fn test_coinbase_funding_tx() {
9138+
// Miners are able to fund channels directly from coinbase transactions, however
9139+
// by consensus rules, outputs of a coinbase transaction are encumbered by a 100
9140+
// block maturity timelock. To ensure that a (non-0conf) channel like this is enforceable
9141+
// on-chain, the minimum depth is updated to 100 blocks for coinbase funding transactions.
9142+
//
9143+
// Note that 0conf channels with coinbase funding transactions are unaffected and are
9144+
// immediately operational after opening.
9145+
let chanmon_cfgs = create_chanmon_cfgs(2);
9146+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
9147+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
9148+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
9149+
9150+
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
9151+
let open_channel = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
9152+
9153+
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel);
9154+
let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
9155+
9156+
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
9157+
9158+
// Create the coinbase funding transaction.
9159+
let (temporary_channel_id, tx, _) = create_coinbase_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100000, 42);
9160+
9161+
nodes[0].node.funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
9162+
check_added_monitors!(nodes[0], 0);
9163+
let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
9164+
9165+
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &funding_created);
9166+
check_added_monitors!(nodes[1], 1);
9167+
expect_channel_pending_event(&nodes[1], &nodes[0].node.get_our_node_id());
9168+
9169+
let funding_signed = get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id());
9170+
9171+
nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &funding_signed);
9172+
check_added_monitors!(nodes[0], 1);
9173+
9174+
expect_channel_pending_event(&nodes[0], &nodes[1].node.get_our_node_id());
9175+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
9176+
9177+
// Starting at height 0, we "confirm" the coinbase at height 1.
9178+
confirm_transaction_at(&nodes[0], &tx, 1);
9179+
// We connect 98 more blocks to have 99 confirmations for the coinbase transaction.
9180+
connect_blocks(&nodes[0], COINBASE_MATURITY - 2);
9181+
// Check that we have no pending message events (we have not queued a `channel_ready` yet).
9182+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
9183+
// Now connect one more block which results in 100 confirmations of the coinbase transaction.
9184+
connect_blocks(&nodes[0], 1);
9185+
// There should now be a `channel_ready` which can be handled.
9186+
let _ = &nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &get_event_msg!(&nodes[0], MessageSendEvent::SendChannelReady, nodes[1].node.get_our_node_id()));
9187+
9188+
confirm_transaction_at(&nodes[1], &tx, 1);
9189+
connect_blocks(&nodes[1], COINBASE_MATURITY - 2);
9190+
assert!(nodes[1].node.get_and_clear_pending_msg_events().is_empty());
9191+
connect_blocks(&nodes[1], 1);
9192+
expect_channel_ready_event(&nodes[1], &nodes[0].node.get_our_node_id());
9193+
create_chan_between_nodes_with_value_confirm_second(&nodes[0], &nodes[1]);
9194+
}
9195+
91369196
fn do_test_tx_confirmed_skipping_blocks_immediate_broadcast(test_height_before_timelock: bool) {
91379197
// In the first version of the chain::Confirm interface, after a refactor was made to not
91389198
// broadcast CSV-locked transactions until their CSV lock is up, we wouldn't reliably broadcast

0 commit comments

Comments
 (0)