Skip to content

backend support for 'close reason'#10578

Open
wdiffman wants to merge 2 commits intospesmilo:masterfrom
wdiffman:close_reason
Open

backend support for 'close reason'#10578
wdiffman wants to merge 2 commits intospesmilo:masterfrom
wdiffman:close_reason

Conversation

@wdiffman
Copy link
Copy Markdown

Closes #10389 following @SomberNight 's suggestion. Used reason instead of diagnosis, though.
In the test, bob's close reason isn't guaranteed to be set by the time alice's close_channel returns, so I used a short sleep; please advise if there is a better way.

qt frontent support for 'close reason'

fix test
Copy link
Copy Markdown
Member

@SomberNight SomberNight left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey. Thanks for the PR. Added some comments.

Comment thread electrum/lnchannel.py
Comment on lines 340 to +344
if who_closed == LOCAL:
self.logger.info(f'we (local) force closed')
elif who_closed == REMOTE:
self.logger.info(f'they (remote) force closed.')
self.save_close_reason(ChanCloseReason.REMOTE_FORCE)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also set the close reason in the LOCAL case here.
First of all, it is nicer to make the code (in the local scope) symmetric/homogeneous.
But also, ultimately the final close reason should reflect what gets mined and weird chains of events could happen: e.g. local force-close gets broadcast to mempool first, then remote force-close replaces it in mempool, but then local force-close gets mined. If you call save_close_reason here, it will self-heal.

We could keep the other call in lnworker._force_close_channel in addition to this one, as that one runs much earlier.

Comment thread electrum/gui/qt/channels_list.py Outdated
LOCAL_BALANCE = enum.auto()
REMOTE_BALANCE = enum.auto()
CHANNEL_STATUS = enum.auto()
CLOSE_REASON = enum.auto()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it does not make sense to add a column for this. It takes a lot of horizontal space and adds noise.
Isn't it enough to show this in the channel details dialog?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I only added because I thought someone might want to search for channels closed with a given reason. But if that's the case, we can fix it later. I will remove.

Comment thread electrum/commands.py Outdated
'remote_reserve': chan.config[LOCAL].reserve_sat,
'local_unsettled_sent': chan.balance_tied_up_in_htlcs_by_direction(LOCAL, direction=SENT) // 1000,
'remote_unsettled_sent': chan.balance_tied_up_in_htlcs_by_direction(REMOTE, direction=SENT) // 1000,
'close_reason': (r := chan.get_close_reason()) and r.name or None, # Harder to read, but keeps it within a single line.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this would make the names of the enum members part of the external API

also see other comment re wallet db upgrades

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re the comment # Harder to read, but keeps it within a single line. -- ok, but the comment itself is not needed IMO

Comment thread electrum/lnchannel.py
_who_closed: Optional[int] = None # HTLCOwner (1 or -1). 0 means "unknown"

def save_close_reason(self, reason: ChanCloseReason) -> None:
self.storage['close_reason'] = reason.name
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would make the names of the enum members part of the wallet db. That is, if someone later renames e.g. ChanCloseReason.LOCAL_FORCE to ChanCloseReason.LOCAL_FORCE_CLOSE, they need to do a wallet db upgrade.

IMO it is preferable to use the names and not the int values in the wallet db, so this is fine, but it is completely unintuitive and error-prone for the programmer, so at least a comment must be added.

Just duplicate this comment:

# Note: these states are persisted by name (for a given channel) in the wallet file,
# so consider doing a wallet db upgrade when changing them.

Comment thread tests/test_lnpeer.py Outdated
await util.wait_for2(p2.initialized, 1)
await p1.close_channel(alice_channel.channel_id)
# alice's side is completed, but bob need to wait to see the reason.
await asyncio.sleep(1) # FIXME: use a better wait
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my machine, the test passes even with this sleep completely removed. Is that not the case for you?

Anyway, the simplest less-wasteful thing that could be done is:

            async with util.async_timeout(1):
                while not bob_channel.is_closed_or_closing():
                    await asyncio.sleep(0.01)

Something more complicated:

diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index a4950ba9d5..b65f78a041 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -1636,12 +1636,21 @@ class TestPeerDirect(TestPeer):
         w2.network.config.TEST_SHUTDOWN_FEE = 100
         w1.network.config.TEST_SHUTDOWN_LEGACY = True
         w2.network.config.TEST_SHUTDOWN_LEGACY = True
+
+        any_chan_changed = asyncio.Event()
+        async def on_chan_changed(*args):
+            any_chan_changed.set()
+            any_chan_changed.clear()
+        util.register_callback(on_chan_changed, ["channel"])
+
         async def action():
             await util.wait_for2(p1.initialized, 1)
             await util.wait_for2(p2.initialized, 1)
             await p1.close_channel(alice_channel.channel_id)
             # alice's side is completed, but bob need to wait to see the reason.
-            await asyncio.sleep(1)  # FIXME: use a better wait
+            async with util.async_timeout(1):
+                while not bob_channel.is_closed_or_closing():
+                    await any_chan_changed.wait()
             self.assertEqual(alice_channel.get_close_reason(), ChanCloseReason.LOCAL_COOP)
             self.assertEqual(bob_channel.get_close_reason(), ChanCloseReason.REMOTE_COOP)
             gath.cancel()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it does, but that's will not be necessarily true for all runs.
I will use the simple wait, but thanks for sharing the more complete version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

How to tell if channel was closed by local or remote in JSON-RPC API?

2 participants