From 556b98736e78eb04bc68fd44e701e3fa174023a9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 21 Feb 2022 18:09:45 +0100 Subject: [PATCH] lnworker.try_force_closing: changed to not be async (and renamed) This is to ensure that the channel is "immediately" set to FORCE_CLOSING. (previously it took at least one event loop iteration) --- electrum/lnpeer.py | 6 +++--- electrum/lnworker.py | 13 +++++++++---- electrum/tests/test_lnpeer.py | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 1b918d785..a35b72b35 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -1052,7 +1052,7 @@ class Peer(Logger): fut.set_exception(RemoteMisbehaving("remote ahead of us")) elif we_are_ahead: self.logger.warning(f"channel_reestablish ({chan.get_id_for_log()}): we are ahead of remote! trying to force-close.") - asyncio.ensure_future(self.lnworker.try_force_closing(chan.channel_id)) + self.lnworker.schedule_force_closing(chan.channel_id) fut.set_exception(RemoteMisbehaving("we are ahead of remote")) else: # all good @@ -1382,7 +1382,7 @@ class Peer(Logger): self.logger.info(f"on_update_fail_malformed_htlc. chan {chan.get_id_for_log()}. " f"htlc_id {htlc_id}. failure_code={failure_code}") if failure_code & OnionFailureCodeMetaFlag.BADONION == 0: - asyncio.ensure_future(self.lnworker.try_force_closing(chan.channel_id)) + self.lnworker.schedule_force_closing(chan.channel_id) raise RemoteMisbehaving(f"received update_fail_malformed_htlc with unexpected failure code: {failure_code}") reason = OnionRoutingFailure(code=failure_code, data=payload["sha256_of_onion"]) chan.receive_fail_htlc(htlc_id, error_bytes=None, reason=reason) @@ -1404,7 +1404,7 @@ class Peer(Logger): if chan.get_state() != ChannelState.OPEN: raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()!r}") if cltv_expiry > bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX: - asyncio.ensure_future(self.lnworker.try_force_closing(chan.channel_id)) + self.lnworker.schedule_force_closing(chan.channel_id) raise RemoteMisbehaving(f"received update_add_htlc with cltv_expiry > BLOCKHEIGHT_MAX. value was {cltv_expiry}") # add htlc chan.receive_htlc(htlc, onion_packet) diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 413c86f28..f72b88138 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -966,7 +966,7 @@ class LNWallet(LNWorker): if chan.get_state() == ChannelState.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) + await self.schedule_force_closing(chan.channel_id) elif chan.get_state() == ChannelState.FUNDED: peer = self._peers.get(chan.node_id) @@ -2109,16 +2109,21 @@ class LNWallet(LNWorker): """Force-close the channel. Network-related exceptions are propagated to the caller. (automatic rebroadcasts will be scheduled) """ + # note: as we are async, it can take a few event loop iterations between the caller + # "calling us" and us getting to run, and we only set the channel state now: tx = self._force_close_channel(chan_id) await self.network.broadcast_transaction(tx) return tx.txid() - async def try_force_closing(self, chan_id: bytes) -> None: - """Force-close the channel. Network-related exceptions are suppressed. + def schedule_force_closing(self, chan_id: bytes) -> 'asyncio.Task[None]': + """Schedules a task to force-close the channel and returns it. + Network-related exceptions are suppressed. (automatic rebroadcasts will be scheduled) + Note: this method is intentionally not async so that callers have a guarantee + that the channel state is set immediately. """ tx = self._force_close_channel(chan_id) - await self.network.try_broadcasting(tx, 'force-close') + return asyncio.create_task(self.network.try_broadcasting(tx, 'force-close')) def remove_channel(self, chan_id): chan = self.channels[chan_id] diff --git a/electrum/tests/test_lnpeer.py b/electrum/tests/test_lnpeer.py index 7a98e7ead..2d5bbb33a 100644 --- a/electrum/tests/test_lnpeer.py +++ b/electrum/tests/test_lnpeer.py @@ -231,7 +231,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]): pay_to_node = LNWallet.pay_to_node pay_invoice = LNWallet.pay_invoice force_close_channel = LNWallet.force_close_channel - try_force_closing = LNWallet.try_force_closing + schedule_force_closing = LNWallet.schedule_force_closing get_first_timestamp = lambda self: 0 on_peer_successfully_established = LNWallet.on_peer_successfully_established get_channel_by_id = LNWallet.get_channel_by_id