Skip to content

Commit

Permalink
Merge pull request #1860 from wpaulino/open-channel-anchors-support
Browse files Browse the repository at this point in the history
Support opening anchor channels and test end-to-end unilateral close
  • Loading branch information
TheBlueMatt authored Jan 19, 2023
2 parents ad40573 + 660165c commit 50d1260
Show file tree
Hide file tree
Showing 9 changed files with 525 additions and 74 deletions.
291 changes: 239 additions & 52 deletions lightning/src/ln/channel.rs

Large diffs are not rendered by default.

50 changes: 47 additions & 3 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4196,7 +4196,7 @@ where
let mut peer_state_lock = peer_state_mutex_opt.unwrap().lock().unwrap();
let peer_state = &mut *peer_state_lock;
let mut channel = match Channel::new_from_req(&self.fee_estimator, &self.entropy_source, &self.signer_provider,
counterparty_node_id.clone(), &peer_state.latest_features, msg, user_channel_id, &self.default_configuration,
counterparty_node_id.clone(), &self.channel_type_features(), &peer_state.latest_features, msg, user_channel_id, &self.default_configuration,
self.best_block.read().unwrap().height(), &self.logger, outbound_scid_alias)
{
Err(e) => {
Expand Down Expand Up @@ -6267,7 +6267,7 @@ pub(crate) fn provided_channel_features(config: &UserConfig) -> ChannelFeatures
/// Fetches the set of [`ChannelTypeFeatures`] flags which are provided by or required by
/// [`ChannelManager`].
pub(crate) fn provided_channel_type_features(config: &UserConfig) -> ChannelTypeFeatures {
ChannelTypeFeatures::from_counterparty_init(&provided_init_features(config))
ChannelTypeFeatures::from_init(&provided_init_features(config))
}

/// Fetches the set of [`InitFeatures`] flags which are provided by or required by
Expand All @@ -6288,6 +6288,12 @@ pub fn provided_init_features(_config: &UserConfig) -> InitFeatures {
features.set_channel_type_optional();
features.set_scid_privacy_optional();
features.set_zero_conf_optional();
#[cfg(anchors)]
{ // Attributes are not allowed on if expressions on our current MSRV of 1.41.
if _config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx {
features.set_anchors_zero_fee_htlc_tx_optional();
}
}
features
}

Expand Down Expand Up @@ -7027,7 +7033,9 @@ where
let mut short_to_chan_info = HashMap::with_capacity(cmp::min(channel_count as usize, 128));
let mut channel_closures = Vec::new();
for _ in 0..channel_count {
let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (&args.entropy_source, &args.signer_provider, best_block_height))?;
let mut channel: Channel<<SP::Target as SignerProvider>::Signer> = Channel::read(reader, (
&args.entropy_source, &args.signer_provider, best_block_height, &provided_channel_type_features(&args.default_config)
))?;
let funding_txo = channel.get_funding_txo().ok_or(DecodeError::InvalidValue)?;
funding_txo_set.insert(funding_txo.clone());
if let Some(ref mut monitor) = args.channel_monitors.get_mut(&funding_txo) {
Expand Down Expand Up @@ -8311,6 +8319,42 @@ mod tests {

nodes[1].node.handle_update_fee(&unkown_public_key, &update_fee_msg);
}

#[cfg(anchors)]
#[test]
fn test_anchors_zero_fee_htlc_tx_fallback() {
// Tests that if both nodes support anchors, but the remote node does not want to accept
// anchor channels at the moment, an error it sent to the local node such that it can retry
// the channel without the anchors feature.
let chanmon_cfgs = create_chanmon_cfgs(2);
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
let mut anchors_config = test_default_channel_config();
anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
anchors_config.manually_accept_inbound_channels = true;
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(anchors_config.clone()), Some(anchors_config.clone())]);
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);

nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 0, 0, None).unwrap();
let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
assert!(open_channel_msg.channel_type.as_ref().unwrap().supports_anchors_zero_fee_htlc_tx());

nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), &open_channel_msg);
let events = nodes[1].node.get_and_clear_pending_events();
match events[0] {
Event::OpenChannelRequest { temporary_channel_id, .. } => {
nodes[1].node.force_close_broadcasting_latest_txn(&temporary_channel_id, &nodes[0].node.get_our_node_id()).unwrap();
}
_ => panic!("Unexpected event"),
}

let error_msg = get_err_msg!(nodes[1], nodes[0].node.get_our_node_id());
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &error_msg);

let open_channel_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
assert!(!open_channel_msg.channel_type.unwrap().supports_anchors_zero_fee_htlc_tx());

check_closed_event!(nodes[1], 1, ClosureReason::HolderForceClosed);
}
}

