commit 06dfe1699cca8d116914138147301b0ec9b289bc
parent 9ca445bd5d8cff6df049f21e374bbb915b77d23b
Author: ThomasV <thomasv@electrum.org>
Date: Fri, 3 Apr 2020 16:25:42 +0200
LNWatcher: Distinguish between blockchain-triggered channel state
transitions, and actions taken as a result.
- state transitions are performed in lnchannel.update_onchain_state()
- peer actions are in LNWorker.on_channel_update()
Diffstat:
3 files changed, 69 insertions(+), 66 deletions(-)
diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
@@ -54,6 +54,7 @@ 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
from .lnmsg import encode_msg, decode_msg
+from .address_synchronizer import TX_HEIGHT_LOCAL
if TYPE_CHECKING:
from .lnworker import LNWallet
@@ -1092,3 +1093,63 @@ class Channel(Logger):
min_value_worth_closing_channel_over_sat = max(num_htlcs * 10 * self.config[REMOTE].dust_limit_sat,
500_000)
return total_value_sat > min_value_worth_closing_channel_over_sat
+
+ def update_onchain_state(self, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
+ # note: state transitions are irreversible, but
+ # save_funding_height, save_closing_height are reversible
+ if funding_height.height == TX_HEIGHT_LOCAL:
+ self.update_unfunded_state()
+ elif closing_height.height == TX_HEIGHT_LOCAL:
+ self.update_funded_state(funding_txid, funding_height)
+ else:
+ self.update_closed_state(funding_txid, funding_height, closing_txid, closing_height, keep_watching)
+
+ def update_unfunded_state(self):
+ self.delete_funding_height()
+ self.delete_closing_height()
+ if self.get_state() in [channel_states.PREOPENING, channel_states.OPENING, channel_states.FORCE_CLOSING] and self.lnworker:
+ if self.constraints.is_initiator:
+ # set channel state to REDEEMED so that it can be removed manually
+ # to protect ourselves against a server lying by omission,
+ # we check that funding_inputs have been double spent and deeply mined
+ inputs = self.storage.get('funding_inputs', [])
+ if not inputs:
+ self.logger.info(f'channel funding inputs are not provided')
+ self.set_state(channel_states.REDEEMED)
+ for i in inputs:
+ spender_txid = self.lnworker.wallet.db.get_spent_outpoint(*i)
+ if spender_txid is None:
+ continue
+ if spender_txid != self.funding_outpoint.txid:
+ tx_mined_height = self.lnworker.wallet.get_tx_height(spender_txid)
+ if tx_mined_height.conf > lnutil.REDEEM_AFTER_DOUBLE_SPENT_DELAY:
+ self.logger.info(f'channel is double spent {inputs}')
+ self.set_state(channel_states.REDEEMED)
+ break
+ else:
+ now = int(time.time())
+ if now - self.storage.get('init_timestamp', 0) > CHANNEL_OPENING_TIMEOUT:
+ self.lnworker.remove_channel(self.channel_id)
+
+ def update_funded_state(self, funding_txid, funding_height):
+ self.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
+ self.delete_closing_height()
+ if self.get_state() == channel_states.OPENING:
+ if self.short_channel_id is None:
+ self.lnworker.maybe_save_short_chan_id(self, funding_height)
+ if self.short_channel_id:
+ self.set_state(channel_states.FUNDED)
+
+ def update_closed_state(self, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
+ self.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
+ self.save_closing_height(closing_txid, closing_height.height, closing_height.timestamp)
+ if self.get_state() < channel_states.CLOSED:
+ conf = closing_height.conf
+ if conf > 0:
+ self.set_state(channel_states.CLOSED)
+ else:
+ # we must not trust the server with unconfirmed transactions
+ # if the remote force closed, we remain OPEN until the closing tx is confirmed
+ pass
+ if self.get_state() == channel_states.CLOSED and not keep_watching:
+ self.set_state(channel_states.REDEEMED)
diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
@@ -341,23 +341,11 @@ class LNWalletWatcher(LNWatcher):
@ignore_exceptions
@log_exceptions
async def update_channel_state(self, funding_outpoint, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
- # note: state transitions are irreversible, but
- # save_funding_height, save_closing_height are reversible
chan = self.lnworker.channel_by_txo(funding_outpoint)
if not chan:
return
- if funding_height.height == TX_HEIGHT_LOCAL:
- chan.delete_funding_height()
- chan.delete_closing_height()
- await self.lnworker.update_unfunded_channel(chan, funding_txid)
- elif closing_height.height == TX_HEIGHT_LOCAL:
- chan.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
- chan.delete_closing_height()
- await self.lnworker.update_open_channel(chan, funding_txid, funding_height)
- else:
- chan.save_funding_height(funding_txid, funding_height.height, funding_height.timestamp)
- chan.save_closing_height(closing_txid, closing_height.height, closing_height.timestamp)
- await self.lnworker.update_closed_channel(chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching)
+ chan.update_onchain_state(funding_txid, funding_height, closing_txid, closing_height, keep_watching)
+ await self.lnworker.on_channel_update(chan)
async def do_breach_remedy(self, funding_outpoint, closing_tx, spenders):
chan = self.lnworker.channel_by_txo(funding_outpoint)
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -696,57 +696,24 @@ class LNWallet(LNWorker):
if chan.funding_outpoint.to_str() == txo:
return chan
- async def update_unfunded_channel(self, chan, funding_txid):
- if chan.get_state() in [channel_states.PREOPENING, channel_states.OPENING, channel_states.FORCE_CLOSING]:
- if chan.constraints.is_initiator:
- # set channel state to REDEEMED so that it can be removed manually
- # to protect ourselves against a server lying by omission,
- # we check that funding_inputs have been double spent and deeply mined
- inputs = chan.storage.get('funding_inputs', [])
- if not inputs:
- self.logger.info(f'channel funding inputs are not provided')
- chan.set_state(channel_states.REDEEMED)
- for i in inputs:
- spender_txid = self.wallet.db.get_spent_outpoint(*i)
- if spender_txid is None:
- continue
- if spender_txid != funding_txid:
- tx_mined_height = self.wallet.get_tx_height(spender_txid)
- if tx_mined_height.conf > lnutil.REDEEM_AFTER_DOUBLE_SPENT_DELAY:
- self.logger.info(f'channel is double spent {inputs}')
- chan.set_state(channel_states.REDEEMED)
- break
- else:
- now = int(time.time())
- if now - chan.storage.get('init_timestamp', 0) > CHANNEL_OPENING_TIMEOUT:
- self.remove_channel(chan.channel_id)
- async def update_open_channel(self, chan, funding_txid, funding_height):
+ async def on_channel_update(self, chan):
if chan.get_state() == channel_states.OPEN and chan.should_be_closed_due_to_expiring_htlcs(self.network.get_local_height()):
self.logger.info(f"force-closing due to expiring htlcs")
await self.try_force_closing(chan.channel_id)
- return
- if chan.get_state() == channel_states.OPENING:
- if chan.short_channel_id is None:
- self.maybe_save_short_chan_id(chan, funding_height)
- if chan.short_channel_id:
- chan.set_state(channel_states.FUNDED)
-
- if chan.get_state() == channel_states.FUNDED:
+ elif chan.get_state() == channel_states.FUNDED:
peer = self.peers.get(chan.node_id)
if peer and peer.is_initialized():
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)))
- return
- await peer.maybe_update_fee(chan)
- conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf
- peer.on_network_update(chan, conf)
+ if peer:
+ await peer.maybe_update_fee(chan)
+ conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf
+ peer.on_network_update(chan, conf)
elif chan.get_state() == channel_states.FORCE_CLOSING:
force_close_tx = chan.force_close_tx()
@@ -757,19 +724,6 @@ class LNWallet(LNWorker):
await self.network.try_broadcasting(force_close_tx, 'force-close')
- async def update_closed_channel(self, chan, funding_txid, funding_height, closing_txid, closing_height, keep_watching):
-
- if chan.get_state() < channel_states.CLOSED:
- conf = closing_height.conf
- if conf > 0:
- chan.set_state(channel_states.CLOSED)
- else:
- # we must not trust the server with unconfirmed transactions
- # if the remote force closed, we remain OPEN until the closing tx is confirmed
- pass
-
- if chan.get_state() == channel_states.CLOSED and not keep_watching:
- chan.set_state(channel_states.REDEEMED)
@log_exceptions