From 9394b18b4e87efb4a0de004d2ac8461a3d8ee416 Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 18 Feb 2025 15:16:11 +0100 Subject: [PATCH] add method list_enabled_ln_feature_bits --- electrum/lnutil.py | 21 +++++++++++++++++---- tests/test_lnutil.py | 15 +++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 1ad1703ae..0cc951b41 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -1468,28 +1468,28 @@ class LnFeatures(IntFlag): def for_init_message(self) -> 'LnFeatures': features = LnFeatures(0) - for flag in list_enabled_bits(self): + for flag in list_enabled_ln_feature_bits(self): if LnFeatureContexts.INIT & _ln_feature_contexts[1 << flag]: features |= (1 << flag) return features def for_node_announcement(self) -> 'LnFeatures': features = LnFeatures(0) - for flag in list_enabled_bits(self): + for flag in list_enabled_ln_feature_bits(self): if LnFeatureContexts.NODE_ANN & _ln_feature_contexts[1 << flag]: features |= (1 << flag) return features def for_invoice(self) -> 'LnFeatures': features = LnFeatures(0) - for flag in list_enabled_bits(self): + for flag in list_enabled_ln_feature_bits(self): if LnFeatureContexts.INVOICE & _ln_feature_contexts[1 << flag]: features |= (1 << flag) return features def for_channel_announcement(self) -> 'LnFeatures': features = LnFeatures(0) - for flag in list_enabled_bits(self): + for flag in list_enabled_ln_feature_bits(self): ctxs = _ln_feature_contexts[1 << flag] if LnFeatureContexts.CHAN_ANN_AS_IS & ctxs: features |= (1 << flag) @@ -1627,6 +1627,19 @@ def get_ln_flag_pair_of_bit(flag_bit: int) -> int: return flag_bit - 1 +def list_enabled_ln_feature_bits(features: int) -> tuple[int, ...]: + """Returns a list of enabled feature bits. If both opt and req are set, only + req will be included in the result.""" + all_enabled_bits = list_enabled_bits(features) + single_feature_bits: set[int] = set() + for bit in all_enabled_bits: + if bit % 2 == 0: # even bit, always added + single_feature_bits.add(bit) + elif bit - 1 not in single_feature_bits: + # add if we haven't already added the corresponding req (even) bit + single_feature_bits.add(bit) + return tuple(sorted(single_feature_bits)) + class IncompatibleOrInsaneFeatures(Exception): pass class UnknownEvenFeatureBits(IncompatibleOrInsaneFeatures): pass diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py index 79097fff4..561609601 100644 --- a/tests/test_lnutil.py +++ b/tests/test_lnutil.py @@ -7,15 +7,18 @@ import electrum_ecc as ecc from electrum import bitcoin from electrum.json_db import StoredDict -from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_seed, make_offered_htlc, - make_received_htlc, make_commitment, make_htlc_tx_witness, make_htlc_tx_output, - make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, derive_privkey, +from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_seed, + make_offered_htlc, + make_received_htlc, make_commitment, make_htlc_tx_witness, + make_htlc_tx_output, + make_htlc_tx_inputs, secret_to_pubkey, derive_blinded_pubkey, + derive_privkey, derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret, get_compressed_pubkey_from_bech32, ScriptHtlc, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, ln_compare_features, IncompatibleLightningFeatures, ChannelType, offered_htlc_trim_threshold_sat, received_htlc_trim_threshold_sat, - ImportedChannelBackupStorage) + ImportedChannelBackupStorage, list_enabled_ln_feature_bits) from electrum.util import bfh, MyEncoder from electrum.transaction import Transaction, PartialTransaction, Sighash from electrum.lnworker import LNWallet @@ -1008,6 +1011,10 @@ class TestLNUtil(ElectrumTestCase): LnFeatures.VAR_ONION_OPT, ln_compare_features(f2, f1)) + def test_list_enabled_ln_feature_bits(self): + self.assertEqual((0, 2, 6), list_enabled_ln_feature_bits(77)) + self.assertEqual((), list_enabled_ln_feature_bits(0)) + def test_ln_features_supports(self): f_null = LnFeatures(0) f_opt = LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT