Skip to content

lightning: support error and warning messages#7645

Closed
bitromortac wants to merge 4 commits into
spesmilo:masterfrom
bitromortac:2112-warning-messages
Closed

lightning: support error and warning messages#7645
bitromortac wants to merge 4 commits into
spesmilo:masterfrom
bitromortac:2112-warning-messages

Conversation

@bitromortac
Copy link
Copy Markdown
Contributor

Partially takes care about #7624.

Adds sending of error and warning messages as well as handling of warning messages.

Currently in the codebase ~anytime we encounter errors we just raise exceptions, but most of the time we should send errors (and force close) or send a warning and close the connection. How this could be done is to directly call send_error or send_warning or, if some violation in a helper function happens, one could raise LNProtocolError/LNProtocolWarning and convert them to the messages in the outer scope. In principle, one should go through the entire spec and handle those failures, however, because I wanted to first get feedback about the API, I only included handling of the open_channel and shutdown messages as examples. When converting exceptions one should be careful not to send secret data.

Comment thread electrum/lnpeer.py Outdated
Comment thread electrum/lnpeer.py Outdated
Comment thread electrum/lnpeer.py Outdated
# channel_id of zero means that the error refers to all channels
if channel_id == bytes(32):
for channel_id in self.channels:
await self.lnworker.force_close_channel(channel_id)
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.

shouldn't this also be guarded with force_close_channel?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have channel force closing in both scenarios. The case with all-zero bits is a bit dangerous.

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.

Sorry, I'm not sure I understand.
My question is this: What is the expected behaviour of calling peer.send_error(bytes(32), force_close_channel=False)?
Right now, AFAICT, it would fclose the channels.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah sorry, I misunderstood, it is guarded now. The spec says we must force close upon sending an error (individual or all channels), but I would keep the force_close_channel parameter nevertheless.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Another question is if we really should allow force closing of all channels if we receive the zero channel_id, it looks dangerous.

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.

The BOLT says we must fail channels with the sending node. If the sending node wants all their channels closed, there is not much we can do... Are you concerned about malicious behaviour by the remote, or programming bugs in the remote's implementation, or something else?

Re malicious behaviour, I was thinking we might introduce Peer.force_close_channel and Peer.try_force_closing as wrappers for LNWorker.force_close_channel and LNWorker.try_force_closing, that would enforce that the channel_id corresponds to a channel with this Peer. This does not have to be a part of this PR though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The BOLT says we must fail channels with the sending node. If the sending node wants all their channels closed, there is not much we can do... Are you concerned about malicious behaviour by the remote, or programming bugs in the remote's implementation, or something else?

Yes, mostly about something that could trigger another node sending the zero message (or all nodes in the network across multiple implementations :P).

Re malicious behaviour, I was thinking we might introduce Peer.force_close_channel and Peer.try_force_closing as wrappers for LNWorker.force_close_channel and LNWorker.try_force_closing, that would enforce that the channel_id corresponds to a channel with this Peer. This does not have to be a part of this PR though.

Sounds useful, added the wrapper and used it instead.

Copy link
Copy Markdown
Contributor Author

@bitromortac bitromortac Feb 21, 2022

Choose a reason for hiding this comment

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

Should we also loop over the temporary channel ids as well? Oh and needs a rebase...

@bitromortac bitromortac force-pushed the 2112-warning-messages branch from e94e44b to 67fcf85 Compare January 31, 2022 13:25
@spesmilo spesmilo deleted a comment from dangvietquang Feb 8, 2022
@spesmilo spesmilo deleted a comment from dangvietquang Feb 8, 2022
Comment thread electrum/lnpeer.py Outdated
Comment thread electrum/lnpeer.py Outdated
@bitromortac bitromortac force-pushed the 2112-warning-messages branch 2 times, most recently from 6338701 to 120f71e Compare February 18, 2022 10:39
Comment thread electrum/lnpeer.py Outdated
Comment thread electrum/lnpeer.py Outdated
@bitromortac bitromortac force-pushed the 2112-warning-messages branch 3 times, most recently from 3cce707 to 257d1c6 Compare February 21, 2022 13:28
@ecdsa ecdsa mentioned this pull request Feb 21, 2022
3 tasks
Comment thread electrum/lnpeer.py
Comment thread electrum/lnpeer.py Outdated
Comment thread electrum/lnpeer.py
@bitromortac bitromortac force-pushed the 2112-warning-messages branch 2 times, most recently from 4f84c37 to 3a4b977 Compare February 22, 2022 14:30
* adds methods for sending protocol errors/warnings
* handling of warning messages
The exceptions are meant to be raised in places where the BOLTs require
the sending of warning or error messages. They are necessary to handle
protocol failures occuring helper functions that check constraints.
@bitromortac bitromortac force-pushed the 2112-warning-messages branch from 3a4b977 to e67070c Compare February 22, 2022 16:25
@ecdsa
Copy link
Copy Markdown
Member

