Browse Source

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
hard-fail-on-bad-server-string
ThomasV 5 years ago
parent
commit
61dfcba092
  1. 7
      electrum/gui/kivy/uix/dialogs/lightning_channels.py
  2. 3
      electrum/gui/qt/channels_list.py
  3. 2
      electrum/gui/qt/main_window.py
  4. 13
      electrum/json_db.py
  5. 78
      electrum/lnchannel.py
  6. 28
      electrum/lnpeer.py
  7. 2
      electrum/lnwatcher.py
  8. 80
      electrum/lnworker.py
  9. 4
      electrum/tests/regtest/regtest.sh
  10. 6
      electrum/tests/test_lnchannel.py
  11. 7
      electrum/tests/test_lnpeer.py

7
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

3
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):

2
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

13
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

78
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
}

28
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...')

2
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:

80
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)

4
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

6
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()

7
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)

Loading…
Cancel
Save