|
|
@ -9,11 +9,12 @@ from collections import OrderedDict, defaultdict |
|
|
|
import asyncio |
|
|
|
import os |
|
|
|
import time |
|
|
|
from typing import Tuple, Dict, TYPE_CHECKING, Optional, Union |
|
|
|
from typing import Tuple, Dict, TYPE_CHECKING, Optional, Union, Set |
|
|
|
from datetime import datetime |
|
|
|
import functools |
|
|
|
|
|
|
|
import aiorpcx |
|
|
|
from aiorpcx import TaskGroup |
|
|
|
|
|
|
|
from .crypto import sha256, sha256d |
|
|
|
from . import bitcoin, util |
|
|
@ -74,6 +75,7 @@ class Peer(Logger): |
|
|
|
self._sent_init = False # type: bool |
|
|
|
self._received_init = False # type: bool |
|
|
|
self.initialized = asyncio.Future() |
|
|
|
self.got_disconnected = asyncio.Event() |
|
|
|
self.querying = asyncio.Event() |
|
|
|
self.transport = transport |
|
|
|
self.pubkey = pubkey # remote pubkey |
|
|
@ -98,6 +100,11 @@ class Peer(Logger): |
|
|
|
self.orphan_channel_updates = OrderedDict() |
|
|
|
Logger.__init__(self) |
|
|
|
self.taskgroup = SilentTaskGroup() |
|
|
|
# HTLCs offered by REMOTE, that we started removing but are still active: |
|
|
|
self.received_htlcs_pending_removal = set() # type: Set[Tuple[Channel, int]] |
|
|
|
self.received_htlc_removed_event = asyncio.Event() |
|
|
|
self._htlc_switch_iterstart_event = asyncio.Event() |
|
|
|
self._htlc_switch_iterdone_event = asyncio.Event() |
|
|
|
|
|
|
|
def send_message(self, message_name: str, **kwargs): |
|
|
|
assert type(message_name) is str |
|
|
@ -492,6 +499,7 @@ class Peer(Logger): |
|
|
|
except: |
|
|
|
pass |
|
|
|
self.lnworker.peer_closed(self) |
|
|
|
self.got_disconnected.set() |
|
|
|
|
|
|
|
def is_static_remotekey(self): |
|
|
|
return self.features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT) |
|
|
@ -1575,6 +1583,7 @@ class Peer(Logger): |
|
|
|
self.logger.info(f"_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") |
|
|
|
assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" |
|
|
|
assert chan.hm.is_htlc_irrevocably_added_yet(htlc_proposer=REMOTE, htlc_id=htlc_id) |
|
|
|
self.received_htlcs_pending_removal.add((chan, htlc_id)) |
|
|
|
chan.settle_htlc(preimage, htlc_id) |
|
|
|
self.send_message( |
|
|
|
"update_fulfill_htlc", |
|
|
@ -1585,6 +1594,7 @@ class Peer(Logger): |
|
|
|
def fail_htlc(self, *, chan: Channel, htlc_id: int, error_bytes: bytes): |
|
|
|
self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.") |
|
|
|
assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" |
|
|
|
self.received_htlcs_pending_removal.add((chan, htlc_id)) |
|
|
|
chan.fail_htlc(htlc_id) |
|
|
|
self.send_message( |
|
|
|
"update_fail_htlc", |
|
|
@ -1596,9 +1606,10 @@ class Peer(Logger): |
|
|
|
def fail_malformed_htlc(self, *, chan: Channel, htlc_id: int, reason: OnionRoutingFailure): |
|
|
|
self.logger.info(f"fail_malformed_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.") |
|
|
|
assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" |
|
|
|
chan.fail_htlc(htlc_id) |
|
|
|
if not (reason.code & OnionFailureCodeMetaFlag.BADONION and len(reason.data) == 32): |
|
|
|
raise Exception(f"unexpected reason when sending 'update_fail_malformed_htlc': {reason!r}") |
|
|
|
self.received_htlcs_pending_removal.add((chan, htlc_id)) |
|
|
|
chan.fail_htlc(htlc_id) |
|
|
|
self.send_message( |
|
|
|
"update_fail_malformed_htlc", |
|
|
|
channel_id=chan.channel_id, |
|
|
@ -1800,8 +1811,13 @@ class Peer(Logger): |
|
|
|
async def htlc_switch(self): |
|
|
|
await self.initialized |
|
|
|
while True: |
|
|
|
await asyncio.sleep(0.1) |
|
|
|
self._htlc_switch_iterdone_event.set() |
|
|
|
self._htlc_switch_iterdone_event.clear() |
|
|
|
await asyncio.sleep(0.1) # TODO maybe make this partly event-driven |
|
|
|
self._htlc_switch_iterstart_event.set() |
|
|
|
self._htlc_switch_iterstart_event.clear() |
|
|
|
self.ping_if_required() |
|
|
|
self._maybe_cleanup_received_htlcs_pending_removal() |
|
|
|
for chan_id, chan in self.channels.items(): |
|
|
|
if not chan.can_send_ctx_updates(): |
|
|
|
continue |
|
|
@ -1853,6 +1869,29 @@ class Peer(Logger): |
|
|
|
for htlc_id in done: |
|
|
|
unfulfilled.pop(htlc_id) |
|
|
|
|
|
|
|
def _maybe_cleanup_received_htlcs_pending_removal(self) -> None: |
|
|
|
done = set() |
|
|
|
for chan, htlc_id in self.received_htlcs_pending_removal: |
|
|
|
if chan.hm.is_htlc_irrevocably_removed_yet(htlc_proposer=REMOTE, htlc_id=htlc_id): |
|
|
|
done.add((chan, htlc_id)) |
|
|
|
if done: |
|
|
|
for key in done: |
|
|
|
self.received_htlcs_pending_removal.remove(key) |
|
|
|
self.received_htlc_removed_event.set() |
|
|
|
self.received_htlc_removed_event.clear() |
|
|
|
|
|
|
|
async def wait_one_htlc_switch_iteration(self) -> None: |
|
|
|
"""Waits until the HTLC switch does a full iteration or the peer disconnects, |
|
|
|
whichever happens first. |
|
|
|
""" |
|
|
|
async def htlc_switch_iteration(): |
|
|
|
await self._htlc_switch_iterstart_event.wait() |
|
|
|
await self._htlc_switch_iterdone_event.wait() |
|
|
|
|
|
|
|
async with TaskGroup(wait=any) as group: |
|
|
|
await group.spawn(htlc_switch_iteration()) |
|
|
|
await group.spawn(self.got_disconnected.wait()) |
|
|
|
|
|
|
|
async def process_unfulfilled_htlc( |
|
|
|
self, *, |
|
|
|
chan: Channel, |
|
|
|