diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py index 94af6269d..c138ffbf9 100644 --- a/electrum/gui/kivy/uix/dialogs/lightning_channels.py +++ b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -246,6 +246,7 @@ Builder.load_string(r''' warning: '' is_frozen_for_sending: False is_frozen_for_receiving: False + channel_type:'' BoxLayout: padding: '12dp', '12dp', '12dp', '12dp' spacing: '12dp' @@ -294,6 +295,9 @@ Builder.load_string(r''' BoxLabel: text: _('Frozen (for receiving)') value: str(root.is_frozen_for_receiving) + BoxLabel: + text: _('Channel type') + value: str(root.channel_type) Widget: size_hint: 1, 0.1 TopLabel: @@ -484,6 +488,7 @@ class ChannelDetailsPopup(Popup, Logger): self.warning = '' if self.app.wallet.lnworker.channel_db or self.app.wallet.lnworker.is_trampoline_peer(chan.node_id) else _('Warning') + ': ' + msg self.is_frozen_for_sending = chan.is_frozen_for_sending() self.is_frozen_for_receiving = chan.is_frozen_for_receiving() + self.channel_type = chan.storage['channel_type'].name_minimal self.update_action_dropdown() def update_action_dropdown(self): diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py index 2d440fae3..1f787ba09 100644 --- a/electrum/gui/qt/channel_details.py +++ b/electrum/gui/qt/channel_details.py @@ -193,6 +193,7 @@ class ChannelDetailsDialog(QtWidgets.QDialog, MessageBoxMixin): form_layout.addRow(_('Remote dust limit:'), self.dust_limit) self.remote_reserve = self.window.format_amount_and_units(chan.config[REMOTE].reserve_sat) form_layout.addRow(_('Remote reserve:'), SelectableLabel(self.remote_reserve)) + form_layout.addRow(_('Channel type:'), SelectableLabel(chan.storage['channel_type'].name_minimal)) vbox.addLayout(form_layout) # add htlc tree view to vbox (wouldn't scale correctly in QFormLayout) diff --git a/electrum/json_db.py b/electrum/json_db.py index 9f06a3c6a..c9b339122 100644 --- a/electrum/json_db.py +++ b/electrum/json_db.py @@ -98,7 +98,7 @@ class StoredDict(dict): if not self.db or self.db._should_convert_to_stored_dict(key): v = StoredDict(v, self.db, self.path + [key]) # convert_value is called depth-first - if isinstance(v, dict) or isinstance(v, str): + if isinstance(v, dict) or isinstance(v, str) or isinstance(v, int): if self.db: v = self.db._convert_value(self.path, key, v) # set parent of StoredObject diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index bea1c0ea4..85df4844a 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -52,7 +52,8 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey ScriptHtlc, PaymentFailure, calc_fees_for_commitment_tx, RemoteMisbehaving, make_htlc_output_witness_script, ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr, fee_for_htlc_output, offered_htlc_trim_threshold_sat, - received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address) + received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, + ChannelType) from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo from .lnhtlc import HTLCManager @@ -711,7 +712,8 @@ class Channel(AbstractChannel): return chan_ann def is_static_remotekey_enabled(self) -> bool: - return bool(self.storage.get('static_remotekey_enabled')) + channel_type = ChannelType(self.storage.get('channel_type')) + return bool(channel_type & ChannelType.OPTION_STATIC_REMOTEKEY) def get_wallet_addresses_channel_might_want_reserved(self) -> Sequence[str]: ret = [] @@ -927,6 +929,7 @@ class Channel(AbstractChannel): Action must be initiated by LOCAL. Finally, the next remote ctx becomes the latest remote ctx. """ + # TODO: when more channel types are supported, this method should depend on channel type next_remote_ctn = self.get_next_ctn(REMOTE) self.logger.info(f"sign_next_commitment {next_remote_ctn}") @@ -968,6 +971,7 @@ class Channel(AbstractChannel): If all checks pass, the next local ctx becomes the latest local ctx. """ # TODO in many failure cases below, we should "fail" the channel (force-close) + # TODO: when more channel types are supported, this method should depend on channel type next_local_ctn = self.get_next_ctn(LOCAL) self.logger.info(f"receive_new_commitment. ctn={next_local_ctn}, len(htlc_sigs)={len(htlc_sigs)}") diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 0b345bfc8..1b918d785 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -42,7 +42,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf LightningPeerConnectionClosed, HandshakeFailed, RemoteMisbehaving, ShortChannelID, IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage, - UpfrontShutdownScriptViolation) + UpfrontShutdownScriptViolation, ChannelType) from .lnutil import FeeUpdate, channel_id_from_funding_tx from .lntransport import LNTransport, LNTransportBase from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType @@ -511,6 +511,9 @@ class Peer(Logger): def is_static_remotekey(self): return self.features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT) + def is_channel_type(self): + return self.features.supports(LnFeatures.OPTION_CHANNEL_TYPE_OPT) + def is_upfront_shutdown_script(self): return self.features.supports(LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT) @@ -528,16 +531,15 @@ class Peer(Logger): self.logger.info(f"upfront shutdown script received: {upfront_shutdown_script}") return upfront_shutdown_script - def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> LocalConfig: + def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner, channel_type: ChannelType) -> LocalConfig: channel_seed = os.urandom(32) initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat - static_remotekey = None # sending empty bytes as the upfront_shutdown_script will give us the # flexibility to decide an address at closing time upfront_shutdown_script = b'' - if self.is_static_remotekey(): + if channel_type & channel_type.OPTION_STATIC_REMOTEKEY: wallet = self.lnworker.wallet assert wallet.txin_type == 'p2wpkh' addr = wallet.get_new_sweep_address_for_channel() @@ -613,7 +615,24 @@ class Peer(Logger): raise Exception('Not a trampoline node: ' + str(self.their_features)) feerate = self.lnworker.current_feerate_per_kw() - local_config = self.make_local_config(funding_sat, push_msat, LOCAL) + # we set a channel type for internal bookkeeping + open_channel_tlvs = {} + if self.their_features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT): + our_channel_type = ChannelType(ChannelType.OPTION_STATIC_REMOTEKEY) + else: + our_channel_type = ChannelType(0) + # 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() + } + + local_config = self.make_local_config(funding_sat, push_msat, LOCAL, our_channel_type) + # if it includes open_channel_tlvs: MUST include upfront_shutdown_script. + open_channel_tlvs['upfront_shutdown_script'] = { + 'shutdown_scriptpubkey': local_config.upfront_shutdown_script + } # for the first commitment transaction per_commitment_secret_first = get_per_commitment_secret_from_seed( @@ -642,10 +661,7 @@ class Peer(Logger): channel_flags=0x00, # not willing to announce channel channel_reserve_satoshis=local_config.reserve_sat, htlc_minimum_msat=local_config.htlc_minimum_msat, - open_channel_tlvs={ - 'upfront_shutdown_script': - {'shutdown_scriptpubkey': local_config.upfront_shutdown_script} - } + open_channel_tlvs=open_channel_tlvs, ) # <- accept_channel @@ -660,6 +676,15 @@ class Peer(Logger): upfront_shutdown_script = self.upfront_shutdown_script_from_payload( payload, 'accept') + accept_channel_tlvs = payload.get('accept_channel_tlvs') + their_channel_type = accept_channel_tlvs.get('channel_type') if accept_channel_tlvs else None + if their_channel_type: + their_channel_type = ChannelType.from_bytes(their_channel_type['type'], byteorder='big').discard_unknown_and_check() + # if channel_type is set, and channel_type was set in open_channel, + # and they are not equal types: MUST reject the channel. + if open_channel_tlvs.get('channel_type') is not None and their_channel_type != our_channel_type: + raise Exception("Channel type is not the one that we sent.") + remote_config = RemoteConfig( payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']), multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]), @@ -675,7 +700,7 @@ class Peer(Logger): htlc_minimum_msat=payload['htlc_minimum_msat'], next_per_commitment_point=remote_per_commitment_point, current_per_commitment_point=None, - upfront_shutdown_script=upfront_shutdown_script + upfront_shutdown_script=upfront_shutdown_script, ) ChannelConfig.cross_validate_params( local_config=local_config, @@ -724,7 +749,7 @@ class Peer(Logger): funding_txn_minimum_depth=funding_txn_minimum_depth ) storage = self.create_channel_storage( - channel_id, outpoint, local_config, remote_config, constraints) + channel_id, outpoint, local_config, remote_config, constraints, our_channel_type) chan = Channel( storage, sweep_address=self.lnworker.sweep_address, @@ -755,7 +780,7 @@ class Peer(Logger): self.lnworker.add_new_channel(chan) return chan, funding_tx - def create_channel_storage(self, channel_id, outpoint, local_config, remote_config, constraints): + def create_channel_storage(self, channel_id, outpoint, local_config, remote_config, constraints, channel_type): chan_dict = { "node_id": self.pubkey.hex(), "channel_id": channel_id.hex(), @@ -772,7 +797,7 @@ class Peer(Logger): "fail_htlc_reasons": {}, # htlc_id -> onion_packet "unfulfilled_htlcs": {}, # htlc_id -> error_bytes, failure_message "revocation_store": {}, - "static_remotekey_enabled": self.is_static_remotekey(), # stored because it cannot be "downgraded", per BOLT2 + "channel_type": channel_type, } return StoredDict(chan_dict, self.lnworker.db if self.lnworker else None, []) @@ -796,7 +821,21 @@ class Peer(Logger): push_msat = payload['push_msat'] feerate = payload['feerate_per_kw'] # note: we are not validating this temp_chan_id = payload['temporary_channel_id'] - local_config = self.make_local_config(funding_sat, push_msat, REMOTE) + + open_channel_tlvs = payload.get('open_channel_tlvs') + 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: + raise Exception("sender has advertized 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: + 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") + + local_config = self.make_local_config(funding_sat, push_msat, REMOTE, channel_type) upfront_shutdown_script = self.upfront_shutdown_script_from_payload( payload, 'open') @@ -839,6 +878,17 @@ class Peer(Logger): per_commitment_point_first = secret_to_pubkey( int.from_bytes(per_commitment_secret_first, 'big')) min_depth = 3 + accept_channel_tlvs = { + 'upfront_shutdown_script': { + 'shutdown_scriptpubkey': local_config.upfront_shutdown_script + }, + } + # 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', temporary_channel_id=temp_chan_id, @@ -855,10 +905,7 @@ class Peer(Logger): delayed_payment_basepoint=local_config.delayed_basepoint.pubkey, htlc_basepoint=local_config.htlc_basepoint.pubkey, first_per_commitment_point=per_commitment_point_first, - accept_channel_tlvs={ - 'upfront_shutdown_script': - {'shutdown_scriptpubkey': local_config.upfront_shutdown_script} - } + accept_channel_tlvs=accept_channel_tlvs, ) # <- funding created @@ -875,7 +922,7 @@ class Peer(Logger): ) outpoint = Outpoint(funding_txid, funding_idx) chan_dict = self.create_channel_storage( - channel_id, outpoint, local_config, remote_config, constraints) + channel_id, outpoint, local_config, remote_config, constraints, channel_type) chan = Channel( chan_dict, sweep_address=self.lnworker.sweep_address, diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 362a9023e..8a4878746 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -8,7 +8,6 @@ import json from collections import namedtuple, defaultdict from typing import NamedTuple, List, Tuple, Mapping, Optional, TYPE_CHECKING, Union, Dict, Set, Sequence import re -import time import attr from aiorpcx import NetAddress @@ -1030,6 +1029,12 @@ class LnFeatures(IntFlag): _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_REQ] = (LNFC.INIT | LNFC.NODE_ANN) _ln_feature_contexts[OPTION_SHUTDOWN_ANYSEGWIT_OPT] = (LNFC.INIT | LNFC.NODE_ANN) + OPTION_CHANNEL_TYPE_REQ = 1 << 44 + OPTION_CHANNEL_TYPE_OPT = 1 << 45 + + _ln_feature_contexts[OPTION_CHANNEL_TYPE_REQ] = (LNFC.INIT | LNFC.NODE_ANN) + _ln_feature_contexts[OPTION_CHANNEL_TYPE_OPT] = (LNFC.INIT | LNFC.NODE_ANN) + # temporary OPTION_TRAMPOLINE_ROUTING_REQ_ECLAIR = 1 << 50 OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR = 1 << 51 @@ -1104,6 +1109,56 @@ class LnFeatures(IntFlag): or get_ln_flag_pair_of_bit(flag) in our_flags) +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 + + def discard_unknown_and_check(self): + """Discards unknown flags and checks flag combination.""" + flags = list_enabled_bits(self) + known_channel_types = [] + for flag in flags: + channel_type = ChannelType(1 << flag) + if channel_type.name: + known_channel_types.append(channel_type) + final_channel_type = known_channel_types[0] + for channel_type in known_channel_types[1:]: + final_channel_type |= channel_type + + final_channel_type.check_combinations() + return final_channel_type + + def check_combinations(self): + if self == ChannelType.OPTION_STATIC_REMOTEKEY: + pass + elif self == ChannelType.OPTION_ANCHOR_OUTPUTS | ChannelType.OPTION_STATIC_REMOTEKEY: + pass + elif self == ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX | ChannelType.OPTION_STATIC_REMOTEKEY: + pass + else: + raise ValueError("Channel type is not a valid flag combination.") + + def complies_with_features(self, features: LnFeatures) -> bool: + flags = list_enabled_bits(self) + complies = True + for flag in flags: + feature = LnFeatures(1 << flag) + complies &= features.supports(feature) + return complies + + def to_bytes_minimal(self): + # MUST use the smallest bitmap possible to represent the channel type. + bit_length =self.value.bit_length() + byte_length = bit_length // 8 + int(bool(bit_length % 8)) + return self.to_bytes(byte_length, byteorder='big') + + @property + def name_minimal(self): + return self.name.replace('OPTION_', '') + + del LNFC # name is ambiguous without context # features that are actually implemented and understood in our codebase: @@ -1119,6 +1174,7 @@ LN_FEATURES_IMPLEMENTED = ( | LnFeatures.BASIC_MPP_OPT | LnFeatures.BASIC_MPP_REQ | LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT | LnFeatures.OPTION_TRAMPOLINE_ROUTING_REQ | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_REQ + | LnFeatures.OPTION_CHANNEL_TYPE_OPT | LnFeatures.OPTION_CHANNEL_TYPE_REQ ) diff --git a/electrum/lnwire/peer_wire.csv b/electrum/lnwire/peer_wire.csv index d6eb9ccc2..17b8a103d 100644 --- a/electrum/lnwire/peer_wire.csv +++ b/electrum/lnwire/peer_wire.csv @@ -53,6 +53,8 @@ msgdata,open_channel,channel_flags,byte, msgdata,open_channel,tlvs,open_channel_tlvs, tlvtype,open_channel_tlvs,upfront_shutdown_script,0 tlvdata,open_channel_tlvs,upfront_shutdown_script,shutdown_scriptpubkey,byte,... +tlvtype,open_channel_tlvs,channel_type,1 +tlvdata,open_channel_tlvs,channel_type,type,byte,... msgtype,accept_channel,33 msgdata,accept_channel,temporary_channel_id,byte,32 msgdata,accept_channel,dust_limit_satoshis,u64, @@ -71,6 +73,8 @@ msgdata,accept_channel,first_per_commitment_point,point, msgdata,accept_channel,tlvs,accept_channel_tlvs, tlvtype,accept_channel_tlvs,upfront_shutdown_script,0 tlvdata,accept_channel_tlvs,upfront_shutdown_script,shutdown_scriptpubkey,byte,... +tlvtype,accept_channel_tlvs,channel_type,1 +tlvdata,accept_channel_tlvs,channel_type,type,byte,... msgtype,funding_created,34 msgdata,funding_created,temporary_channel_id,byte,32 msgdata,funding_created,funding_txid,sha256, diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 2135eca82..413c86f28 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -167,7 +167,7 @@ BASE_FEATURES = LnFeatures(0)\ | LnFeatures.OPTION_STATIC_REMOTEKEY_OPT\ | LnFeatures.VAR_ONION_OPT\ | LnFeatures.PAYMENT_SECRET_OPT\ - | LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT + | LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT\ # we do not want to receive unrequested gossip (see lnpeer.maybe_save_remote_update) LNWALLET_FEATURES = BASE_FEATURES\ @@ -177,10 +177,11 @@ LNWALLET_FEATURES = BASE_FEATURES\ | LnFeatures.BASIC_MPP_OPT\ | LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT\ | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT\ + | LnFeatures.OPTION_CHANNEL_TYPE_OPT\ LNGOSSIP_FEATURES = BASE_FEATURES\ | LnFeatures.GOSSIP_QUERIES_OPT\ - | LnFeatures.GOSSIP_QUERIES_REQ + | LnFeatures.GOSSIP_QUERIES_REQ\ class LNWorker(Logger, NetworkRetryManager[LNPeerAddr]): diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py index eb6230e4e..b42229166 100644 --- a/electrum/tests/test_lnchannel.py +++ b/electrum/tests/test_lnchannel.py @@ -107,6 +107,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator, 'fail_htlc_reasons': {}, 'unfulfilled_htlcs': {}, 'revocation_store': {}, + 'channel_type': lnutil.ChannelType.OPTION_STATIC_REMOTEKEY } return StoredDict(state, None, []) diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index e6eaaf5ee..7a98e7ead 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -138,6 +138,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]): self.features |= LnFeatures.VAR_ONION_OPT self.features |= LnFeatures.PAYMENT_SECRET_OPT self.features |= LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT + self.features |= LnFeatures.OPTION_CHANNEL_TYPE_OPT self.pending_payments = defaultdict(asyncio.Future) for chan in chans: chan.lnworker = self diff --git a/electrum/tests/test_lnutil.py b/electrum/tests/test_lnutil.py index 116cbe8cd..0483e8b8e 100644 --- a/electrum/tests/test_lnutil.py +++ b/electrum/tests/test_lnutil.py @@ -9,7 +9,7 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret, get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError, ScriptHtlc, extract_nodeid, calc_fees_for_commitment_tx, UpdateAddHtlc, LnFeatures, - ln_compare_features, IncompatibleLightningFeatures) + ln_compare_features, IncompatibleLightningFeatures, ChannelType) from electrum.util import bh2u, bfh, MyEncoder from electrum.transaction import Transaction, PartialTransaction from electrum.lnworker import LNWallet @@ -890,3 +890,15 @@ class TestLNUtil(ElectrumTestCase): self.assertEqual( None, LNWallet._decode_channel_update_msg(bytes.fromhex("0101") + msg_without_prefix)) + + def test_channel_type(self): + # test compliance and non compliance with LN features + features = LnFeatures(LnFeatures.BASIC_MPP_OPT | LnFeatures.OPTION_STATIC_REMOTEKEY_OPT) + self.assertTrue(ChannelType.OPTION_STATIC_REMOTEKEY.complies_with_features(features)) + + features = LnFeatures(LnFeatures.BASIC_MPP_OPT | LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT) + self.assertFalse(ChannelType.OPTION_STATIC_REMOTEKEY.complies_with_features(features)) + + # ignore unknown channel types + channel_type = ChannelType(0b10000000001000000000010).discard_unknown_and_check() + self.assertEqual(ChannelType(0b10000000001000000000000), channel_type) \ No newline at end of file diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py index 230465bae..cdd1fe969 100644 --- a/electrum/wallet_db.py +++ b/electrum/wallet_db.py @@ -37,7 +37,7 @@ from .invoices import Invoice from .keystore import bip44_derivation from .transaction import Transaction, TxOutpoint, tx_from_any, PartialTransaction, PartialTxOutput from .logging import Logger -from .lnutil import LOCAL, REMOTE, FeeUpdate, UpdateAddHtlc, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, RevocationStore +from .lnutil import LOCAL, REMOTE, FeeUpdate, UpdateAddHtlc, LocalConfig, RemoteConfig, ChannelType from .lnutil import ImportedChannelBackupStorage, OnchainChannelBackupStorage from .lnutil import ChannelConstraints, Outpoint, ShachainElement from .json_db import StoredDict, JsonDB, locked, modifier @@ -53,7 +53,7 @@ if TYPE_CHECKING: OLD_SEED_VERSION = 4 # electrum versions < 2.0 NEW_SEED_VERSION = 11 # electrum versions >= 2.0 -FINAL_SEED_VERSION = 43 # electrum >= 2.7 will set this to prevent +FINAL_SEED_VERSION = 44 # electrum >= 2.7 will set this to prevent # old versions from overwriting new format @@ -192,6 +192,7 @@ class WalletDB(JsonDB): self._convert_version_41() self._convert_version_42() self._convert_version_43() + self._convert_version_44() self.put('seed_version', FINAL_SEED_VERSION) # just to be sure self._after_upgrade_tasks() @@ -850,6 +851,19 @@ class WalletDB(JsonDB): self.data['channels'] = channels self.data['seed_version'] = 43 + def _convert_version_44(self): + if not self._is_upgrade_method_needed(43, 43): + return + channels = self.data.get('channels', {}) + for key, item in channels.items(): + if item['static_remotekey_enabled']: + channel_type = ChannelType.OPTION_STATIC_REMOTEKEY + else: + channel_type = ChannelType(0) + del item['static_remotekey_enabled'] + item['channel_type'] = channel_type + self.data['seed_version'] = 44 + def _convert_imported(self): if not self._is_upgrade_method_needed(0, 13): return @@ -1377,6 +1391,8 @@ class WalletDB(JsonDB): v = ChannelConstraints(**v) elif key == 'funding_outpoint': v = Outpoint(**v) + elif key == 'channel_type': + v = ChannelType(v) return v def _should_convert_to_stored_dict(self, key) -> bool: