Skip to content

Commit

Permalink
f Add typed funding tx
Browse files Browse the repository at this point in the history
  • Loading branch information
TheBlueMatt authored and jbesraa committed Jun 18, 2024
1 parent 4a06790 commit 8a3dff0
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 54 deletions.
14 changes: 7 additions & 7 deletions lightning/src/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,8 @@ pub enum Event {
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
user_channel_id: u128,
/// Channel funding transaction
funding_tx: Transaction,
/// The outpoint of the channel's funding transaction.
funding_txo: OutPoint,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The `temporary_channel_id` this channel used to be known by during channel establishment.
Expand Down Expand Up @@ -1559,12 +1559,12 @@ impl Writeable for Event {
(4, responder, option),
});
},
&Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_tx, ref counterparty_node_id, ref former_temporary_channel_id} => {
&Event::FundingTxBroadcastSafe { ref channel_id, ref user_channel_id, ref funding_txo, ref counterparty_node_id, ref former_temporary_channel_id} => {
43u8.write(writer)?;
write_tlv_fields!(writer, {
(0, channel_id, required),
(2, user_channel_id, required),
(4, funding_tx, required),
(4, funding_txo, required),
(6, counterparty_node_id, required),
(8, former_temporary_channel_id, required),
});
Expand Down Expand Up @@ -2023,20 +2023,20 @@ impl MaybeReadable for Event {
43u8 => {
let mut channel_id = RequiredWrapper(None);
let mut user_channel_id = RequiredWrapper(None);
let mut funding_tx = RequiredWrapper(None);
let mut funding_txo = RequiredWrapper(None);
let mut counterparty_node_id = RequiredWrapper(None);
let mut former_temporary_channel_id = RequiredWrapper(None);
read_tlv_fields!(reader, {
(0, channel_id, required),
(2, user_channel_id, required),
(4, funding_tx, required),
(4, funding_txo, required),
(6, counterparty_node_id, required),
(8, former_temporary_channel_id, required)
});
Ok(Some(Event::FundingTxBroadcastSafe {
channel_id: channel_id.0.unwrap(),
user_channel_id: user_channel_id.0.unwrap(),
funding_tx: funding_tx.0.unwrap(),
funding_txo: funding_txo.0.unwrap(),
counterparty_node_id: counterparty_node_id.0.unwrap(),
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
}))
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1331,6 +1331,8 @@ pub(super) struct ChannelContext<SP: Deref> where SP::Target: SignerProvider {
counterparty_forwarding_info: Option<CounterpartyForwardingInfo>,

pub(crate) channel_transaction_parameters: ChannelTransactionParameters,
/// The transaction which funds this channel. Note that for manually-funded channels this may
/// be a dummy empty transaction.
funding_transaction: Option<Transaction>,
is_batch_funding: Option<()>,

Expand Down
128 changes: 87 additions & 41 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,32 @@ struct ClaimablePayment {
htlcs: Vec<ClaimableHTLC>,
}

enum FundingType {
Unchecked(OutPoint),
Checked(Transaction),
}

impl FundingType {
fn txid(&self) -> Txid {
match self {
FundingType::Unchecked(outp) => outp.txid,
FundingType::Checked(tx) => tx.txid(),
}
}

fn transaction_or_dummy(&self) -> Transaction {
match self {
FundingType::Unchecked(_) => Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: Vec::new(),
output: Vec::new(),
},
FundingType::Checked(tx) => tx.clone(),
}
}
}

/// Information about claimable or being-claimed payments
struct ClaimablePayments {
/// Map from payment hash to the payment data and any HTLCs which are to us and can be
Expand Down Expand Up @@ -2568,12 +2594,12 @@ macro_rules! send_channel_ready {
}}
}
macro_rules! emit_funding_tx_broadcast_safe_event {
($locked_events: expr, $channel: expr, $funding_tx: expr) => {
($locked_events: expr, $channel: expr, $funding_txo: expr) => {
if !$channel.context.funding_tx_broadcast_safe_event_emitted() {
$locked_events.push_back((events::Event::FundingTxBroadcastSafe {
channel_id: $channel.context.channel_id(),
user_channel_id: $channel.context.get_user_id(),
funding_tx: $funding_tx,
funding_txo: $funding_txo,
counterparty_node_id: $channel.context.get_counterparty_node_id(),
former_temporary_channel_id: $channel.context.temporary_channel_id().expect("Unreachable: FundingTxBroadcastSafe event feature added to channel establishment process in LDK v0.124.0 where this should never be None."),
}, None));
Expand Down Expand Up @@ -4250,7 +4276,7 @@ where

/// Handles the generation of a funding transaction, optionally (for tests) with a function
/// which checks the correctness of the funding transaction given the associated channel.
fn funding_transaction_generated_intern<FundingOutput: FnMut(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, &'static str>>(
fn funding_transaction_generated_intern<FundingOutput: FnMut(&OutboundV1Channel<SP>) -> Result<OutPoint, &'static str>>(
&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, is_batch_funding: bool,
mut find_funding_output: FundingOutput, is_manual_broadcast: bool,
) -> Result<(), APIError> {
Expand All @@ -4277,7 +4303,7 @@ where
let _: Result<(), _> = handle_error!(self, Err(err), counterparty);
Err($api_err)
} } }
match find_funding_output(&chan, &funding_transaction) {
match find_funding_output(&chan) {
Ok(found_funding_txo) => funding_txo = found_funding_txo,
Err(err) => {
let chan_err = ChannelError::close(err.to_owned());
Expand Down Expand Up @@ -4348,8 +4374,9 @@ where

#[cfg(test)]
pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> {
self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_, tx| {
Ok(OutPoint { txid: tx.txid(), index: output_index })
let txid = funding_transaction.txid();
self.funding_transaction_generated_intern(temporary_channel_id, counterparty_node_id, funding_transaction, false, |_| {
Ok(OutPoint { txid, index: output_index })
}, false)
}

Expand Down Expand Up @@ -4418,11 +4445,11 @@ where
/// [`Event::FundingTxBroadcastSafe`]: crate::events::Event::FundingTxBroadcastSafe
/// [`Event::ChannelClosed`]: crate::events::Event::ChannelClosed
/// [`ChannelManager::funding_transaction_generated`]: crate::ln::channelmanager::ChannelManager::funding_transaction_generated
pub fn unsafe_manual_funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding_transaction: Transaction) -> Result<(), APIError> {
pub fn unsafe_manual_funding_transaction_generated(&self, temporary_channel_id: &ChannelId, counterparty_node_id: &PublicKey, funding: OutPoint) -> Result<(), APIError> {
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);

let temporary_channels = &[(temporary_channel_id, counterparty_node_id)];
return self.batch_funding_transaction_generated_intern(temporary_channels, funding_transaction, true);
return self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Unchecked(funding));

}

Expand All @@ -4449,33 +4476,35 @@ where
}
}
}
result.and(self.batch_funding_transaction_generated_intern(temporary_channels, funding_transaction, false))
result.and(self.batch_funding_transaction_generated_intern(temporary_channels, FundingType::Checked(funding_transaction)))
}

fn batch_funding_transaction_generated_intern(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding_transaction: Transaction, is_manual_broadcast: bool) -> Result<(), APIError> {
fn batch_funding_transaction_generated_intern(&self, temporary_channels: &[(&ChannelId, &PublicKey)], funding: FundingType) -> Result<(), APIError> {
let mut result = Ok(());
if funding_transaction.output.len() > u16::max_value() as usize {
result = result.and(Err(APIError::APIMisuseError {
err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
}));
}
{
let height = self.best_block.read().unwrap().height;
// Transactions are evaluated as final by network mempools if their locktime is strictly
// lower than the next block height. However, the modules constituting our Lightning
// node might not have perfect sync about their blockchain views. Thus, if the wallet
// module is ahead of LDK, only allow one more block of headroom.
if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) &&
funding_transaction.lock_time.is_block_height() &&
funding_transaction.lock_time.to_consensus_u32() > height + 1
{
if let FundingType::Checked(funding_transaction) = &funding {
if funding_transaction.output.len() > u16::max_value() as usize {
result = result.and(Err(APIError::APIMisuseError {
err: "Funding transaction absolute timelock is non-final".to_owned()
err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
}));
}
{
let height = self.best_block.read().unwrap().height;
// Transactions are evaluated as final by network mempools if their locktime is strictly
// lower than the next block height. However, the modules constituting our Lightning
// node might not have perfect sync about their blockchain views. Thus, if the wallet
// module is ahead of LDK, only allow one more block of headroom.
if !funding_transaction.input.iter().all(|input| input.sequence == Sequence::MAX) &&
funding_transaction.lock_time.is_block_height() &&
funding_transaction.lock_time.to_consensus_u32() > height + 1
{
result = result.and(Err(APIError::APIMisuseError {
err: "Funding transaction absolute timelock is non-final".to_owned()
}));
}
}
}

let txid = funding_transaction.txid();
let txid = funding.txid();
let is_batch_funding = temporary_channels.len() > 1;
let mut funding_batch_states = if is_batch_funding {
Some(self.funding_batch_states.lock().unwrap())
Expand All @@ -4493,27 +4522,36 @@ where
btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())),
}
});
let is_manual_broadcast = match &funding {
FundingType::Checked(_) => false,
FundingType::Unchecked(_) => true,
};
for &(temporary_channel_id, counterparty_node_id) in temporary_channels {
result = result.and_then(|_| self.funding_transaction_generated_intern(
temporary_channel_id,
counterparty_node_id,
funding_transaction.clone(),
funding.transaction_or_dummy(),
is_batch_funding,
|chan, tx| {
|chan| {
let mut output_index = None;
let expected_spk = chan.context.get_funding_redeemscript().to_p2wsh();
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() {
if output_index.is_some() {
return Err("Multiple outputs matched the expected script and value");
let outpoint = match &funding {
FundingType::Checked(tx) => {
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == expected_spk && outp.value.to_sat() == chan.context.get_value_satoshis() {
if output_index.is_some() {
return Err("Multiple outputs matched the expected script and value");
}
output_index = Some(idx as u16);
}
}
output_index = Some(idx as u16);
}
}
if output_index.is_none() {
return Err("No output matched the script_pubkey and value in the FundingGenerationReady event");
}
let outpoint = OutPoint { txid: tx.txid(), index: output_index.unwrap() };
if output_index.is_none() {
return Err("No output matched the script_pubkey and value in the FundingGenerationReady event");
}
OutPoint { txid, index: output_index.unwrap() }
},
FundingType::Unchecked(outpoint) => outpoint.clone(),
};
if let Some(funding_batch_state) = funding_batch_state.as_mut() {
// TODO(dual_funding): We only do batch funding for V1 channels at the moment, but we'll probably
// need to fix this somehow to not rely on using the outpoint for the channel ID if we
Expand Down Expand Up @@ -6661,7 +6699,15 @@ where
if channel.context.is_manual_broadcast() {
log_info!(logger, "Not broadcasting funding transaction with txid {} as it is manually managed", tx.txid());
let mut pending_events = self.pending_events.lock().unwrap();
emit_funding_tx_broadcast_safe_event!(pending_events, channel, tx);
match channel.context.get_funding_txo() {
Some(funding_txo) => {
emit_funding_tx_broadcast_safe_event!(pending_events, channel, funding_txo.into_bitcoin_outpoint())
},
None => {
log_error!(logger, "Channel resumed without a funding txo, this should never happen!");
return (htlc_forwards, decode_update_add_htlcs);
}
};
} else {
log_info!(logger, "Broadcasting funding transaction with txid {}", tx.txid());
self.tx_broadcaster.broadcast_transactions(&[&tx]);
Expand Down
13 changes: 7 additions & 6 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3796,10 +3796,10 @@ fn test_unsafe_manual_funding_transaction_generated() {
let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);

let (temporary_channel_id, tx, _funding_output) = create_funding_tx_without_witness_data(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
let (temporary_channel_id, _, funding_outpoint) = create_funding_tx_without_witness_data(&nodes[0], &nodes[1].node.get_our_node_id(), 1_000_000, 42);
assert_eq!(temporary_channel_id, expected_temporary_channel_id);

assert!(nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).is_ok());
assert!(nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), funding_outpoint).is_ok());
let node_0_msg_events = nodes[0].node.get_and_clear_pending_msg_events();
match node_0_msg_events[0] {
MessageSendEvent::SendFundingCreated { ref node_id, .. } => {
Expand Down Expand Up @@ -11248,8 +11248,8 @@ fn test_funding_signed_event() {
let accept_channel = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());

nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), &accept_channel);
let (temporary_channel_id, tx, _) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100_000, 42);
nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), tx.clone()).unwrap();
let (temporary_channel_id, tx, funding_outpoint) = create_funding_transaction(&nodes[0], &nodes[1].node.get_our_node_id(), 100_000, 42);
nodes[0].node.unsafe_manual_funding_transaction_generated(&temporary_channel_id, &nodes[1].node.get_our_node_id(), funding_outpoint).unwrap();
check_added_monitors!(nodes[0], 0);

let funding_created = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
Expand All @@ -11263,8 +11263,9 @@ fn test_funding_signed_event() {
let events = &nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 2);
match &events[0] {
crate::events::Event::FundingTxBroadcastSafe { funding_tx, .. } => {
assert_eq!(funding_tx.txid(), funding_created.funding_txid);
crate::events::Event::FundingTxBroadcastSafe { funding_txo, .. } => {
assert_eq!(funding_txo.txid, funding_created.funding_txid);
assert_eq!(funding_txo.vout, funding_created.funding_output_index.into());
},
_ => panic!("Unexpected event"),
};
Expand Down

0 comments on commit 8a3dff0

Please sign in to comment.