-
Notifications
You must be signed in to change notification settings - Fork 358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement accepting dual-funded channels without contributing #3137
base: main
Are you sure you want to change the base?
Implement accepting dual-funded channels without contributing #3137
Conversation
b981cd7
to
0caf60e
Compare
0caf60e
to
44821af
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3137 +/- ##
==========================================
- Coverage 89.63% 88.66% -0.98%
==========================================
Files 126 126
Lines 102668 105124 +2456
Branches 102668 105124 +2456
==========================================
+ Hits 92028 93205 +1177
- Misses 7919 9261 +1342
+ Partials 2721 2658 -63 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, mostly localized code improvements suggested.
As I see, the |
Looks to me that #2989 is in fact included in this PR (good!) |
0db700b
to
7dceedd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial comments, sorry this took so long to get back to.
lightning/src/ln/channelmanager.rs
Outdated
|
||
if chan.interactive_tx_signing_session.is_some() { | ||
let monitor = try_chan_phase_entry!(self, | ||
chan.commitment_signed_initial_v2(&msg, best_block, &self.signer_provider, &&logger), chan_phase_entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh...So the spec changed the definition of commitment_signed
to now also include the initial funding commitment tx signing? Even though it implies other things inside the interactive state machine? Is it too late to change this? This seems nuts.
Does this mean that we should support adding HTLCs prior to the initial commitment transaction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous things it implies should really be looked at in the spec and changed there. We don't want to support adding HTLCs prior to initial commitment. So I really need to investigate what is going on with the spec and what might need to be fixed there.
lightning/src/ln/channelmanager.rs
Outdated
if chan.interactive_tx_signing_session.is_some() { | ||
let monitor = try_chan_phase_entry!(self, | ||
chan.commitment_signed_initial_v2(&msg, best_block, &self.signer_provider, &&logger), chan_phase_entry); | ||
if let Ok(persist_status) = self.chain_monitor.watch_channel(chan.context.get_funding_txo().unwrap(), monitor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the persist status is pending we need to handle the later stuff in monitor_updating_restored
. Really the whole contents of the block here should be in monitor_updating_restored
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @TheBlueMatt, for review and some good points raised. I'll address these ASAP ❤️
7dceedd
to
64c35f4
Compare
Still working on remaining initial feedback. |
64c35f4
to
a46da5a
Compare
No rush, let me know when you want another pass. |
0613f64
to
93896c4
Compare
6b7c977
to
5febfc9
Compare
5febfc9
to
8e70429
Compare
I see there is still a lot of potential DRYing up to do here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few quick questions.
@@ -4480,6 +4759,105 @@ impl<SP: Deref> Channel<SP> where | |||
Ok(()) | |||
} | |||
|
|||
pub fn commitment_signed_initial_v2<L: Deref>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be on OutboundChannelV2
since its basically the funding_signed
equivalent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we are just adding it for inbound V2 channels, but actually, eventually outbound will also need it once that's enabled (https://github.com/lightning/bolts/blob/master/02-peer-protocol.md#the-commitment_signed-message), and I'll probably share the behaviour via InteractivelyFunded
trait or something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll want to handle it differently in that case, though, no? The initial commitment signed here is handled ~identical to funding_signed
in OutboundV1Channel
, which implies we need to DRY this up somewhat (by moving it to ChannelContext
), but actually exposing it seems like something we should do on the type so that V1 and V2 work the same as each other as much as possible.
Moving this to Channel
is a pretty substantial difference - we're now moving from the Outbound
logic to the Channel
/funded logic at a different point in the channel's lifecycle, which I'll be honest I don't really want to have to review.
8e70429
to
fba77f2
Compare
fba77f2
to
9b46d1f
Compare
pub fn funding_tx_constructed<L: Deref>( | ||
&mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L | ||
) -> Result<(msgs::CommitmentSigned, Option<Event>), ChannelError> | ||
where | ||
L::Target: Logger | ||
{ | ||
let (commitment_signed, funding_ready_for_sig_event) = match self.internal_funding_tx_constructed( | ||
signing_session, logger, | ||
) { | ||
Ok(res) => res, | ||
Err(err) => return Err(err), | ||
}; | ||
|
||
Ok((commitment_signed, funding_ready_for_sig_event)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire method body can be replaced with:
self.internal_funding_tx_constructed(signing_session, logger)
So better to just drop this method and rename internal_funding_tx_constructed
to funding_tx_constructed
?
@@ -8756,6 +9023,85 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) | |||
ret | |||
} | |||
|
|||
fn get_initial_counterparty_commitment_signature<SP:Deref, L: Deref>( | |||
context: &mut ChannelContext<SP>, logger: &L |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context
doesn't need to be a &mut
here.
@@ -8756,6 +9023,85 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) | |||
ret | |||
} | |||
|
|||
fn get_initial_counterparty_commitment_signature<SP:Deref, L: Deref>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to make this a method on ChannelContext
?
Likewise for get_initial_commitment_signed
. At least there is a get_funding_signed_msg
there already.
Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) | ||
.map_err(|_| ChannelError::Close( | ||
( | ||
"Failed to get signatures for new commitment_signed".to_owned(), | ||
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, | ||
)))?.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A little more idiomatic and readable to map
rather than use ?.0
and re-wrap.
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index d9fe0c5dd..dfa87a319 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -9053,12 +9021,13 @@ where
match context.holder_signer {
// TODO (taproot|arik): move match into calling method for Taproot
ChannelSignerType::Ecdsa(ref ecdsa) => {
- Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx)
+ ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx)
+ .map(|(signature, _)| signature)
.map_err(|_| ChannelError::Close(
(
"Failed to get signatures for new commitment_signed".to_owned(),
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) },
- )))?.0)
+ )))
},
// TODO (taproot|arik)
#[cfg(taproot)]
/// Handles a signed funding transaction generated by interactive transaction construction and | ||
/// provided by the client. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't really clear from the docs when a user would call this and where transaction
comes from. Or at least the wording is confusing to me.
lightning/src/ln/channelmanager.rs
Outdated
match chan_phase_entry.get_mut() { | ||
ChannelPhase::UnfundedOutboundV2(chan) => { | ||
chan.funding_tx_constructed(&mut signing_session, &self.logger) | ||
}, | ||
ChannelPhase::UnfundedInboundV2(chan) => { | ||
chan.funding_tx_constructed(&mut signing_session, &self.logger) | ||
}, | ||
_ => Err(ChannelError::Warn( | ||
"Got a tx_complete message with no interactive transaction construction expected or in-progress" | ||
.into())), | ||
}.and_then(|(commitment_signed, funding_ready_for_sig_event_opt)| { | ||
let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); | ||
match channel_phase { | ||
ChannelPhase::UnfundedOutboundV2(chan) => { | ||
chan.into_channel(signing_session) | ||
}, | ||
ChannelPhase::UnfundedInboundV2(chan) => { | ||
chan.into_channel(signing_session) | ||
}, | ||
_ => { | ||
debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. | ||
Err(ChannelError::Warn( | ||
"Got a tx_complete message with no interactive transaction construction expected or in-progress" | ||
.into())) | ||
} | ||
}.map(|channel| (channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed)) | ||
}).map(|(channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed)| { | ||
peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel)); | ||
if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { | ||
let mut pending_events = self.pending_events.lock().unwrap(); | ||
pending_events.push_back((funding_ready_for_sig_event, None)); | ||
} | ||
peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { | ||
node_id: counterparty_node_id, | ||
updates: CommitmentUpdate { | ||
commitment_signed, | ||
update_add_htlcs: vec![], | ||
update_fulfill_htlcs: vec![], | ||
update_fail_htlcs: vec![], | ||
update_fail_malformed_htlcs: vec![], | ||
update_fee: None, | ||
}, | ||
}); | ||
}).map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When possible, it's best to avoid side-effects inside map
and and_then
calls. It would make the code easier to follow and less indented. Consider the following:
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index b23bd08ff..c2e67287a 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -7955,7 +7955,7 @@ where
};
if let Some(mut signing_session) = signing_session_opt {
let funding_txid = signing_session.unsigned_tx.txid();
- match chan_phase_entry.get_mut() {
+ let (commitment_signed, funding_ready_for_sig_event_opt) = match chan_phase_entry.get_mut() {
ChannelPhase::UnfundedOutboundV2(chan) => {
chan.funding_tx_constructed(&mut signing_session, &self.logger)
},
@@ -7965,45 +7965,39 @@ where
_ => Err(ChannelError::Warn(
"Got a tx_complete message with no interactive transaction construction expected or in-progress"
.into())),
- }.and_then(|(commitment_signed, funding_ready_for_sig_event_opt)| {
- let (channel_id, channel_phase) = chan_phase_entry.remove_entry();
- match channel_phase {
- ChannelPhase::UnfundedOutboundV2(chan) => {
- chan.into_channel(signing_session)
- },
- ChannelPhase::UnfundedInboundV2(chan) => {
- chan.into_channel(signing_session)
- },
- _ => {
- debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match.
- Err(ChannelError::Warn(
- "Got a tx_complete message with no interactive transaction construction expected or in-progress"
- .into()))
- }
- }.map(|channel| (channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed))
- }).map(|(channel_id, mut channel, funding_ready_for_sig_event_opt, commitment_signed)| {
- channel.set_next_funding_txid(&funding_txid);
- peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel));
- if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt {
- let mut pending_events = self.pending_events.lock().unwrap();
- pending_events.push_back((funding_ready_for_sig_event, None));
- }
- peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
- node_id: counterparty_node_id,
- updates: CommitmentUpdate {
- commitment_signed,
- update_add_htlcs: vec![],
- update_fulfill_htlcs: vec![],
- update_fail_htlcs: vec![],
- update_fail_malformed_htlcs: vec![],
- update_fee: None,
- },
- });
- }).map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))
- } else {
- // We're not in a signing session yet so we don't need to do anything else.
- Ok(())
+ }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?;
+
+ let (channel_id, channel_phase) = chan_phase_entry.remove_entry();
+ let mut channel = match channel_phase {
+ ChannelPhase::UnfundedOutboundV2(chan) => chan.into_channel(signing_session),
+ ChannelPhase::UnfundedInboundV2(chan) => chan.into_channel(signing_session),
+ _ => {
+ debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match.
+ Err(ChannelError::Warn(
+ "Got a tx_complete message with no interactive transaction construction expected or in-progress"
+ .into()))
+ },
+ }.map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id))?;
+
+ channel.set_next_funding_txid(&funding_txid);
+ peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel));
+ if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt {
+ let mut pending_events = self.pending_events.lock().unwrap();
+ pending_events.push_back((funding_ready_for_sig_event, None));
+ }
+ peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs {
+ node_id: counterparty_node_id,
+ updates: CommitmentUpdate {
+ commitment_signed,
+ update_add_htlcs: vec![],
+ update_fulfill_htlcs: vec![],
+ update_fail_htlcs: vec![],
+ update_fail_malformed_htlcs: vec![],
+ update_fee: None,
+ },
+ });
}
+ Ok(())
},
hash_map::Entry::Vacant(_) => {
Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id))
@@ -9530,7 +9542,8 @@ impl<SP: Deref> Writeable for Channel<SP> where SP::Target: SignerProvider { | |||
(47, next_holder_commitment_point, option), | |||
(49, self.context.local_initiated_shutdown, option), // Added in 0.0.122 | |||
(51, is_manual_broadcast, option), // Added in 0.0.124 | |||
(53, funding_tx_broadcast_safe_event_emitted, option) // Added in 0.0.124 | |||
(53, funding_tx_broadcast_safe_event_emitted, option), // Added in 0.0.124 | |||
(55, self.context.next_funding_txid, option) // Added in 0.0.125 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add this to the ReadableArgs
implementation as discussed offline.
lightning/src/ln/mod.rs
Outdated
@@ -96,7 +96,7 @@ mod async_signer_tests; | |||
#[cfg(test)] | |||
#[allow(unused_mut)] | |||
mod offers_tests; | |||
#[allow(dead_code)] // TODO(dual_funding): Exchange for dual_funding cfg | |||
#[allow(dead_code)] // TODO(splicing): Exchange for splicing cfg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the dead_code exception no longer needed. This change seems to be search/and/replace false positive.
Needs rebase now :( |
Sorry, catching up on everything now! Been mostly offline for a week. |
9b46d1f
to
2b65e1f
Compare
Just rebased for now while I'm still addressing past week's feedback. |
2b65e1f
to
6dc0b7c
Compare
We'll only gate public API related to contributing toward an inbound or opening a dual funded channel.
Here we add the `interactive_tx_constructor` field to the `Channel`, `OutboundV2Channel`, and `InboundV2Channel` structs.
6dc0b7c
to
2216120
Compare
We split this out from #2302 for easier review and to address the common non-public API parts of the V2 channel establishment implementation.
This will allow the holder to be an acceptor, but not initiator of V2 channels. We also don't expose an API for contributing to an inbound channel.
The functionality to initiate V2 channels and fund inbound channels forms part of #2302.