Skip to content

Batch funding #2486

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

Merged
merged 3 commits into from
Sep 26, 2023
Merged

Conversation

wvanlint
Copy link
Contributor

@wvanlint wvanlint commented Aug 9, 2023

Implements batch funding:

  • A batch funding transaction can be provided to ChannelManager, together with the temporary channel IDs it is funding.
  • The state of the batch will be maintained by the ChannelManager, tracking whether channels have received funding_signed and whether the monitor is persisted.
  • Channels will have an additional state flag to indicate that funding_signed has been received but broadcasting the funding transaction is held until all channels in the batch have received funding_signed and have their monitors persisted. This state is cleared by ChannelManager at the appropriate time.
  • No persistence is necessary as funding nodes can forget channels if they have not broadcast the corresponding funding transaction.
  • When a channel in a batch closes or a peer disconnects, this propagates to the other channels in the batch.
  • To avoid deadlocks, locks are acquired in the same order. The newly added locks are held at the innermost level, with locks around the state of the batch being acquired before locks around batches being closed.

Fixes #1510.

@wvanlint wvanlint force-pushed the batch_channel_opens branch 2 times, most recently from cc00689 to ac5c8c5 Compare August 9, 2023 20:33
@wvanlint wvanlint marked this pull request as ready for review August 9, 2023 23:07
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't as bad as I thought it would be, but is definitely gonna require re-reading most of channel and channelmanager to convince myself its correct :)

/// Flag which is set on `FundingSent` to indicate this channel is funded in a batch and the
/// broadcasting of the funding transaction is being held until all channels in the batch
/// have received funding_signed and have their monitors persisted.
WaitingForBatch = 1 << 13,
}
const BOTH_SIDES_SHUTDOWN_MASK: u32 = ChannelState::LocalShutdownSent as u32 | ChannelState::RemoteShutdownSent as u32;
const MULTI_STATE_FLAGS: u32 = BOTH_SIDES_SHUTDOWN_MASK | ChannelState::PeerDisconnected as u32 | ChannelState::MonitorUpdateInProgress as u32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the new state should also be added here, but we'll need test coverage - can you add a test that checks for receiving our peer's channel_ready on a channel which is currently WaitingForBatch? This is basically the peer accepting the channel 0conf.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WaitingForBatch flag is not a MULTI_STATE_FLAG I believe as it is only set on FundingSent? I did a pass through all the uses of FundingSent in channel.rs and added tests for the channel_ready behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yea, the MULTI_STATE_FLAGS thing is a bit of a misnomer, I think, it maybe should just be NON_STATE_FLAGS - the channel_state is really the OR of a state (the initial states and then ChannelReady) and some flags. MULTI_STATE_FLAGS lists the flags. But, eg, is monitor_updating_restored correct - below the funding_broadcastable setting we set it back to None if channel_state & !MULTI_STATE_FLAGS >= ChannelReady (which is true if WaitingForBatch is set even if we're not ChannelReady). Similar in do_best_block_updated. Still, to your point, if we do add it to MULTI_STATE_FLAGS I think we'll probably break check_get_channel_ready (we'll need to also skip the send if we are in WaitingForBatch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems there are a couple of use cases:

  • A mask of flags that can be applied to multiple states and should be inherited when transitioning between states. MULTI_STATE_FLAGS seems to fulfill that purpose for the *ShutdownSent, PeerDisconnected, MonitorUpdateInProgress flags.
  • A mask of all flags that can be applied to only look at the sequential states and compare them. I added a superset of MULTI_STATE_FLAGS called STATE_FLAGS here.

I went through all uses of Channel::channel_state, verified the usage and adjusted any < or > comparisons to occur against self.channel_state & !STATE_FLAGS.

@wvanlint wvanlint force-pushed the batch_channel_opens branch from ac5c8c5 to 6ce3713 Compare August 15, 2023 00:16
@codecov-commenter
Copy link

codecov-commenter commented Aug 15, 2023

Codecov Report

Attention: 44 lines in your changes are missing coverage. Please review.

Comparison is base (20e1c27) 88.86% compared to head (7b013b5) 88.91%.
Report is 1 commits behind head on main.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2486      +/-   ##
==========================================
+ Coverage   88.86%   88.91%   +0.05%     
==========================================
  Files         113      113              
  Lines       84517    85131     +614     
  Branches    84517    85131     +614     
==========================================
+ Hits        75103    75696     +593     
- Misses       7211     7222      +11     
- Partials     2203     2213      +10     
Files Coverage Δ
lightning/src/ln/reload_tests.rs 96.24% <100.00%> (+0.19%) ⬆️
lightning/src/ln/functional_test_utils.rs 91.43% <98.43%> (+0.27%) ⬆️
lightning/src/events/mod.rs 29.98% <0.00%> (+0.24%) ⬆️
lightning/src/ln/functional_tests.rs 97.30% <98.22%> (-0.03%) ⬇️
lightning/src/ln/channel.rs 88.32% <95.69%> (+0.27%) ⬆️
lightning/src/ln/channelmanager.rs 81.60% <81.93%> (+0.04%) ⬆️

... and 5 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wvanlint wvanlint force-pushed the batch_channel_opens branch from 6ce3713 to e26ba06 Compare August 15, 2023 18:00
@wvanlint wvanlint requested a review from TheBlueMatt August 15, 2023 18:10
Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current approach of dropping the whole batch is the safest one from a malleability viewpoint, yet once the transaction is counter-signed by all counterparties and broadcast, malleability is no more an issue if we can confirm the transaction.

I think there are intersections here with dual-funding/splicing and per-counterparty negotiated option_zeroconf, at least in term of API.

@TheBlueMatt
Copy link
Collaborator

I think the current approach of dropping the whole batch is the safest one from a malleability viewpoint, yet once the transaction is counter-signed by all counterparties and broadcast, malleability is no more an issue if we can confirm the transaction.

Not sure what you're referring to? Are you referring to some specific above discussion or just stating that we need to drop the batch if some output doesn't get a counter-signature?

@wvanlint wvanlint force-pushed the batch_channel_opens branch 3 times, most recently from bceda6a to 6761bc6 Compare August 17, 2023 23:54
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, thanks for all the work here, I really like the overall patch, but a handful of comments on minor tweaks for readability. I'd also love to see more test coverage of some edge cases, eg what happens if a peer sends us a channel_ready before we've broadcasted while we're waiting on a batch, a test of the initial monitor update(s) completing async, and a test for shutting down with one of the two channels having had their initial monitor update persisted but not the other one.

/// Flag which is set on `FundingSent` to indicate this channel is funded in a batch and the
/// broadcasting of the funding transaction is being held until all channels in the batch
/// have received funding_signed and have their monitors persisted.
WaitingForBatch = 1 << 13,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do kinda wonder if we shouldn't put this up further and renumber later flags. We'd have to juggle it a bunch in the serialization logic so maybe not worth it, but there's a few places it'd simplify the code cause we could keep >= kinda things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, right, it would make the serialization logic more complex. I do wonder if this can be refactored at some point to model the ordered states and flags without bitwise logic.

@wvanlint
Copy link
Contributor Author

Expanded testing coverage which covers:

  • Asynchronous monitor persistence in ln::functional_tests::test_batch_channel_open
  • Peer disconnection in ln::functional_tests::test_disconnect_in_funding_batch
  • Early channel ready in ln::channel::tests::test_waiting_for_batch
  • Reloading the node with a partially succeeded funding batch in ln::reload_tests::test_reload_partial_funding_batch

@wvanlint wvanlint requested a review from TheBlueMatt August 24, 2023 18:01
@wvanlint wvanlint force-pushed the batch_channel_opens branch 3 times, most recently from 3baad70 to 54010cd Compare August 30, 2023 23:53
@wvanlint wvanlint requested a review from wpaulino August 31, 2023 00:22
@wvanlint wvanlint requested a review from wpaulino September 12, 2023 23:36
@wvanlint wvanlint force-pushed the batch_channel_opens branch 2 times, most recently from 25d5504 to 6d40a8f Compare September 19, 2023 00:16
wpaulino
wpaulino previously approved these changes Sep 22, 2023
Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, I'd like to land this monday for 0.0.117 (later probably we can't make the release 😭), and am happy with the channel.rs changes and most of channelmanager, just want to see the lockorder inverted so we can simplify the potential for races.

@wvanlint wvanlint force-pushed the batch_channel_opens branch 2 times, most recently from 535c19e to 7816cf6 Compare September 26, 2023 06:22
@wvanlint
Copy link
Contributor Author

wvanlint commented Sep 26, 2023

In the latest change, the lock order is now reversed, acquiring funding_batch_states locks before per_peer_state locks. This improves the locking behavior when making changes across the batch instead of minimizing the number of code changes.

It does require a central place to propagate closures of channels to the remaining channels in the batch. Previously, this was done in update_maps_on_chan_removal, but that macro assumes per_peer_state locks are held. Instead, ChannelManager::finish_force_close_channel was extended as a method to handle all channel closures as it enforces no per_peer_state locks are held after #2597. ShutdownResult was extended as well, and is now sometimes constructed in ChannelManager, but provides a step towards further unification of channel close handling and can be moved into Channel methods that move to Shutdown states as well.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor cleanups and assertions, still digging into test coverage.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, please squash the fixup.

This is a step towards more unified closing of channels, and provides a
place where the per_peer_state lock is not held.
@wvanlint
Copy link
Contributor Author

Squashed.

Copy link
Contributor

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we were able to get rid of the additional tracking map!

@@ -2632,7 +2632,7 @@ where
self.close_channel_internal(channel_id, counterparty_node_id, target_feerate_sats_per_1000_weight, shutdown_script)
}

