electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 61dfcba092c8b2ad68a5821010bd1a16fd1d57cc
parent c31fa059fe01a38ae5f382715283fbec5f6dca9a
Author: ThomasV <thomasv@electrum.org>
Date:   Tue, 29 Oct 2019 08:02:14 +0100

Refactor channel states:
 - persisted states are saved
 - state transitions are checked
 - transient states are stored in channel.peer_state
 - new channel states: 'PREOPENING', 'FUNDED' and 'REDEEMED'
 - upgrade storage to version 21

Diffstat:
Melectrum/gui/kivy/uix/dialogs/lightning_channels.py | 7++++---
Melectrum/gui/qt/channels_list.py | 3++-
Melectrum/gui/qt/main_window.py | 2+-
Melectrum/json_db.py | 13++++++++++++-
Melectrum/lnchannel.py | 78+++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Melectrum/lnpeer.py | 28++++++++++++++++------------
Melectrum/lnwatcher.py | 2+-
Melectrum/lnworker.py | 80+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Melectrum/tests/regtest/regtest.sh | 4++++
Melectrum/tests/test_lnchannel.py | 6++++--
Melectrum/tests/test_lnpeer.py | 7++++---
11 files changed, 151 insertions(+), 79 deletions(-)

diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -136,10 +136,11 @@ class ChannelDetailsPopup(Popup): def details(self): chan = self.chan + status = self.app.wallet.lnworker.get_channel_status(chan) return { _('Short Chan ID'): format_short_channel_id(chan.short_channel_id), _('Initiator'): 'Local' if chan.constraints.is_initiator else 'Remote', - _('State'): chan.get_state(), + _('State'): status, _('Local CTN'): chan.get_latest_ctn(LOCAL), _('Remote CTN'): chan.get_latest_ctn(REMOTE), _('Capacity'): self.app.format_amount_and_units(chan.constraints.capacity), @@ -181,7 +182,7 @@ class ChannelDetailsPopup(Popup): def _force_close(self, b): if not b: return - if self.chan.get_state() == 'CLOSED': + if self.chan.is_closed(): self.app.show_error(_('Channel already closed')) return loop = self.app.wallet.network.asyncio_loop @@ -223,7 +224,7 @@ class LightningChannelsDialog(Factory.Popup): def update_item(self, item): chan = item._chan - item.status = chan.get_state() + item.status = self.app.wallet.lnworker.get_channel_status(chan) item.short_channel_id = format_short_channel_id(chan.short_channel_id) l, r = self.format_fields(chan) item.local_balance = _('Local') + ':' + l diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py @@ -60,12 +60,13 @@ class ChannelsList(MyTreeView): if bal_other != bal_minus_htlcs_other: label += ' (+' + self.parent.format_amount(bal_other - bal_minus_htlcs_other) + ')' labels[subject] = label + status = self.lnworker.get_channel_status(chan) return [ format_short_channel_id(chan.short_channel_id), bh2u(chan.node_id), labels[LOCAL], labels[REMOTE], - chan.get_state() + status ] def on_success(self, txid): diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py @@ -435,7 +435,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): wallet.thread = TaskThread(self, self.on_error) self.update_recently_visited(wallet.storage.path) if wallet.lnworker: - wallet.lnworker.on_channels_updated() + wallet.network.trigger_callback('channels_updated', wallet) self.need_update.set() # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized # update menus diff --git a/electrum/json_db.py b/electrum/json_db.py @@ -40,7 +40,7 @@ from .logging import Logger OLD_SEED_VERSION = 4 # electrum versions < 2.0 NEW_SEED_VERSION = 11 # electrum versions >= 2.0 -FINAL_SEED_VERSION = 20 # electrum >= 2.7 will set this to prevent +FINAL_SEED_VERSION = 21 # electrum >= 2.7 will set this to prevent # old versions from overwriting new format @@ -214,6 +214,7 @@ class JsonDB(Logger): self._convert_version_18() self._convert_version_19() self._convert_version_20() + self._convert_version_21() self.put('seed_version', FINAL_SEED_VERSION) # just to be sure self._after_upgrade_tasks() @@ -485,6 +486,16 @@ class JsonDB(Logger): self.put('seed_version', 20) + def _convert_version_21(self): + if not self._is_upgrade_method_needed(20, 20): + return + channels = self.get('channels') + if channels: + for channel in channels: + channel['state'] = 'OPENING' + self.put('channels', channels) + self.put('seed_version', 21) + def _convert_imported(self): if not self._is_upgrade_method_needed(0, 13): return diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py @@ -26,7 +26,7 @@ import os from collections import namedtuple, defaultdict import binascii import json -from enum import Enum, auto +from enum import IntEnum from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable, Sequence, TYPE_CHECKING import time @@ -55,6 +55,42 @@ if TYPE_CHECKING: from .lnworker import LNWallet +# lightning channel states +class channel_states(IntEnum): + PREOPENING = 0 # negociating + OPENING = 1 # awaiting funding tx + FUNDED = 2 # funded (requires min_depth and tx verification) + OPEN = 3 # both parties have sent funding_locked + FORCE_CLOSING = 4 # force-close tx has been broadcast + CLOSING = 5 # closing negociation + CLOSED = 6 # funding txo has been spent + REDEEMED = 7 # we can stop watching + +class peer_states(IntEnum): + DISCONNECTED = 0 + REESTABLISHING = 1 + GOOD = 2 + +cs = channel_states +state_transitions = [ + (cs.PREOPENING, cs.OPENING), + (cs.OPENING, cs.FUNDED), + (cs.FUNDED, cs.OPEN), + (cs.OPENING, cs.CLOSING), + (cs.FUNDED, cs.CLOSING), + (cs.OPEN, cs.CLOSING), + (cs.OPENING, cs.FORCE_CLOSING), + (cs.FUNDED, cs.FORCE_CLOSING), + (cs.OPEN, cs.FORCE_CLOSING), + (cs.CLOSING, cs.FORCE_CLOSING), + (cs.OPENING, cs.CLOSED), + (cs.FUNDED, cs.CLOSED), + (cs.OPEN, cs.CLOSED), + (cs.CLOSING, cs.CLOSED), + (cs.FORCE_CLOSING, cs.CLOSED), + (cs.CLOSED, cs.REDEEMED), +] + class ChannelJsonEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, bytes): @@ -136,18 +172,13 @@ class Channel(Logger): self.short_channel_id = ShortChannelID.normalize(state["short_channel_id"]) self.short_channel_id_predicted = self.short_channel_id self.onion_keys = str_bytes_dict_from_save(state.get('onion_keys', {})) - self.force_closed = state.get('force_closed') self.data_loss_protect_remote_pcp = str_bytes_dict_from_save(state.get('data_loss_protect_remote_pcp', {})) self.remote_update = bfh(state.get('remote_update')) if state.get('remote_update') else None log = state.get('log') - self.hm = HTLCManager(log=log, - initial_feerate=initial_feerate) - - - self._is_funding_txo_spent = None # "don't know" - self._state = None - self.set_state('DISCONNECTED') + self.hm = HTLCManager(log=log, initial_feerate=initial_feerate) + self._state = channel_states[state['state']] + self.peer_state = peer_states.DISCONNECTED self.sweep_info = {} # type: Dict[str, Dict[str, SweepInfo]] self._outgoing_channel_update = None # type: Optional[bytes] @@ -184,26 +215,31 @@ class Channel(Logger): next_per_commitment_point=None) self.config[LOCAL] = self.config[LOCAL]._replace(current_commitment_signature=remote_sig) self.hm.channel_open_finished() - self.set_state('OPENING') + self.peer_state = peer_states.GOOD + self.set_state(channel_states.OPENING) - def set_force_closed(self): - self.force_closed = True - - def set_state(self, state: str): + def set_state(self, state): + """ set on-chain state """ + if (self._state, state) not in state_transitions: + raise Exception(f"Transition not allowed: {self._state.name} -> {state.name}") self._state = state + self.logger.debug(f'Setting channel state: {state.name}') + if self.lnworker: + self.lnworker.save_channel(self) + self.lnworker.network.trigger_callback('channel', self) def get_state(self): return self._state def is_closed(self): - return self.force_closed or self.get_state() in ['CLOSED', 'CLOSING'] + return self.get_state() > channel_states.OPEN def _check_can_pay(self, amount_msat: int) -> None: # TODO check if this method uses correct ctns (should use "latest" + 1) if self.is_closed(): raise PaymentFailure('Channel closed') - if self.get_state() != 'OPEN': - raise PaymentFailure('Channel not open') + if self.get_state() != channel_states.OPEN: + raise PaymentFailure('Channel not open', self.get_state()) if self.available_to_spend(LOCAL) < amount_msat: raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}') if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs: @@ -222,12 +258,8 @@ class Channel(Logger): return False return True - def set_funding_txo_spentness(self, is_spent: bool): - assert isinstance(is_spent, bool) - self._is_funding_txo_spent = is_spent - def should_try_to_reestablish_peer(self) -> bool: - return self._is_funding_txo_spent is False and self._state == 'DISCONNECTED' + return self._state < channel_states.CLOSED and self.peer_state == peer_states.DISCONNECTED def get_funding_address(self): script = funding_output_script(self.config[LOCAL], self.config[REMOTE]) @@ -624,7 +656,7 @@ class Channel(Logger): "node_id": self.node_id, "log": self.hm.to_save(), "onion_keys": str_bytes_dict_to_save(self.onion_keys), - "force_closed": self.force_closed, + "state": self._state.name, "data_loss_protect_remote_pcp": str_bytes_dict_to_save(self.data_loss_protect_remote_pcp), "remote_update": self.remote_update.hex() if self.remote_update else None } diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py @@ -29,7 +29,7 @@ from .logging import Logger from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment, process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage, ProcessedOnionPacket) -from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture +from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture, channel_states, peer_states from . import lnutil from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, @@ -579,6 +579,7 @@ class Peer(Logger): "local_config": local_config, "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth), "remote_update": None, + "state": channel_states.PREOPENING.name, } chan = Channel(chan_dict, sweep_address=self.lnworker.sweep_address, @@ -594,10 +595,10 @@ class Peer(Logger): self.logger.info('received funding_signed') remote_sig = payload['signature'] chan.receive_new_commitment(remote_sig, []) + chan.open_with_first_pcp(remote_per_commitment_point, remote_sig) # broadcast funding tx # TODO make more robust (timeout low? server returns error?) await asyncio.wait_for(self.network.broadcast_transaction(funding_tx), LN_P2P_NETWORK_TIMEOUT) - chan.open_with_first_pcp(remote_per_commitment_point, remote_sig) return chan async def on_open_channel(self, payload): @@ -664,6 +665,7 @@ class Peer(Logger): "local_config": local_config, "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth), "remote_update": None, + "state": channel_states.PREOPENING.name, } chan = Channel(chan_dict, sweep_address=self.lnworker.sweep_address, @@ -709,11 +711,11 @@ class Peer(Logger): async def reestablish_channel(self, chan: Channel): await self.initialized.wait() chan_id = chan.channel_id - if chan.get_state() != 'DISCONNECTED': + if chan.peer_state != peer_states.DISCONNECTED: self.logger.info('reestablish_channel was called but channel {} already in state {}' .format(chan_id, chan.get_state())) return - chan.set_state('REESTABLISHING') + chan.peer_state = peer_states.REESTABLISHING self.network.trigger_callback('channel', chan) # BOLT-02: "A node [...] upon disconnection [...] MUST reverse any uncommitted updates sent by the other side" chan.hm.discard_unsigned_remote_updates() @@ -856,11 +858,11 @@ class Peer(Logger): await self.lnworker.force_close_channel(chan_id) return + chan.peer_state = peer_states.GOOD # note: chan.short_channel_id being set implies the funding txn is already at sufficient depth if their_next_local_ctn == next_local_ctn == 1 and chan.short_channel_id: self.send_funding_locked(chan) # checks done - chan.set_state('OPENING') if chan.config[LOCAL].funding_locked_received and chan.short_channel_id: self.mark_open(chan) self.network.trigger_callback('channel', chan) @@ -949,11 +951,12 @@ class Peer(Logger): def mark_open(self, chan: Channel): assert chan.short_channel_id is not None scid = chan.short_channel_id - # only allow state transition to "OPEN" from "OPENING" - if chan.get_state() != "OPENING": + # only allow state transition from "FUNDED" to "OPEN" + if chan.get_state() != channel_states.FUNDED: + self.logger.info(f"cannot mark open, {chan.get_state()}") return assert chan.config[LOCAL].funding_locked_received - chan.set_state("OPEN") + chan.set_state(channel_states.OPEN) self.network.trigger_callback('channel', chan) self.add_own_channel(chan) self.logger.info(f"CHANNEL OPENING COMPLETED for {scid}") @@ -1114,7 +1117,8 @@ class Peer(Logger): async def pay(self, route: List['RouteEdge'], chan: Channel, amount_msat: int, payment_hash: bytes, min_final_cltv_expiry: int) -> UpdateAddHtlc: - assert chan.get_state() == "OPEN", chan.get_state() + if chan.get_state() != channel_states.OPEN: + raise PaymentFailure('Channel not open') assert amount_msat > 0, "amount_msat is not greater zero" # create onion packet final_cltv = self.network.get_local_height() + min_final_cltv_expiry @@ -1200,7 +1204,7 @@ class Peer(Logger): amount_msat_htlc = int.from_bytes(payload["amount_msat"], 'big') onion_packet = OnionPacket.from_bytes(payload["onion_routing_packet"]) processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) - if chan.get_state() != "OPEN": + if chan.get_state() != channel_states.OPEN: raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()}") if cltv_expiry >= 500_000_000: asyncio.ensure_future(self.lnworker.force_close_channel(channel_id)) @@ -1255,7 +1259,7 @@ class Peer(Logger): return outgoing_chan_upd = self.get_outgoing_gossip_channel_update_for_chan(next_chan)[2:] outgoing_chan_upd_len = len(outgoing_chan_upd).to_bytes(2, byteorder="big") - if next_chan.get_state() != 'OPEN': + if next_chan.get_state() != channel_states.OPEN: self.logger.info(f"cannot forward htlc. next_chan not OPEN: {next_chan_scid} in state {next_chan.get_state()}") reason = OnionRoutingFailureMessage(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_len+outgoing_chan_upd) @@ -1453,7 +1457,7 @@ class Peer(Logger): @log_exceptions async def _shutdown(self, chan: Channel, payload, is_local): # set state so that we stop accepting HTLCs - chan.set_state('CLOSING') + chan.set_state(channel_states.CLOSING) # wait until no HTLCs remain in either commitment transaction while len(chan.hm.htlcs(LOCAL)) + len(chan.hm.htlcs(REMOTE)) > 0: self.logger.info('waiting for htlcs to settle...') diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py @@ -190,7 +190,7 @@ class LNWatcher(AddressSynchronizer): return self.network.trigger_callback('update_closed_channel', funding_outpoint, spenders, funding_txid, funding_height, closing_txid, - closing_height, closing_tx) # FIXME sooo many args.. + closing_height, closing_tx, keep_watching) # FIXME sooo many args.. # TODO: add tests for local_watchtower await self.do_breach_remedy(funding_outpoint, spenders) if not keep_watching: diff --git a/electrum/lnworker.py b/electrum/lnworker.py @@ -40,6 +40,7 @@ from .lnaddr import lnencode, LnAddr, lndecode from .ecc import der_sig_from_sig_string from .ecc_fast import is_using_fast_ecc from .lnchannel import Channel, ChannelJsonEncoder +from .lnchannel import channel_states, peer_states from . import lnutil from .lnutil import funding_output_script from .bitcoin import redeem_script_to_address @@ -420,10 +421,21 @@ class LNWallet(LNWorker): def peer_closed(self, peer): for chan in self.channels_for_peer(peer.pubkey).values(): - chan.set_state('DISCONNECTED') + chan.peer_state = peer_states.DISCONNECTED self.network.trigger_callback('channel', chan) self.peers.pop(peer.pubkey) + def get_channel_status(self, chan): + # status displayed in the GUI + cs = chan.get_state() + if chan.is_closed(): + return cs.name + peer = self.peers.get(chan.node_id) + ps = chan.peer_state + if ps != peer_states.GOOD: + return ps.name + return cs.name + def payment_completed(self, chan: Channel, direction: Direction, htlc: UpdateAddHtlc): chan_id = chan.channel_id @@ -629,7 +641,6 @@ class LNWallet(LNWorker): block_height, tx_pos, chan.funding_outpoint.output_index) self.logger.info(f"save_short_channel_id: {chan.short_channel_id}") self.save_channel(chan) - self.on_channels_updated() def channel_by_txo(self, txo): with self.lock: @@ -644,23 +655,25 @@ class LNWallet(LNWorker): chan = self.channel_by_txo(funding_outpoint) if not chan: return - #self.logger.debug(f'on_channel_open {funding_outpoint}') - self.channel_timestamps[bh2u(chan.channel_id)] = funding_txid, funding_height.height, funding_height.timestamp, None, None, None - self.storage.put('lightning_channel_timestamps', self.channel_timestamps) - chan.set_funding_txo_spentness(False) - # send event to GUI - self.network.trigger_callback('channel', chan) - - if self.should_channel_be_closed_due_to_expiring_htlcs(chan): + if chan.get_state() == channel_states.OPEN and self.should_channel_be_closed_due_to_expiring_htlcs(chan): self.logger.info(f"force-closing due to expiring htlcs") await self.force_close_channel(chan.channel_id) return - if chan.short_channel_id is None: - self.save_short_chan_id(chan) - if chan.get_state() == "OPENING" and chan.short_channel_id: - peer = self.peers[chan.node_id] - peer.send_funding_locked(chan) - elif chan.get_state() == "OPEN": + + if chan.get_state() == channel_states.OPENING: + if chan.short_channel_id is None: + self.save_short_chan_id(chan) + if chan.short_channel_id: + chan.set_state(channel_states.FUNDED) + self.channel_timestamps[bh2u(chan.channel_id)] = chan.funding_outpoint.txid, funding_height.height, funding_height.timestamp, None, None, None + self.storage.put('lightning_channel_timestamps', self.channel_timestamps) + + if chan.get_state() == channel_states.FUNDED: + peer = self.peers.get(chan.node_id) + if peer: + peer.send_funding_locked(chan) + + elif chan.get_state() == channel_states.OPEN: peer = self.peers.get(chan.node_id) if peer is None: self.logger.info("peer not found for {}".format(bh2u(chan.node_id))) @@ -669,7 +682,8 @@ class LNWallet(LNWorker): await peer.bitcoin_fee_update(chan) conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf peer.on_network_update(chan, conf) - elif chan.force_closed and chan.get_state() != 'CLOSED': + + elif chan.get_state() == channel_states.FORCE_CLOSING: txid = chan.force_close_tx().txid() height = self.lnwatcher.get_tx_height(txid).height self.logger.info(f"force closing tx {txid}, height {height}") @@ -677,21 +691,27 @@ class LNWallet(LNWorker): self.logger.info('REBROADCASTING CLOSING TX') await self.force_close_channel(chan.channel_id) + @ignore_exceptions @log_exceptions - async def on_update_closed_channel(self, event, funding_outpoint, spenders, funding_txid, funding_height, closing_txid, closing_height, closing_tx): + async def on_update_closed_channel(self, event, funding_outpoint, spenders, funding_txid, funding_height, closing_txid, closing_height, closing_tx, keep_watching): chan = self.channel_by_txo(funding_outpoint) if not chan: return - #self.logger.debug(f'on_channel_closed {funding_outpoint}') + + # fixme: this is wasteful self.channel_timestamps[bh2u(chan.channel_id)] = funding_txid, funding_height.height, funding_height.timestamp, closing_txid, closing_height.height, closing_height.timestamp self.storage.put('lightning_channel_timestamps', self.channel_timestamps) - chan.set_funding_txo_spentness(True) - chan.set_state('CLOSED') - self.on_channels_updated() - self.network.trigger_callback('channel', chan) + # remove from channel_db if chan.short_channel_id is not None: self.channel_db.remove_channel(chan.short_channel_id) + + if chan.get_state() < channel_states.CLOSED: + chan.set_state(channel_states.CLOSED) + + if chan.get_state() == channel_states.CLOSED and not keep_watching: + chan.set_state(channel_states.REDEEMED) + # detect who closed and set sweep_info sweep_info_dict = chan.sweep_ctx(closing_tx) self.logger.info(f'sweep_info_dict length: {len(sweep_info_dict)}') @@ -800,11 +820,8 @@ class LNWallet(LNWorker): temp_channel_id=os.urandom(32)) self.save_channel(chan) self.lnwatcher.add_channel(chan.funding_outpoint.to_str(), chan.get_funding_address()) - self.on_channels_updated() - return chan - - def on_channels_updated(self): self.network.trigger_callback('channels_updated', self.wallet) + return chan @log_exceptions async def add_peer(self, connect_str: str) -> Peer: @@ -1149,7 +1166,7 @@ class LNWallet(LNWorker): # note: currently we add *all* our channels; but this might be a privacy leak? for chan in channels: # check channel is open - if chan.get_state() != "OPEN": + if chan.get_state() != channel_states.OPEN: continue # check channel has sufficient balance # FIXME because of on-chain fees of ctx, this check is insufficient @@ -1206,7 +1223,7 @@ class LNWallet(LNWorker): 'channel_id': format_short_channel_id(chan.short_channel_id) if chan.short_channel_id else None, 'full_channel_id': bh2u(chan.channel_id), 'channel_point': chan.funding_outpoint.to_str(), - 'state': chan.get_state(), + 'state': chan.get_state().name, 'remote_pubkey': bh2u(chan.node_id), 'local_balance': chan.balance(LOCAL)//1000, 'remote_balance': chan.balance(REMOTE)//1000, @@ -1220,9 +1237,7 @@ class LNWallet(LNWorker): async def force_close_channel(self, chan_id): chan = self.channels[chan_id] tx = chan.force_close_tx() - chan.set_force_closed() - self.save_channel(chan) - self.on_channels_updated() + chan.set_state(channel_states.FORCE_CLOSING) try: await self.network.broadcast_transaction(tx) except Exception as e: @@ -1276,6 +1291,7 @@ class LNWallet(LNWorker): if ratio < 0.5: self.logger.warning(f"fee level for channel {bh2u(chan.channel_id)} is {chan_feerate} sat/kiloweight, " f"current recommended feerate is {self.current_feerate_per_kw()} sat/kiloweight, consider force closing!") + # reestablish if not chan.should_try_to_reestablish_peer(): continue peer = self.peers.get(chan.node_id, None) diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh @@ -90,6 +90,10 @@ if [[ $1 == "init" ]]; then new_blocks 1 fi +if [[ $1 == "new_block" ]]; then + new_blocks 1 +fi + # start daemons. Bob is started first because he is listening if [[ $1 == "start" ]]; then $bob daemon -d diff --git a/electrum/tests/test_lnchannel.py b/electrum/tests/test_lnchannel.py @@ -34,6 +34,7 @@ from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED from electrum.lnutil import FeeUpdate from electrum.ecc import sig_string_from_der_sig from electrum.logging import console_stderr_handler +from electrum.lnchannel import channel_states from . import ElectrumTestCase @@ -94,6 +95,7 @@ def create_channel_state(funding_txid, funding_index, funding_sat, is_initiator, ), "node_id":other_node_id, 'onion_keys': {}, + 'state': 'PREOPENING', } def bip32(sequence): @@ -136,8 +138,8 @@ def create_test_channels(feerate=6000, local=None, remote=None): alice.hm.log[LOCAL]['ctn'] = 0 bob.hm.log[LOCAL]['ctn'] = 0 - alice.set_state('OPEN') - bob.set_state('OPEN') + alice._state = channel_states.OPEN + bob._state = channel_states.OPEN a_out = alice.get_latest_commitment(LOCAL).outputs() b_out = bob.get_next_commitment(REMOTE).outputs() diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py @@ -16,6 +16,7 @@ from electrum.lnpeer import Peer from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving from electrum.lnutil import PaymentFailure, LnLocalFeatures +from electrum.lnchannel import channel_states from electrum.lnrouter import LNPathFinder from electrum.channel_db import ChannelDB from electrum.lnworker import LNWallet, NoPathFound @@ -202,9 +203,9 @@ class TestPeer(ElectrumTestCase): w1.peer = p1 w2.peer = p2 # mark_open won't work if state is already OPEN. - # so set it to OPENING - self.alice_channel.set_state("OPENING") - self.bob_channel.set_state("OPENING") + # so set it to FUNDED + self.alice_channel._state = channel_states.FUNDED + self.bob_channel._state = channel_states.FUNDED # this populates the channel graph: p1.mark_open(self.alice_channel) p2.mark_open(self.bob_channel)