ecdsa commented Mar 9, 2022

Please note that before this PR, an exception is raised in wait_for_message if an error is sent to the queue.
This PR raises a GracefulDisconnect exception in both on_error and wait_for_message, I think that it is redundant, we can raise it in on_error only. We still need to push an error in the queue, in order to terminate any pending wait_for_message task.

1 similar comment
@ecdsa
Copy link
Copy Markdown
Member

ecdsa commented Mar 9, 2022

Please note that before this PR, an exception is raised in wait_for_message if an error is sent to the queue.
This PR raises a GracefulDisconnect exception in both on_error and wait_for_message, I think that it is redundant, we can raise it in on_error only. We still need to push an error in the queue, in order to terminate any pending wait_for_message task.

@ecdsa
Copy link
Copy Markdown
Member

ecdsa commented Mar 9, 2022

PR rebased and merged manually, after minor modifications: 61eebb2 and 47917d9

@ecdsa ecdsa closed this Mar 9, 2022
SomberNight added a commit to SomberNight/electrum that referenced this pull request Mar 17, 2022
- rm the `_get_channel_ids` abstraction as each of its usages needs subtle differences.
  Some code duplication is preferable in this case.
- raise exceptions in `wait_for_message`, so that callers such as the GUI can show user-feedback
- on_error/on_warning were dropping messages with temp_chan_ids if they were not stored in
  `temp_id_to_id` - which was only done once the mapping was known (so the normal chan_id was known).
  To fix this, we now store temp_chan_ids into `temp_id_to_id` early.

related:
spesmilo#7645 (and related commits)

-----

example before commit:
```
D/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | Sending OPEN_CHANNEL
D/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | Received ERROR
I/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)

E | gui.qt.main_window.[test_segwit_2] | Could not open channel
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 661, in wrapper
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 742, in channel_establishment_flow
    payload = await self.wait_for_message('accept_channel', temp_channel_id)  #
  File "...\electrum\electrum\lnpeer.py", line 315, in wait_for_message
    name, payload = await asyncio.wait_for(q.get(), LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 468, in wait_for
    await waiter
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...\Python39\lib\asyncio\tasks.py", line 492, in wait_for
    fut.result()
asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "...\electrum\electrum\gui\qt\util.py", line 914, in run
    result = task.task()
  File "...\electrum\electrum\gui\qt\main_window.py", line 1875, in task
    return self.wallet.lnworker.open_channel(
  File "...\electrum\electrum\lnworker.py", line 1075, in open_channel
    chan, funding_tx = fut.result()
  File "...\Python39\lib\concurrent\futures\_base.py", line 445, in result
    return self.__get_result()
  File "...\Python39\lib\concurrent\futures\_base.py", line 390, in __get_result
    raise self._exception
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnworker.py", line 1006, in _open_channel_coroutine
    chan, funding_tx = await asyncio.wait_for(coro, LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 494, in wait_for
    raise exceptions.TimeoutError() from exc
asyncio.exceptions.TimeoutError
```