fn finish_force_close_channel(&self, shutdown_res: ShutdownResult) {
fn finish_close_channel(&self, shutdown_res: ShutdownResult) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: also update the Finishing force-closure of channel... log below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in follow-up.

let events = nodes[0].node.get_and_clear_pending_events();
assert_eq!(events.len(), 1);
match events[0] {
Event::ChannelClosed { channel_id, .. } => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use check_closed_event?

Copy link
Contributor Author

@wvanlint wvanlint Sep 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, that function does not match exactly as there are multiple closure reasons (the root cause one and the batch closure). Tried to refactor this in the follow-up.

/// - An optional (counterparty_node_id, funding_txo, [`ChannelMonitorUpdate`]) tuple
/// - A list of HTLCs to fail back in the form of the (source, payment hash, and this channel's
/// counterparty_node_id and channel_id).
/// - An optional transaction id identifying a corresponding batch funding transaction.
pub(crate) type ShutdownResult = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a good time to make this a struct now, but not a blocker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in follow-up.

@@ -25,6 +25,7 @@ use crate::util::ser::{Writeable, ReadableArgs};
use crate::util::config::UserConfig;
use crate::util::string::UntrustedString;

use bitcoin::{PackedLockTime, Transaction, TxOut};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these are all unused now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in follow-up.

btree_map::Entry::Vacant(vacant) => Some(vacant.insert(Vec::new())),
}
});
for (channel_idx, &(temporary_channel_id, counterparty_node_id)) in temporary_channels.iter().enumerate() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: channel_idx is unused now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in follow-up.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets address any remaining feedback in a followup so we can land this.

/// Return values are identical to [`Self::funding_transaction_generated`], respective to
/// each individual channel and transaction output.
///
/// Do NOT broadcast the funding transaction yourself. This batch funding transcaction
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transcaction 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in follow-up 😅

@TheBlueMatt TheBlueMatt merged commit 3e1e393 into lightningdevkit:main Sep 26, 2023
@wvanlint wvanlint deleted the batch_channel_opens branch September 26, 2023 23:08
@wvanlint
Copy link
Contributor Author

Following up in #2613.

@wvanlint wvanlint changed the title Batch funding for v1 channel establishments Batch funding Oct 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Batch Channel Opens
5 participants