#[cfg(all(any(test, feature = "_test_utils"), feature = "_bench_unstable"))]
Expand Down
56 changes: 47 additions & 9 deletions lightning/src/ln/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
//! (see [BOLT-2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md) for more information).
//! - `Keysend` - send funds to a node without an invoice
//! (see the [`Keysend` feature assignment proposal](https://github.com/lightning/bolts/issues/605#issuecomment-606679798) for more information).
//! - `AnchorsZeroFeeHtlcTx` - requires/supports that commitment transactions include anchor outputs
//! and HTLC transactions are pre-signed with zero fee (see
//! [BOLT-3](https://github.com/lightning/bolts/blob/master/03-transactions.md) for more
//! information).
//!
//! [BOLT #9]: https://github.com/lightning/bolts/blob/master/09-features.md
//! [messages]: crate::ln::msgs
Expand Down Expand Up @@ -122,7 +126,7 @@ mod sealed {
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
BasicMPP | Wumbo,
BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
Expand All @@ -138,7 +142,7 @@ mod sealed {
// Byte 1
VariableLengthOnion | StaticRemoteKey | PaymentSecret,
// Byte 2
BasicMPP | Wumbo,
BasicMPP | Wumbo | AnchorsZeroFeeHtlcTx,
// Byte 3
ShutdownAnySegwit,
// Byte 4
Expand Down Expand Up @@ -176,7 +180,7 @@ mod sealed {
// Byte 1
StaticRemoteKey,
// Byte 2
,
AnchorsZeroFeeHtlcTx,
// Byte 3
,
// Byte 4
Expand Down Expand Up @@ -357,6 +361,9 @@ mod sealed {
define_feature!(19, Wumbo, [InitContext, NodeContext],
"Feature flags for `option_support_large_channel` (aka wumbo channels).", set_wumbo_optional, set_wumbo_required,
supports_wumbo, requires_wumbo);
define_feature!(23, AnchorsZeroFeeHtlcTx, [InitContext, NodeContext, ChannelTypeContext],
"Feature flags for `option_anchors_zero_fee_htlc_tx`.", set_anchors_zero_fee_htlc_tx_optional,
set_anchors_zero_fee_htlc_tx_required, supports_anchors_zero_fee_htlc_tx, requires_anchors_zero_fee_htlc_tx);
define_feature!(27, ShutdownAnySegwit, [InitContext, NodeContext],
"Feature flags for `opt_shutdown_anysegwit`.", set_shutdown_any_segwit_optional,
set_shutdown_any_segwit_required, supports_shutdown_anysegwit, requires_shutdown_anysegwit);
Expand Down Expand Up @@ -505,10 +512,10 @@ impl InvoiceFeatures {
}

impl ChannelTypeFeatures {
/// Constructs the implicit channel type based on the common supported types between us and our
/// counterparty
pub(crate) fn from_counterparty_init(counterparty_init: &InitFeatures) -> Self {
let mut ret = counterparty_init.to_context_internal();
// Maps the relevant `InitFeatures` to `ChannelTypeFeatures`. Any unknown features to
// `ChannelTypeFeatures` are not included in the result.
pub(crate) fn from_init(init: &InitFeatures) -> Self {
let mut ret = init.to_context_internal();
// ChannelTypeFeatures must only contain required bits, so we OR the required forms of all
// optional bits and then AND out the optional ones.
for byte in ret.flags.iter_mut() {
Expand Down Expand Up @@ -678,6 +685,24 @@ impl<T: sealed::Context> Features<T> {
(byte & unknown_features) != 0
})
}

// Returns true if the features within `self` are a subset of the features within `other`.
pub(crate) fn is_subset(&self, other: &Self) -> bool {
for (idx, byte) in self.flags.iter().enumerate() {
if let Some(other_byte) = other.flags.get(idx) {
if byte & other_byte != *byte {
// `self` has bits set that `other` doesn't.
return false;
}
} else {
if *byte > 0 {
// `self` has a non-zero byte that `other` doesn't.
return false;
}
}
}
true
}
}

impl<T: sealed::UpfrontShutdownScript> Features<T> {
Expand All @@ -704,6 +729,18 @@ impl<T: sealed::Wumbo> Features<T> {
}
}

impl<T: sealed::SCIDPrivacy> Features<T> {
pub(crate) fn clear_scid_privacy(&mut self) {
<T as sealed::SCIDPrivacy>::clear_bits(&mut self.flags);
}
}

impl<T: sealed::AnchorsZeroFeeHtlcTx> Features<T> {
pub(crate) fn clear_anchors_zero_fee_htlc_tx(&mut self) {
<T as sealed::AnchorsZeroFeeHtlcTx>::clear_bits(&mut self.flags);
}
}

#[cfg(test)]
impl<T: sealed::UnknownFeature> Features<T> {
pub(crate) fn unknown() -> Self {
Expand Down Expand Up @@ -808,6 +845,7 @@ mod tests {
init_features.set_channel_type_optional();
init_features.set_scid_privacy_optional();
init_features.set_zero_conf_optional();
init_features.set_anchors_zero_fee_htlc_tx_optional();

assert!(init_features.initial_routing_sync());
assert!(!init_features.supports_upfront_shutdown_script());
Expand All @@ -826,7 +864,7 @@ mod tests {
assert_eq!(node_features.flags.len(), 7);
assert_eq!(node_features.flags[0], 0b00000010);
assert_eq!(node_features.flags[1], 0b01010001);
assert_eq!(node_features.flags[2], 0b00001010);
assert_eq!(node_features.flags[2], 0b10001010);
assert_eq!(node_features.flags[3], 0b00001000);
assert_eq!(node_features.flags[4], 0b10000000);
assert_eq!(node_features.flags[5], 0b10100000);
Expand Down Expand Up @@ -917,7 +955,7 @@ mod tests {
// required-StaticRemoteKey ChannelTypeFeatures.
let mut init_features = InitFeatures::empty();
init_features.set_static_remote_key_optional();
let converted_features = ChannelTypeFeatures::from_counterparty_init(&init_features);
let converted_features = ChannelTypeFeatures::from_init(&init_features);
assert_eq!(converted_features, ChannelTypeFeatures::only_static_remote_key());
assert!(!converted_features.supports_any_optional_bits());
assert!(converted_features.requires_static_remote_key());
Expand Down
16 changes: 13 additions & 3 deletions lightning/src/ln/functional_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,22 @@ pub fn confirm_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Tran
connect_blocks(node, CHAN_CONFIRM_DEPTH - 1);
scid
}
/// Mine a signle block containing the given transaction
/// Mine a single block containing the given transaction
pub fn mine_transaction<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction) {
let height = node.best_block_info().1 + 1;
confirm_transaction_at(node, tx, height);
}
/// Mine a single block containing the given transactions
pub fn mine_transactions<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction]) {
let height = node.best_block_info().1 + 1;
confirm_transactions_at(node, txn, height);
}
/// Mine the given transaction at the given height, mining blocks as required to build to that
/// height
///
/// Returns the SCID a channel confirmed in the given transaction will have, assuming the funding
/// output is the 1st output in the transaction.
pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) -> u64 {
pub fn confirm_transactions_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, txn: &[&Transaction], conf_height: u32) -> u64 {
let first_connect_height = node.best_block_info().1 + 1;
assert!(first_connect_height <= conf_height);
if conf_height > first_connect_height {
Expand All @@ -84,10 +89,15 @@ pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &T
for _ in 0..*node.network_chan_count.borrow() { // Make sure we don't end up with channels at the same short id by offsetting by chan_count
block.txdata.push(Transaction { version: 0, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() });
}
block.txdata.push(tx.clone());
for tx in txn {
block.txdata.push((*tx).clone());
}
connect_block(node, &block);
scid_utils::scid_from_parts(conf_height as u64, block.txdata.len() as u64 - 1, 0).unwrap()
}
pub fn confirm_transaction_at<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, tx: &Transaction, conf_height: u32) -> u64 {
confirm_transactions_at(node, &[tx], conf_height)
}

/// The possible ways we may notify a ChannelManager of a new block
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down
4 changes: 2 additions & 2 deletions lightning/src/ln/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6861,7 +6861,7 @@ fn test_user_configurable_csv_delay() {
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
if let Err(error) = Channel::new_from_req(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &open_channel, 0,
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
&low_our_to_self_config, 0, &nodes[0].logger, 42)
{
match error {
Expand Down Expand Up @@ -6893,7 +6893,7 @@ fn test_user_configurable_csv_delay() {
let mut open_channel = get_event_msg!(nodes[1], MessageSendEvent::SendOpenChannel, nodes[0].node.get_our_node_id());
open_channel.to_self_delay = 200;
if let Err(error) = Channel::new_from_req(&LowerBoundedFeeEstimator::new(&test_utils::TestFeeEstimator { sat_per_kw: Mutex::new(253) }),
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[1].node.init_features(), &open_channel, 0,
&nodes[0].keys_manager, &nodes[0].keys_manager, nodes[1].node.get_our_node_id(), &nodes[0].node.channel_type_features(), &nodes[1].node.init_features(), &open_channel, 0,
&high_their_to_self_config, 0, &nodes[0].logger, 42)
{
match error {
Expand Down
Loading

0 comments on commit 50d1260

Please sign in to comment.