example after commit:
```
D/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Sending OPEN_CHANNEL
D/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Received ERROR
I/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat). chan_id=124ca21fa6aa2993430ad71f465f0d44731ef87f7478e4b31327e4459b5a3988
E | lnworker.LNWallet.[test_segwit_2] | Exception in _open_channel_coroutine: GracefulDisconnect('remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)')
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnworker.py", line 1006, in _open_channel_coroutine
    chan, funding_tx = await asyncio.wait_for(coro, LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 481, in wait_for
    return fut.result()
  File "...\electrum\electrum\lnpeer.py", line 673, in wrapper
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 755, in channel_establishment_flow
    payload = await self.wait_for_message('accept_channel', temp_channel_id)
  File "...\electrum\electrum\lnpeer.py", line 326, in wait_for_message
    raise GracefulDisconnect(
electrum.interface.GracefulDisconnect: remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)
I/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Disconnecting: GracefulDisconnect()
```
SomberNight added a commit to SomberNight/electrum that referenced this pull request Mar 17, 2022
- rm the `_get_channel_ids` abstraction as each of its usages needs subtle differences.
  Some code duplication is preferable in this case.
- raise exceptions in `wait_for_message`, so that callers such as the GUI can show user-feedback
- on_error/on_warning were dropping messages with temp_chan_ids if they were not stored in
  `temp_id_to_id` - which was only done once the mapping was known (so the normal chan_id was known).
  To fix this, we now store temp_chan_ids into `temp_id_to_id` early.
- `schedule_force_closing` only works if the chan_id is already in `channels`

related:
spesmilo#7645 (and related commits)

-----

example before commit:
```
D/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | Sending OPEN_CHANNEL
D/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | Received ERROR
I/P | lnpeer.Peer.[LNWallet, 03933884aa-3b53e4ab] | remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)

E | gui.qt.main_window.[test_segwit_2] | Could not open channel
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 661, in wrapper
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 742, in channel_establishment_flow
    payload = await self.wait_for_message('accept_channel', temp_channel_id)  #
  File "...\electrum\electrum\lnpeer.py", line 315, in wait_for_message
    name, payload = await asyncio.wait_for(q.get(), LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 468, in wait_for
    await waiter
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "...\Python39\lib\asyncio\tasks.py", line 492, in wait_for
    fut.result()
asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "...\electrum\electrum\gui\qt\util.py", line 914, in run
    result = task.task()
  File "...\electrum\electrum\gui\qt\main_window.py", line 1875, in task
    return self.wallet.lnworker.open_channel(
  File "...\electrum\electrum\lnworker.py", line 1075, in open_channel
    chan, funding_tx = fut.result()
  File "...\Python39\lib\concurrent\futures\_base.py", line 445, in result
    return self.__get_result()
  File "...\Python39\lib\concurrent\futures\_base.py", line 390, in __get_result
    raise self._exception
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnworker.py", line 1006, in _open_channel_coroutine
    chan, funding_tx = await asyncio.wait_for(coro, LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 494, in wait_for
    raise exceptions.TimeoutError() from exc
asyncio.exceptions.TimeoutError
```

example after commit:
```
D/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Sending OPEN_CHANNEL
D/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Received ERROR
I/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat). chan_id=124ca21fa6aa2993430ad71f465f0d44731ef87f7478e4b31327e4459b5a3988
E | lnworker.LNWallet.[test_segwit_2] | Exception in _open_channel_coroutine: GracefulDisconnect('remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)')
Traceback (most recent call last):
  File "...\electrum\electrum\util.py", line 1160, in wrapper
    return await func(*args, **kwargs)
  File "...\electrum\electrum\lnworker.py", line 1006, in _open_channel_coroutine
    chan, funding_tx = await asyncio.wait_for(coro, LN_P2P_NETWORK_TIMEOUT)
  File "...\Python39\lib\asyncio\tasks.py", line 481, in wait_for
    return fut.result()
  File "...\electrum\electrum\lnpeer.py", line 673, in wrapper
    return await func(self, *args, **kwargs)
  File "...\electrum\electrum\lnpeer.py", line 755, in channel_establishment_flow
    payload = await self.wait_for_message('accept_channel', temp_channel_id)
  File "...\electrum\electrum\lnpeer.py", line 326, in wait_for_message
    raise GracefulDisconnect(
electrum.interface.GracefulDisconnect: remote peer sent error [DO NOT TRUST THIS MESSAGE]: invalid funding_satoshis=10000 sat (min=400000 sat max=1500000000 sat)
I/P | lnpeer.Peer.[LNWallet, 03933884aa-ff3a866f] | Disconnecting: GracefulDisconnect()
```
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.

3 participants