From 85a45f9b1f80923864297ea150b25a17baf6af9e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 18 Dec 2025 16:09:49 +0000 Subject: [PATCH 1/2] lnutil: rm remnants of old unsupported original ANCHOR_OUTPUTS option --- electrum/lnutil.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/electrum/lnutil.py b/electrum/lnutil.py index e938cf3d2..01789eda5 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -1469,12 +1469,6 @@ class LnFeatures(IntFlag): _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_OPT] = (LNFC.INIT | LNFC.NODE_ANN) _ln_feature_contexts[OPTION_SUPPORT_LARGE_CHANNEL_REQ] = (LNFC.INIT | LNFC.NODE_ANN) - OPTION_ANCHOR_OUTPUTS_REQ = 1 << 20 - OPTION_ANCHOR_OUTPUTS_OPT = 1 << 21 - _ln_feature_direct_dependencies[OPTION_ANCHOR_OUTPUTS_OPT] = {OPTION_STATIC_REMOTEKEY_OPT} - _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_REQ] = (LNFC.INIT | LNFC.NODE_ANN) - _ln_feature_contexts[OPTION_ANCHOR_OUTPUTS_OPT] = (LNFC.INIT | LNFC.NODE_ANN) - OPTION_ANCHORS_ZERO_FEE_HTLC_REQ = 1 << 22 OPTION_ANCHORS_ZERO_FEE_HTLC_OPT = 1 << 23 _ln_feature_direct_dependencies[OPTION_ANCHORS_ZERO_FEE_HTLC_OPT] = {OPTION_STATIC_REMOTEKEY_OPT} @@ -1623,7 +1617,6 @@ class LnFeatures(IntFlag): class ChannelType(IntFlag): OPTION_LEGACY_CHANNEL = 0 OPTION_STATIC_REMOTEKEY = 1 << 12 - OPTION_ANCHOR_OUTPUTS = 1 << 20 OPTION_ANCHORS_ZERO_FEE_HTLC_TX = 1 << 22 OPTION_SCID_ALIAS = 1 << 46 OPTION_ZEROCONF = 1 << 50 @@ -1647,7 +1640,6 @@ class ChannelType(IntFlag): basic_type = self & ~(ChannelType.OPTION_SCID_ALIAS | ChannelType.OPTION_ZEROCONF) if basic_type not in [ ChannelType.OPTION_STATIC_REMOTEKEY, - ChannelType.OPTION_ANCHOR_OUTPUTS | ChannelType.OPTION_STATIC_REMOTEKEY, ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX | ChannelType.OPTION_STATIC_REMOTEKEY ]: raise ValueError("Channel type is not a valid flag combination.") From aab22a237bd4997878d9d90c9b472ea45f0ca7c3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 18 Dec 2025 16:43:19 +0000 Subject: [PATCH 2/2] ln: require LnFeatures.OPTION_CHANNEL_TYPE as bolts now mandate it This simplifies some code. following https://github.com/lightning/bolts/commit/9d456b1c4a6c8e05a6b5b5edbc6c10f7b4b8e4de --- electrum/lnpeer.py | 47 +++++++++++++++++++------------------------- electrum/lnutil.py | 5 +++-- electrum/lnworker.py | 1 + 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 55a591c46..e241e3f31 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -894,9 +894,6 @@ class Peer(Logger, EventListener): def is_shutdown_anysegwit(self): return self.features.supports(LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT) - def is_channel_type(self): - return self.features.supports(LnFeatures.OPTION_CHANNEL_TYPE_OPT) - def accepts_zeroconf(self): return self.features.supports(LnFeatures.OPTION_ZEROCONF_OPT) @@ -936,10 +933,11 @@ class Peer(Logger, EventListener): # flexibility to decide an address at closing time upfront_shutdown_script = b'' - if self.use_anchors(): + assert channel_type is not None + if channel_type & ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX: # anchors static_payment_key = self.lnworker.static_payment_key static_remotekey = None - else: + else: # static_remotekey assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY wallet = self.lnworker.wallet assert wallet.txin_type == 'p2wpkh' @@ -1055,13 +1053,12 @@ class Peer(Logger, EventListener): # Eclair accepts channel_type with that bit, but does not require it. # if option_channel_type is negotiated: MUST set channel_type - if self.is_channel_type(): - # if it includes channel_type: MUST set it to a defined type representing the type it wants. - open_channel_tlvs['channel_type'] = { - 'type': our_channel_type.to_bytes_minimal() - } + # if it includes channel_type: MUST set it to a defined type representing the type it wants. + open_channel_tlvs['channel_type'] = { + 'type': our_channel_type.to_bytes_minimal() + } - if self.use_anchors(): + if our_channel_type & ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX: multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_we_opened( funding_root_secret=self.lnworker.funding_root_keypair.privkey, remote_node_id_or_prefix=self.pubkey, @@ -1168,7 +1165,7 @@ class Peer(Logger, EventListener): initial_feerate_per_kw=feerate, config=self.network.config, peer_features=self.features, - has_anchors=self.use_anchors(), + channel_type=our_channel_type, ) # -> funding created @@ -1285,21 +1282,19 @@ class Peer(Logger, EventListener): channel_type = open_channel_tlvs.get('channel_type') if open_channel_tlvs else None # The receiving node MAY fail the channel if: # option_channel_type was negotiated but the message doesn't include a channel_type - if self.is_channel_type() and channel_type is None: + if channel_type is None: raise Exception("sender has advertised option_channel_type, but hasn't sent the channel type") # MUST fail the channel if it supports channel_type, # channel_type was set, and the type is not suitable. - elif self.is_channel_type() and channel_type is not None: + else: channel_type = ChannelType.from_bytes(channel_type['type'], byteorder='big').discard_unknown_and_check() if not channel_type.complies_with_features(self.features): raise Exception("sender has sent a channel type we don't support") + assert isinstance(channel_type, ChannelType) - if self.is_channel_type(): - is_zeroconf = bool(channel_type & ChannelType.OPTION_ZEROCONF) - if is_zeroconf and not self.network.config.ZEROCONF_TRUSTED_NODE.startswith(self.pubkey.hex()): - raise Exception(f"not accepting zeroconf from node {self.pubkey}") - else: - is_zeroconf = False + is_zeroconf = bool(channel_type & ChannelType.OPTION_ZEROCONF) + if is_zeroconf and not self.network.config.ZEROCONF_TRUSTED_NODE.startswith(self.pubkey.hex()): + raise Exception(f"not accepting zeroconf from node {self.pubkey}") if self.lnworker.has_recoverable_channels() and not is_zeroconf: # FIXME: we might want to keep the connection open @@ -1324,7 +1319,7 @@ class Peer(Logger, EventListener): self.logger.info(f"just-in-time opening fee: {channel_opening_fee} msat") pass - if self.use_anchors(): + if channel_type & ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX: multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_they_opened( funding_root_secret=self.lnworker.funding_root_keypair.privkey, remote_node_id_or_prefix=self.pubkey, @@ -1370,7 +1365,7 @@ class Peer(Logger, EventListener): initial_feerate_per_kw=feerate, config=self.network.config, peer_features=self.features, - has_anchors=self.use_anchors(), + channel_type=channel_type, ) channel_flags = ord(payload['channel_flags']) @@ -1390,12 +1385,10 @@ class Peer(Logger, EventListener): 'upfront_shutdown_script': { 'shutdown_scriptpubkey': local_config.upfront_shutdown_script }, + 'channel_type': { + 'type': channel_type.to_bytes_minimal(), + }, } - # The sender: if it sets channel_type: MUST set it to the channel_type from open_channel - if self.is_channel_type(): - accept_channel_tlvs['channel_type'] = { - 'type': channel_type.to_bytes_minimal() - } self.send_message( 'accept_channel', diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 01789eda5..ff33f3914 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -170,8 +170,9 @@ class ChannelConfig(StoredObject): initial_feerate_per_kw: int, config: 'SimpleConfig', peer_features: 'LnFeatures', - has_anchors: bool, + channel_type: 'ChannelType', ) -> None: + has_anchors = bool(channel_type & ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX) # first we validate the configs separately local_config.validate_params(funding_sat=funding_sat, config=config, peer_features=peer_features) remote_config.validate_params(funding_sat=funding_sat, config=config, peer_features=peer_features) @@ -1621,7 +1622,7 @@ class ChannelType(IntFlag): OPTION_SCID_ALIAS = 1 << 46 OPTION_ZEROCONF = 1 << 50 - def discard_unknown_and_check(self): + def discard_unknown_and_check(self) -> 'ChannelType': """Discards unknown flags and checks flag combination.""" flags = list_enabled_bits(self) known_channel_types = [] diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 9fefeb16b..dddf5350c 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -203,6 +203,7 @@ LNWALLET_FEATURES = ( | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT | LnFeatures.OPTION_SCID_ALIAS_OPT | LnFeatures.OPTION_SUPPORT_LARGE_CHANNEL_OPT + | LnFeatures.OPTION_CHANNEL_TYPE_REQ ) LNGOSSIP_FEATURES = (