• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spesmilo / electrum / 28939308758

08 Jul 2026 11:26AM UTC coverage: 65.707% (-0.005%) from 65.712%
28939308758

push

github

SomberNight
release notes: bump 4.8.0 date for second attempt

We failed to get a full quorum to reproduce the prior git tag.
The android apks were problematic to reproducibly build.
should be fixed by https://github.com/spesmilo/electrum/pull/10739

here we go again

25173 of 38311 relevant lines covered (65.71%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.21
/electrum/lnpeer.py
1
#!/usr/bin/env python3
2
#
3
# Copyright (C) 2018 The Electrum developers
4
# Distributed under the MIT software license, see the accompanying
5
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
6

7
from collections import OrderedDict, defaultdict
1✔
8
import asyncio
1✔
9
import os
1✔
10
import time
1✔
11
from typing import Tuple, Dict, TYPE_CHECKING, Optional, Union, Set, Callable, Coroutine, List, Any
1✔
12
from datetime import datetime
1✔
13
import functools
1✔
14
from functools import partial
1✔
15
import inspect
1✔
16

17
import electrum_ecc as ecc
1✔
18
from electrum_ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig64, ECPubkey
1✔
19

20
import aiorpcx
1✔
21
from aiorpcx import ignore_after
1✔
22

23
from .lrucache import LRUCache
1✔
24
from .crypto import sha256, sha256d, privkey_to_pubkey
1✔
25
from . import bitcoin, util
1✔
26
from . import constants
1✔
27
from .util import (log_exceptions, ignore_exceptions, chunks, OldTaskGroup,
1✔
28
                   UnrelatedTransactionException, error_text_bytes_to_safe_str, AsyncHangDetector,
29
                   NoDynamicFeeEstimates, event_listener, EventListener)
30
from . import transaction
1✔
31
from .bitcoin import make_op_return, DummyAddress
1✔
32
from .transaction import PartialTxOutput, match_script_against_template, Sighash
1✔
33
from .logging import Logger
1✔
34
from . import lnonion
1✔
35
from .lnonion import (OnionFailureCode, OnionPacket, obfuscate_onion_error,
1✔
36
                      OnionRoutingFailure, ProcessedOnionPacket, UnsupportedOnionPacketVersion,
37
                      InvalidOnionMac, InvalidOnionPubkey, OnionFailureCodeMetaFlag,
38
                      OnionParsingError)
39
from .lnchannel import Channel, RevokeAndAck, ChannelState, PeerState, ChanCloseOption, CF_ANNOUNCE_CHANNEL
1✔
40
from . import lnutil
1✔
41
from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConfig, LnFeatureContexts,
1✔
42
                     RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
43
                     funding_output_script, get_per_commitment_secret_from_seed,
44
                     secret_to_pubkey, PaymentFailure, LnFeatures,
45
                     LOCAL, REMOTE, HTLCOwner,
46
                     ln_compare_features, MIN_FINAL_CLTV_DELTA_ACCEPTED,
47
                     RemoteMisbehaving, ShortChannelID,
48
                     IncompatibleLightningFeatures, ChannelType, LNProtocolWarning, validate_features,
49
                     IncompatibleOrInsaneFeatures, ReceivedMPPStatus, ReceivedMPPHtlc,
50
                     GossipForwardingMessage, GossipTimestampFilter, channel_id_from_funding_tx,
51
                     serialize_htlc_key, Keypair, RecvMPPResolution)
52
from .lntransport import LNTransport, LNTransportBase, LightningPeerConnectionClosed, HandshakeFailed
1✔
53
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
1✔
54
from .interface import GracefulDisconnect
1✔
55
from .invoices import PR_PAID
1✔
56
from .fee_policy import FEE_LN_ETA_TARGET, FEERATE_PER_KW_MIN_RELAY_LIGHTNING
1✔
57
from .channel_db import FLAG_DIRECTION
1✔
58

59
if TYPE_CHECKING:
60
    from .lnworker import LNGossip, LNWallet
61
    from .lnrouter import LNPaymentRoute
62
    from .simple_config import SimpleConfig
63
    from .transaction import PartialTransaction
64

65

66
LN_P2P_NETWORK_TIMEOUT = 20
1✔
67

68

69
class Peer(Logger, EventListener):
1✔
70
    # note: in general this class is NOT thread-safe. Most methods are assumed to be running on asyncio thread.
71

72
    ORDERED_MESSAGES = (
1✔
73
        'accept_channel', 'funding_signed', 'funding_created', 'accept_channel', 'closing_signed')
74
    SPAMMY_MESSAGES = (
1✔
75
        'ping', 'pong', 'channel_announcement', 'node_announcement', 'channel_update',
76
        'gossip_timestamp_filter', 'reply_channel_range', 'query_channel_range',
77
        'query_short_channel_ids', 'reply_short_channel_ids', 'reply_short_channel_ids_end')
78

79
    DELAY_INC_MSG_PROCESSING_SLEEP = 0.01
1✔
80
    MIN_TIME_BETWEEN_SENDING_COMMITSIGS = 0.05
1✔
81
    RECV_GOSSIP_QUEUE_SOFT_MAXSIZE = 2000
1✔
82
    RECV_GOSSIP_QUEUE_HARD_MAXSIZE = 5000
1✔
83

84
    def __init__(
1✔
85
            self,
86
            lnworker: Union['LNWallet', 'LNGossip'],
87
            pubkey: bytes,
88
            transport: LNTransportBase,
89
            *, is_channel_backup= False):
90

91
        self.lnworker = lnworker
1✔
92
        self.network = lnworker.network
1✔
93
        self.asyncio_loop = self.network.asyncio_loop
1✔
94
        self.is_channel_backup = is_channel_backup
1✔
95
        self._sent_init = False  # type: bool
1✔
96
        self._received_init = False  # type: bool
1✔
97
        self.initialized = self.asyncio_loop.create_future()
1✔
98
        self.got_disconnected = asyncio.Event()
1✔
99
        self.querying = asyncio.Event()
1✔
100
        self.transport = transport
1✔
101
        self.pubkey = pubkey  # remote pubkey
1✔
102
        self.privkey = self.transport.privkey  # local privkey
1✔
103
        self.features = self.lnworker.features  # type: LnFeatures
1✔
104
        if lnworker == lnworker.network.lngossip or \
1✔
105
            self.config.ZEROCONF_TRUSTED_NODE and pubkey != lnworker.trusted_zeroconf_node_id:
106
            # don't signal zeroconf support if we are client (a trusted node is configured),
107
            # and Peer is not our trusted node
108
            self.features &= ~LnFeatures.OPTION_ZEROCONF_OPT
1✔
109
        self.their_features = LnFeatures(0)  # type: LnFeatures
1✔
110
        self.node_ids = [self.pubkey, privkey_to_pubkey(self.privkey)]
1✔
111
        assert self.node_ids[0] != self.node_ids[1]
1✔
112
        self.last_message_time = 0
1✔
113
        self.pong_event = asyncio.Event()
1✔
114
        self.reply_channel_range = None  # type: Optional[asyncio.Queue]
1✔
115
        # gossip uses a single queue to preserve message order
116
        self.recv_gossip_queue = asyncio.Queue(maxsize=self.RECV_GOSSIP_QUEUE_HARD_MAXSIZE)
1✔
117
        self.our_gossip_timestamp_filter = None  # type: Optional[GossipTimestampFilter]
1✔
118
        self.their_gossip_timestamp_filter = None  # type: Optional[GossipTimestampFilter]
1✔
119
        self.outgoing_gossip_reply = False # type: bool
1✔
120
        self.ordered_message_queues = defaultdict(partial(asyncio.Queue, maxsize=10))  # type: Dict[bytes, asyncio.Queue] # for messages that are ordered
1✔
121
        self.temp_id_to_id = {}  # type: Dict[bytes, Optional[bytes]]   # to forward error messages
1✔
122
        self.funding_created_sent = set() # for channels in PREOPENING
1✔
123
        self.funding_signed_sent = set()  # for channels in PREOPENING
1✔
124
        self.shutdown_received = {} # chan_id -> asyncio.Future()
1✔
125
        self.channel_reestablish_msg = defaultdict(self.asyncio_loop.create_future)  # type: Dict[bytes, asyncio.Future]
1✔
126
        self._chan_reest_finished = defaultdict(asyncio.Event)  # type: Dict[bytes, asyncio.Event]
1✔
127
        self.orphan_channel_updates = OrderedDict()  # type: OrderedDict[ShortChannelID, dict]
1✔
128
        Logger.__init__(self)
1✔
129
        self.taskgroup = OldTaskGroup()
1✔
130
        # HTLCs offered by REMOTE, that we started removing but are still active:
131
        self.received_htlcs_pending_removal = set()  # type: Set[Tuple[Channel, int]]
1✔
132
        self.received_htlc_removed_event = asyncio.Event()
1✔
133
        self._htlc_switch_iterstart_event = asyncio.Event()
1✔
134
        self._htlc_switch_iterdone_event = asyncio.Event()
1✔
135
        self._received_revack_event = asyncio.Event()
1✔
136
        self.received_commitsig_event = asyncio.Event()
1✔
137
        self.downstream_htlc_resolved_event = asyncio.Event()
1✔
138
        self.register_callbacks()
1✔
139
        self._num_gossip_messages_forwarded = 0
1✔
140
        self._processed_onion_cache = LRUCache(maxsize=100)  # type: LRUCache[bytes, ProcessedOnionPacket]
1✔
141
        self._last_commitsig_sent_time = time.monotonic()
1✔
142
        self._last_ping_recv_time = min(0, time.monotonic())
1✔
143

144
    def send_message(self, message_name: str, **kwargs):
1✔
145
        assert util.get_running_loop() == util.get_asyncio_loop(), f"this must be run on the asyncio thread!"
1✔
146
        assert type(message_name) is str
1✔
147
        if message_name not in self.SPAMMY_MESSAGES:
1✔
148
            self.logger.debug(f"Sending {message_name.upper()}")
1✔
149
        if message_name.upper() != "INIT" and not self.is_initialized():
1✔
150
            raise Exception("tried to send message before we are initialized")
×
151
        raw_msg = encode_msg(message_name, **kwargs)
1✔
152
        self._store_raw_msg_if_local_update(raw_msg, message_name=message_name, channel_id=kwargs.get("channel_id"))
1✔
153
        self.transport.send_bytes(raw_msg)
1✔
154
        # could `await self.transport.writer.drain()`, but not async
155

156
    def _store_raw_msg_if_local_update(self, raw_msg: bytes, *, message_name: str, channel_id: Optional[bytes]):
1✔
157
        is_commitment_signed = message_name == "commitment_signed"
1✔
158
        if not (message_name.startswith("update_") or is_commitment_signed):
1✔
159
            return
1✔
160
        assert channel_id
1✔
161
        chan = self.get_channel_by_id(channel_id)
1✔
162
        if not chan:
1✔
163
            raise Exception(f"channel {channel_id.hex()} not found for peer {self.pubkey.hex()}")
×
164
        chan.hm.store_local_update_raw_msg(raw_msg, is_commitment_signed=is_commitment_signed)
1✔
165
        if is_commitment_signed:
1✔
166
            # saving now, to ensure replaying updates works (in case of channel reestablishment)
167
            self.lnworker.save_channel(chan)
1✔
168

169
    def maybe_set_initialized(self):
1✔
170
        if self.initialized.done():
1✔
171
            return
1✔
172
        if self._sent_init and self._received_init:
1✔
173
            self.initialized.set_result(True)
1✔
174

175
    def is_initialized(self) -> bool:
1✔
176
        return (self.initialized.done()
1✔
177
                and not self.initialized.cancelled()
178
                and self.initialized.exception() is None
179
                and self.initialized.result() is True)
180

181
    async def initialize(self):
1✔
182
        # If outgoing transport, do handshake now. For incoming, it has already been done.
183
        if isinstance(self.transport, LNTransport):
1✔
184
            await self.transport.handshake()
×
185
        self.logger.info(f"handshake done for {self.transport.peer_addr or self.pubkey.hex()}")
1✔
186
        features = self.features.for_init_message()
1✔
187
        flen = features.min_len()
1✔
188
        self.send_message(
1✔
189
            "init", gflen=0, flen=flen,
190
            features=features,
191
            init_tlvs={
192
                'networks':
193
                {'chains': constants.net.rev_genesis_bytes()}
194
            })
195
        self._sent_init = True
1✔
196
        self.maybe_set_initialized()
1✔
197

198
    @property
1✔
199
    def channels(self) -> Dict[bytes, Channel]:
1✔
200
        return self.lnworker.channels_for_peer(self.pubkey)
1✔
201

202
    def get_channel_by_id(self, channel_id: bytes) -> Optional[Channel]:
1✔
203
        # note: this is faster than self.channels.get(channel_id)
204
        chan = self.lnworker.get_channel_by_id(channel_id)
1✔
205
        if not chan:
1✔
206
            return None
×
207
        if chan.node_id != self.pubkey:
1✔
208
            return None
1✔
209
        return chan
1✔
210

211
    @property
1✔
212
    def config(self) -> 'SimpleConfig':
1✔
213
        return self.lnworker.config
1✔
214

215
    def diagnostic_name(self):
1✔
216
        lnw_name = self.lnworker.diagnostic_name() or self.lnworker.__class__.__name__
1✔
217
        return lnw_name + ', ' + self.transport.name()
1✔
218

219
    async def ping_if_required(self):
1✔
220
        if time.time() - self.last_message_time > 30:
1✔
221
            self.send_message('ping', num_pong_bytes=4, byteslen=4)
×
222
            self.pong_event.clear()
×
223
            await self.pong_event.wait()
×
224

225
    async def _process_message(self, message: bytes) -> None:
1✔
226
        try:
1✔
227
            message_type, payload = decode_msg(message)
1✔
228
        except UnknownOptionalMsgType as e:
1✔
229
            self.logger.info(f"received unknown message from peer. ignoring: {e!r}")
1✔
230
            return
1✔
231
        except FailedToParseMsg as e:
1✔
232
            self.logger.info(
1✔
233
                f"failed to parse message from peer. disconnecting. "
234
                f"msg_type={e.msg_type_name}({e.msg_type_int}). exc={e!r}")
235
            #self.logger.info(f"failed to parse message: message(SECRET?)={message.hex()}")
236
            raise GracefulDisconnect() from e
1✔
237
        self.last_message_time = time.time()
1✔
238
        if message_type not in self.SPAMMY_MESSAGES:
1✔
239
            self.logger.debug(f"Received {message_type.upper()}")
1✔
240
        # only process INIT if we are a backup
241
        if self.is_channel_backup is True and message_type != 'init':
1✔
242
            return
×
243
        if message_type in self.ORDERED_MESSAGES:
1✔
244
            chan_id = payload.get('channel_id') or payload["temporary_channel_id"]
1✔
245
            if (
1✔
246
                chan_id not in self.channels
247
                and chan_id not in self.temp_id_to_id
248
                and chan_id not in self.temp_id_to_id.values()
249
            ):
250
                raise Exception(f"received {message_type} for unknown {chan_id.hex()=}")
×
251
            self.ordered_message_queues[chan_id].put_nowait((message_type, payload))
1✔
252
        else:
253
            if message_type not in ('error', 'warning') and 'channel_id' in payload:
1✔
254
                chan = self.get_channel_by_id(payload['channel_id'])
1✔
255
                if chan is None:
1✔
256
                    self.logger.info(f"Received {message_type} for unknown channel {payload['channel_id'].hex()}")
×
257
                    return
×
258
                args = (chan, payload)
1✔
259
            else:
260
                args = (payload,)
1✔
261
            try:
1✔
262
                f = getattr(self, 'on_' + message_type)
1✔
263
            except AttributeError:
×
264
                #self.logger.info("Received '%s'" % message_type.upper(), payload)
265
                return
×
266
            # raw message is needed to check signature
267
            if message_type in ['node_announcement', 'channel_announcement', 'channel_update']:
1✔
268
                payload['raw'] = message
1✔
269
                payload['sender_node_id'] = self.pubkey
1✔
270
            # note: the message handler might be async or non-async. In either case, by default,
271
            #       we wait for it to complete before we return, i.e. before the next message is processed.
272
            if inspect.iscoroutinefunction(f):
1✔
273
                async with AsyncHangDetector(
1✔
274
                    message=f"message handler still running for {message_type.upper()}",
275
                    logger=self.logger,
276
                ):
277
                    await f(*args)
1✔
278
            else:
279
                f(*args)
1✔
280

281
    def non_blocking_msg_handler(func):
1✔
282
        """Makes a message handler non-blocking: while processing the message,
283
        the message_loop keeps processing subsequent incoming messages asynchronously.
284
        """
285
        assert inspect.iscoroutinefunction(func), 'func needs to be a coroutine'
1✔
286
        @functools.wraps(func)
1✔
287
        async def wrapper(self: 'Peer', *args, **kwargs):
1✔
288
            return await self.taskgroup.spawn(func(self, *args, **kwargs))
1✔
289
        return wrapper
1✔
290

291
    def on_warning(self, payload):
1✔
292
        chan_id = payload.get("channel_id")
1✔
293
        err_bytes = payload['data']
1✔
294
        is_known_chan_id = (chan_id in self.channels) or (chan_id in self.temp_id_to_id)
1✔
295
        self.logger.info(f"remote peer sent warning [DO NOT TRUST THIS MESSAGE]: "
1✔
296
                         f"{error_text_bytes_to_safe_str(err_bytes, max_len=None)}. chan_id={chan_id.hex()}. "
297
                         f"{is_known_chan_id=}")
298

299
    def on_error(self, payload):
1✔
300
        chan_id = payload.get("channel_id")
1✔
301
        err_bytes = payload['data']
1✔
302
        is_known_chan_id = (chan_id in self.channels) or (chan_id in self.temp_id_to_id)
1✔
303
        self.logger.info(f"remote peer sent error [DO NOT TRUST THIS MESSAGE]: "
1✔
304
                         f"{error_text_bytes_to_safe_str(err_bytes, max_len=None)}. chan_id={chan_id.hex()}. "
305
                         f"{is_known_chan_id=}")
306
        if chan := self.get_channel_by_id(chan_id):
1✔
307
            self.schedule_force_closing(chan_id)
1✔
308
            self.ordered_message_queues[chan_id].put_nowait((None, {'error': err_bytes}))
1✔
309
            chan.save_remote_peer_sent_error(err_bytes)
1✔
310
        elif chan_id in self.temp_id_to_id:
×
311
            chan_id = self.temp_id_to_id[chan_id] or chan_id
×
312
            self.ordered_message_queues[chan_id].put_nowait((None, {'error': err_bytes}))
×
313
        elif chan_id == bytes(32):
×
314
            # if channel_id is all zero:
315
            # - MUST fail all channels with the sending node.
316
            for cid in self.channels:
×
317
                self.schedule_force_closing(cid)
×
318
                self.ordered_message_queues[cid].put_nowait((None, {'error': err_bytes}))
×
319
        else:
320
            # if no existing channel is referred to by channel_id:
321
            # - MUST ignore the message.
322
            return
×
323
        raise GracefulDisconnect
1✔
324

325
    def send_warning(self, channel_id: bytes, message: str = None, *, close_connection=False):
1✔
326
        """Sends a warning and disconnects if close_connection.
327

328
        Note:
329
        * channel_id is the temporary channel id when the channel id is not yet available
330

331
        A sending node:
332
        MAY set channel_id to all zero if the warning is not related to a specific channel.
333

334
        when failure was caused by an invalid signature check:
335
        * SHOULD include the raw, hex-encoded transaction in reply to a funding_created,
336
          funding_signed, closing_signed, or commitment_signed message.
337
        """
338
        assert isinstance(channel_id, bytes)
1✔
339
        encoded_data = b'' if not message else message.encode('ascii')
1✔
340
        self.send_message('warning', channel_id=channel_id, data=encoded_data, len=len(encoded_data))
1✔
341
        if close_connection:
1✔
342
            raise GracefulDisconnect
1✔
343

344
    def send_error(self, channel_id: bytes, message: str = None, *, force_close_channel=False):
1✔
345
        """Sends an error message and force closes the channel.
346

347
        Note:
348
        * channel_id is the temporary channel id when the channel id is not yet available
349

350
        A sending node:
351
        * SHOULD send error for protocol violations or internal errors that make channels
352
          unusable or that make further communication unusable.
353
        * SHOULD send error with the unknown channel_id in reply to messages of type
354
          32-255 related to unknown channels.
355
        * MUST fail the channel(s) referred to by the error message.
356
        * MAY set channel_id to all zero to indicate all channels.
357

358
        when failure was caused by an invalid signature check:
359
        * SHOULD include the raw, hex-encoded transaction in reply to a funding_created,
360
          funding_signed, closing_signed, or commitment_signed message.
361
        """
362
        assert isinstance(channel_id, bytes)
1✔
363
        encoded_data = b'' if not message else message.encode('ascii')
1✔
364
        self.send_message('error', channel_id=channel_id, data=encoded_data, len=len(encoded_data))
1✔
365
        # MUST fail the channel(s) referred to by the error message:
366
        #  we may violate this with force_close_channel
367
        if force_close_channel:
1✔
368
            if channel_id in self.channels:
1✔
369
                self.schedule_force_closing(channel_id)
1✔
370
            elif channel_id == bytes(32):
×
371
                for cid in self.channels:
×
372
                    self.schedule_force_closing(cid)
×
373
        raise GracefulDisconnect
1✔
374

375
    async def on_ping(self, payload):
1✔
376
        elapsed_since_last = time.monotonic() - self._last_ping_recv_time
1✔
377
        min_delay = 1.0  # seconds
1✔
378
        if elapsed_since_last < min_delay:
1✔
379
            self.logger.debug("remote sending PINGs too often, sleeping a bit")
×
380
            # note: This rate-limiting helps limit our outbound traffic usage.
381
            #       (max inc msg size 65 KB, max out msg size 65 KB, decoupled)
382
            #       Does not really help for inbound traffic-usage:
383
            #       there are many other ways for the peer to flood us, e.g. unknown 'odd' message types.
384
            # note: this blocks processing *any* further incoming message from this peer
385
            await asyncio.sleep(min_delay - elapsed_since_last)
×
386
        self._last_ping_recv_time = time.monotonic()
1✔
387
        l = payload['num_pong_bytes']
1✔
388
        raw_msg = encode_msg('pong', byteslen=l)
1✔
389
        await self.transport.send_bytes_and_drain(raw_msg)
1✔
390

391
    def on_pong(self, payload):
1✔
392
        self.pong_event.set()
1✔
393

394
    async def wait_for_message(self, expected_name: str, channel_id: bytes):
1✔
395
        q = self.ordered_message_queues[channel_id]
1✔
396
        name, payload = await util.wait_for2(q.get(), LN_P2P_NETWORK_TIMEOUT)
1✔
397
        # raise exceptions for errors, so that the caller sees them
398
        if (err_bytes := payload.get("error")) is not None:
1✔
399
            err_text = error_text_bytes_to_safe_str(err_bytes)
×
400
            raise GracefulDisconnect(
×
401
                f"remote peer sent error [DO NOT TRUST THIS MESSAGE]: {err_text}")
402
        if name != expected_name:
1✔
403
            raise Exception(f"Received unexpected '{name}'")
×
404
        return payload
1✔
405

406
    def on_init(self, payload):
1✔
407
        if self._received_init:
1✔
408
            self.logger.info("ALREADY INITIALIZED BUT RECEIVED INIT")
1✔
409
            return
1✔
410
        _their_features = int.from_bytes(payload['features'], byteorder="big")
1✔
411
        _their_features |= int.from_bytes(payload['globalfeatures'], byteorder="big")
1✔
412
        try:
1✔
413
            self.their_features = validate_features(_their_features, context=LnFeatureContexts.INIT)
1✔
414
        except IncompatibleOrInsaneFeatures as e:
×
415
            raise GracefulDisconnect(f"remote sent insane features: {repr(e)}")
×
416
        # check if features are compatible, and set self.features to what we negotiated
417
        try:
1✔
418
            self.features = ln_compare_features(self.features, self.their_features)
1✔
419
        except IncompatibleLightningFeatures as e:
×
420
            self.initialized.set_exception(e)
×
421
            raise GracefulDisconnect(f"{str(e)}")
×
422
        self.logger.info(
1✔
423
            f"received INIT with features={str(self.their_features.get_names())}. "
424
            f"negotiated={str(self.features)}")
425
        # check that they are on the same chain as us, if provided
426
        their_networks = payload["init_tlvs"].get("networks")
1✔
427
        if their_networks:
1✔
428
            their_chains = list(chunks(their_networks["chains"], 32))
1✔
429
            if constants.net.rev_genesis_bytes() not in their_chains:
1✔
430
                raise GracefulDisconnect(f"no common chain found with remote. (they sent: {their_chains})")
×
431
        # all checks passed
432
        self.lnworker.lnpeermgr.on_peer_successfully_established(self)
1✔
433
        self._received_init = True
1✔
434
        self.maybe_set_initialized()
1✔
435

436
    def on_node_announcement(self, payload):
1✔
437
        if self.lnworker.uses_trampoline():
×
438
            return
×
439
        if self.our_gossip_timestamp_filter is None:
×
440
            return  # why is the peer sending this? should we disconnect?
×
441
        self.recv_gossip_queue.put_nowait(('node_announcement', payload))
×
442

443
    def on_channel_announcement(self, payload):
1✔
444
        if self.lnworker.uses_trampoline():
×
445
            return
×
446
        if self.our_gossip_timestamp_filter is None:
×
447
            return  # why is the peer sending this? should we disconnect?
×
448
        self.recv_gossip_queue.put_nowait(('channel_announcement', payload))
×
449

450
    def on_channel_update(self, payload):
1✔
451
        self.maybe_save_remote_update(payload)
1✔
452
        if self.lnworker.uses_trampoline():
1✔
453
            return
1✔
454
        if self.our_gossip_timestamp_filter is None:
1✔
455
            return  # why is the peer sending this? should we disconnect?
1✔
456
        self.recv_gossip_queue.put_nowait(('channel_update', payload))
×
457

458
    def on_query_channel_range(self, payload):
1✔
459
        if self.lnworker == self.lnworker.network.lngossip or not self._should_forward_gossip():
×
460
            return
×
461
        if not self._is_valid_channel_range_query(payload):
×
462
            return self.send_warning(bytes(32), "received invalid query_channel_range")
×
463
        if self.outgoing_gossip_reply:
×
464
            return self.send_warning(bytes(32), "received multiple queries at the same time")
×
465
        self.outgoing_gossip_reply = True
×
466
        self.recv_gossip_queue.put_nowait(('query_channel_range', payload))
×
467

468
    def on_query_short_channel_ids(self, payload):
1✔
469
        if self.lnworker == self.lnworker.network.lngossip or not self._should_forward_gossip():
×
470
            return
×
471
        if self.outgoing_gossip_reply:
×
472
            return self.send_warning(bytes(32), "received multiple queries at the same time")
×
473
        if not self._is_valid_short_channel_id_query(payload):
×
474
            return self.send_warning(bytes(32), "invalid query_short_channel_ids")
×
475
        self.outgoing_gossip_reply = True
×
476
        self.recv_gossip_queue.put_nowait(('query_short_channel_ids', payload))
×
477

478
    def on_gossip_timestamp_filter(self, payload):
1✔
479
        if self._should_forward_gossip():
×
480
            self.set_gossip_timestamp_filter(payload)
×
481

482
    def set_gossip_timestamp_filter(self, payload: dict) -> None:
1✔
483
        """Set the gossip_timestamp_filter for this peer. If the peer requested historical gossip,
484
        the request is put on the queue, otherwise only the forwarding loop will check the filter"""
485
        if payload.get('chain_hash') != constants.net.rev_genesis_bytes():
×
486
            return
×
487
        filter = GossipTimestampFilter.from_payload(payload)
×
488
        self.their_gossip_timestamp_filter = filter
×
489
        self.logger.debug(f"got gossip_ts_filter from peer {self.pubkey.hex()}: "
×
490
                          f"{str(self.their_gossip_timestamp_filter)}")
491
        if filter and not filter.only_forwarding:
×
492
            self.recv_gossip_queue.put_nowait(('gossip_timestamp_filter', None))
×
493

494
    def maybe_save_remote_update(self, payload):
1✔
495
        if not self.channels:
1✔
496
            return
×
497
        for chan in self.channels.values():
1✔
498
            if payload['short_channel_id'] in [chan.short_channel_id, chan.get_local_scid_alias()]:
1✔
499
                # originator: node_id_1 if the least-significant bit of flags is 0 or node_id_2 otherwise
500
                flags = int.from_bytes(payload['channel_flags'], byteorder='big', signed=False)
1✔
501
                originator = sorted(self.node_ids)[flags & FLAG_DIRECTION]
1✔
502
                if originator == self.lnworker.node_keypair.pubkey:
1✔
503
                    self.logger.debug(f"peer sent us our own channel update for chan {chan.get_id_for_log()}")
1✔
504
                    return
1✔
505
                chan.set_remote_update(payload)
1✔
506
                self.logger.info(f"saved remote channel_update gossip msg for chan {chan.get_id_for_log()}")
1✔
507
                break
1✔
508
        else:
509
            # Save (some bounded number of) orphan channel updates for later
510
            # as it might be for our own direct channel with this peer
511
            # (and we might not yet know the short channel id for that)
512
            # Background: this code is here to deal with a bug in LND,
513
            # see https://github.com/lightningnetwork/lnd/issues/3651 (closed 2022-08-13, lnd-v0.15.1)
514
            # and https://github.com/lightningnetwork/lightning-rfc/pull/657
515
            # This code assumes gossip_queries is set. BOLT7: "if the
516
            # gossip_queries feature is negotiated, [a node] MUST NOT
517
            # send gossip it did not generate itself"
518
            # NOTE: The definition of gossip_queries changed
519
            # https://github.com/lightning/bolts/commit/fce8bab931674a81a9ea895c9e9162e559e48a65
520
            short_channel_id = ShortChannelID(payload['short_channel_id'])
×
521
            self.orphan_channel_updates[short_channel_id] = payload
×
522
            while len(self.orphan_channel_updates) > 25:
×
523
                self.orphan_channel_updates.popitem(last=False)
×
524

525
    def on_announcement_signatures(self, chan: Channel, payload):
1✔
526
        if not chan.is_public() or chan.short_channel_id is None:
1✔
527
            return
×
528
        h = chan.get_channel_announcement_hash()
1✔
529
        node_signature = payload["node_signature"]
1✔
530
        bitcoin_signature = payload["bitcoin_signature"]
1✔
531
        if not ECPubkey(chan.config[REMOTE].multisig_key.pubkey).ecdsa_verify(bitcoin_signature, h):
1✔
532
            raise Exception("bitcoin_sig invalid in announcement_signatures")
×
533
        if not ECPubkey(self.pubkey).ecdsa_verify(node_signature, h):
1✔
534
            raise Exception("node_sig invalid in announcement_signatures")
×
535
        chan.config[REMOTE].announcement_node_sig = node_signature
1✔
536
        chan.config[REMOTE].announcement_bitcoin_sig = bitcoin_signature
1✔
537
        self.lnworker.save_channel(chan)
1✔
538
        self.maybe_send_announcement_signatures(chan, is_reply=True)
1✔
539

540
    def handle_disconnect(func):
1✔
541
        @functools.wraps(func)
1✔
542
        async def wrapper_func(self, *args, **kwargs):
1✔
543
            try:
1✔
544
                return await func(self, *args, **kwargs)
1✔
545
            except GracefulDisconnect as e:
1✔
546
                self.logger.log(e.log_level, f"Disconnecting: {repr(e)}")
×
547
            except (LightningPeerConnectionClosed, IncompatibleLightningFeatures,
1✔
548
                    aiorpcx.socks.SOCKSError) as e:
549
                self.logger.info(f"Disconnecting: {repr(e)}")
×
550
            finally:
551
                self.close_and_cleanup()
1✔
552
        return wrapper_func
1✔
553

554
    @ignore_exceptions  # do not kill outer taskgroup
1✔
555
    @log_exceptions
1✔
556
    @handle_disconnect
1✔
557
    async def main_loop(self):
1✔
558
        async with self.taskgroup as group:
1✔
559
            await group.spawn(self._message_loop())  # initializes connection
1✔
560
            try:
1✔
561
                await util.wait_for2(self.initialized, LN_P2P_NETWORK_TIMEOUT)
1✔
562
            except Exception as e:
×
563
                raise GracefulDisconnect(f"Failed to initialize: {e!r}") from e
×
564
            await group.spawn(self._query_gossip())
1✔
565
            await group.spawn(self._process_gossip())
1✔
566
            await group.spawn(self._send_own_gossip())
1✔
567
            await group.spawn(self._forward_gossip())
1✔
568
            if self.network.lngossip != self.lnworker:
1✔
569
                await group.spawn(self.htlc_switch())
1✔
570

571
    async def _process_gossip(self):
1✔
572
        while True:
1✔
573
            await asyncio.sleep(5)
1✔
574
            if not self.network.lngossip:
×
575
                continue
×
576
            chan_anns = []
×
577
            chan_upds = []
×
578
            node_anns = []
×
579
            while True:
×
580
                name, payload = await self.recv_gossip_queue.get()
×
581
                if name == 'channel_announcement':
×
582
                    chan_anns.append(payload)
×
583
                elif name == 'channel_update':
×
584
                    chan_upds.append(payload)
×
585
                elif name == 'node_announcement':
×
586
                    node_anns.append(payload)
×
587
                elif name == 'query_channel_range':
×
588
                    await self.taskgroup.spawn(self._send_reply_channel_range(payload))
×
589
                elif name == 'query_short_channel_ids':
×
590
                    await self.taskgroup.spawn(self._send_reply_short_channel_ids(payload))
×
591
                elif name == 'gossip_timestamp_filter':
×
592
                    await self.taskgroup.spawn(self._handle_historical_gossip_request())
×
593
                else:
594
                    raise Exception('unknown message')
×
595
                if self.recv_gossip_queue.empty():
×
596
                    break
×
597
            if self.network.lngossip:
×
598
                await self.network.lngossip.process_gossip(chan_anns, node_anns, chan_upds)
×
599

600
    async def _send_own_gossip(self):
1✔
601
        if self.lnworker == self.lnworker.network.lngossip:
1✔
602
            return
×
603
        assert self.is_initialized()
1✔
604
        await asyncio.sleep(10)
1✔
605
        while True:
×
606
            public_channels = [chan for chan in self.lnworker.channels.values() if chan.is_public()]
×
607
            if public_channels:
×
608
                alias = self.config.LIGHTNING_NODE_ALIAS
×
609
                color = self.config.LIGHTNING_NODE_COLOR_RGB
×
610
                self.send_node_announcement(alias, color)
×
611
                for chan in public_channels:
×
612
                    if chan.is_open() and chan.peer_state == PeerState.GOOD:
×
613
                        self.maybe_send_channel_announcement(chan)
×
614
            await asyncio.sleep(600)
×
615

616
    def _should_forward_gossip(self) -> bool:
1✔
617
        if (self.network.lngossip != self.lnworker
1✔
618
                and not self.lnworker.uses_trampoline()
619
                and self.features.supports(LnFeatures.GOSSIP_QUERIES_REQ)):
620
            return True
×
621
        return False
1✔
622

623
    async def _forward_gossip(self):
1✔
624
        assert self.is_initialized()
1✔
625
        if not self._should_forward_gossip():
1✔
626
            return
1✔
627

628
        async def send_new_gossip_with_semaphore(gossip: List[GossipForwardingMessage]):
×
629
            async with self.network.lngossip.gossip_request_semaphore:
×
630
                sent = await self._send_gossip_messages(gossip)
×
631
            if sent > 0:
×
632
                self.logger.debug(f"forwarded {sent} gossip messages to {self.pubkey.hex()}")
×
633

634
        lngossip = self.network.lngossip
×
635
        last_gossip_batch_ts = 0
×
636
        while True:
×
637
            await asyncio.sleep(10)
×
638
            if not self.their_gossip_timestamp_filter:
×
639
                continue  # peer didn't request gossip
×
640

641
            new_gossip, last_lngossip_refresh_ts = await lngossip.get_forwarding_gossip()
×
642
            if not last_lngossip_refresh_ts > last_gossip_batch_ts:
×
643
                continue  # no new batch available
×
644
            last_gossip_batch_ts = last_lngossip_refresh_ts
×
645

646
            await self.taskgroup.spawn(send_new_gossip_with_semaphore(new_gossip))
×
647

648
    async def _handle_historical_gossip_request(self):
1✔
649
        """Called when a peer requests historical gossip with a gossip_timestamp_filter query."""
650
        filter = self.their_gossip_timestamp_filter
×
651
        if not self._should_forward_gossip() or not filter or filter.only_forwarding:
×
652
            return
×
653
        async with self.network.lngossip.gossip_request_semaphore:
×
654
            requested_gossip = self.lnworker.channel_db.get_gossip_in_timespan(filter)
×
655
            filter.only_forwarding = True
×
656
            sent = await self._send_gossip_messages(requested_gossip)
×
657
            if sent > 0:
×
658
                self._num_gossip_messages_forwarded += sent
×
659
                #self.logger.debug(f"forwarded {sent} historical gossip messages to {self.pubkey.hex()}")
660

661
    async def _send_gossip_messages(self, messages: List[GossipForwardingMessage]) -> int:
1✔
662
        amount_sent = 0
×
663
        for msg in messages:
×
664
            if self.their_gossip_timestamp_filter.in_range(msg.timestamp) \
×
665
                and self.pubkey != msg.sender_node_id:
666
                await self.transport.send_bytes_and_drain(msg.msg)
×
667
                amount_sent += 1
×
668
                if amount_sent % 250 == 0:
×
669
                    # this can be a lot of messages, completely blocking the event loop
670
                    await asyncio.sleep(self.DELAY_INC_MSG_PROCESSING_SLEEP)
×
671
        return amount_sent
×
672

673
    async def _query_gossip(self):
1✔
674
        assert self.is_initialized()
1✔
675
        if self.lnworker == self.lnworker.network.lngossip:
1✔
676
            if not self.their_features.supports(LnFeatures.GOSSIP_QUERIES_OPT):
×
677
                raise GracefulDisconnect("remote does not support gossip_queries, which we need")
×
678
            try:
×
679
                ids, complete = await util.wait_for2(self.get_channel_range(), LN_P2P_NETWORK_TIMEOUT)
×
680
            except asyncio.TimeoutError as e:
×
681
                raise GracefulDisconnect("query_channel_range timed out") from e
×
682
            self.logger.info('Received {} channel ids. (complete: {})'.format(len(ids), complete))
×
683
            await self.lnworker.add_new_ids(ids)
×
684
            self.request_gossip(int(time.time()))
×
685
            while True:
×
686
                todo = self.lnworker.get_ids_to_query()
×
687
                if not todo:
×
688
                    await asyncio.sleep(1)
×
689
                    continue
×
690
                await self.get_short_channel_ids(todo)
×
691

692
    @staticmethod
1✔
693
    def _is_valid_channel_range_query(payload: dict) -> bool:
1✔
694
        if payload.get('chain_hash') != constants.net.rev_genesis_bytes():
×
695
            return False
×
696
        if payload.get('first_blocknum', -1) < constants.net.BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS:
×
697
            return False
×
698
        if payload.get('number_of_blocks', 0) < 1:
×
699
            return False
×
700
        return True
×
701

702
    def _is_valid_short_channel_id_query(self, payload: dict) -> bool:
1✔
703
        if payload.get('chain_hash') != constants.net.rev_genesis_bytes():
×
704
            return False
×
705
        enc_short_ids = payload['encoded_short_ids']
×
706
        if enc_short_ids[0] != 0:
×
707
            self.logger.debug(f"got query_short_channel_ids with invalid encoding: {repr(enc_short_ids[0])}")
×
708
            return False
×
709
        if (len(enc_short_ids) - 1) % 8 != 0:
×
710
            self.logger.debug(f"got query_short_channel_ids with invalid length")
×
711
            return False
×
712
        return True
×
713

714
    async def _send_reply_channel_range(self, payload: dict):
1✔
715
        """https://github.com/lightning/bolts/blob/acd383145dd8c3fecd69ce94e4a789767b984ac0/07-routing-gossip.md#requirements-5"""
716
        first_blockheight: int = payload['first_blocknum']
×
717

718
        async with self.network.lngossip.gossip_request_semaphore:
×
719
            sorted_scids: List[ShortChannelID] = self.lnworker.channel_db.get_channels_in_range(
×
720
                first_blockheight,
721
                payload['number_of_blocks']
722
            )
723
            self.logger.debug(f"reply_channel_range to request "
×
724
                              f"first_height={first_blockheight}, "
725
                              f"num_blocks={payload['number_of_blocks']}, "
726
                              f"sending {len(sorted_scids)} scids")
727

728
            complete: bool = False
×
729
            while not complete:
×
730
                # create a 64800 byte chunk of skids, split the remaining scids
731
                encoded_scids, sorted_scids = b''.join(sorted_scids[:8100]), sorted_scids[8100:]
×
732
                complete = len(sorted_scids) == 0  # if there are no scids remaining we are done
×
733
                # number of blocks covered by the scids in this chunk
734
                if complete:
×
735
                    # LAST MESSAGE MUST have first_blocknum plus number_of_blocks equal or greater than
736
                    # the query_channel_range first_blocknum plus number_of_blocks.
737
                    number_of_blocks = ((payload['first_blocknum'] + payload['number_of_blocks'])
×
738
                                        - first_blockheight)
739
                else:
740
                    # we cover the range until the height of the first scid in the next chunk
741
                    number_of_blocks = sorted_scids[0].block_height - first_blockheight
×
742
                self.send_message('reply_channel_range',
×
743
                    chain_hash=constants.net.rev_genesis_bytes(),
744
                    first_blocknum=first_blockheight,
745
                    number_of_blocks=number_of_blocks,
746
                    sync_complete=complete,
747
                    len=1+len(encoded_scids),
748
                    encoded_short_ids=b'\x00' + encoded_scids)
749
                if not complete:
×
750
                    first_blockheight = sorted_scids[0].block_height
×
751
                    await asyncio.sleep(self.DELAY_INC_MSG_PROCESSING_SLEEP)
×
752
            self.outgoing_gossip_reply = False
×
753

754
    async def get_channel_range(self):
1✔
755
        self.reply_channel_range = asyncio.Queue()
×
756
        first_block = constants.net.BLOCK_HEIGHT_FIRST_LIGHTNING_CHANNELS
×
757
        num_blocks = self.lnworker.network.get_local_height() - first_block
×
758
        self.query_channel_range(first_block, num_blocks)
×
759
        intervals = []
×
760
        ids = set()
×
761
        # note: implementations behave differently...
762
        # "sane implementation that follows BOLT-07" example:
763
        #   query_channel_range. <<< first_block 497000, num_blocks 79038
764
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 39516, num_ids 4648, complete True
765
        #   on_reply_channel_range. >>> first_block 536516, num_blocks 19758, num_ids 5734, complete True
766
        #   on_reply_channel_range. >>> first_block 556274, num_blocks 9879, num_ids 13712, complete True
767
        #   on_reply_channel_range. >>> first_block 566153, num_blocks 9885, num_ids 18114, complete True
768
        # lnd example:
769
        #   query_channel_range. <<< first_block 497000, num_blocks 79038
770
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 79038, num_ids 8000, complete False
771
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 79038, num_ids 8000, complete False
772
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 79038, num_ids 8000, complete False
773
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 79038, num_ids 8000, complete False
774
        #   on_reply_channel_range. >>> first_block 497000, num_blocks 79038, num_ids 5344, complete True
775
        # ADDENDUM (01/2025): now it's 'MUST set sync_complete to false if this is not the final reply_channel_range.'
776
        while True:
×
777
            index, num, complete, _ids = await self.reply_channel_range.get()
×
778
            ids.update(_ids)
×
779
            intervals.append((index, index+num))
×
780
            intervals.sort()
×
781
            while len(intervals) > 1:
×
782
                a,b = intervals[0]
×
783
                c,d = intervals[1]
×
784
                if not (a <= c and a <= b and c <= d):
×
785
                    raise Exception(f"insane reply_channel_range intervals {(a,b,c,d)}")
×
786
                if b >= c:
×
787
                    intervals = [(a,d)] + intervals[2:]
×
788
                else:
789
                    break
×
790
            if len(intervals) == 1 and complete:
×
791
                a, b = intervals[0]
×
792
                if a <= first_block and b >= first_block + num_blocks:
×
793
                    break
×
794
        self.reply_channel_range = None
×
795
        return ids, complete
×
796

797
    def request_gossip(self, timestamp=0):
1✔
798
        if timestamp == 0:
×
799
            self.logger.info('requesting whole channel graph')
×
800
        else:
801
            self.logger.info(f'requesting channel graph since {datetime.fromtimestamp(timestamp).isoformat()}')
×
802
        timestamp_range = 0xFFFFFFFF
×
803
        self.our_gossip_timestamp_filter = GossipTimestampFilter(
×
804
            first_timestamp=timestamp,
805
            timestamp_range=timestamp_range,
806
        )
807
        self.send_message(
×
808
            'gossip_timestamp_filter',
809
            chain_hash=constants.net.rev_genesis_bytes(),
810
            first_timestamp=timestamp,
811
            timestamp_range=timestamp_range,
812
        )
813

814
    def query_channel_range(self, first_block, num_blocks):
1✔
815
        self.logger.info(f'query channel range {first_block} {num_blocks}')
×
816
        self.send_message(
×
817
            'query_channel_range',
818
            chain_hash=constants.net.rev_genesis_bytes(),
819
            first_blocknum=first_block,
820
            number_of_blocks=num_blocks)
821

822
    @staticmethod
1✔
823
    def decode_short_ids(encoded):
1✔
824
        if len(encoded) < 1 or (len(encoded) - 1) % 8 != 0:
1✔
825
            raise Exception(f'decode_short_ids: invalid size: {len(encoded)=}')
1✔
826
        elif encoded[0] != 0:
1✔
827
            raise Exception(f'decode_short_ids: unexpected first byte: {encoded[0]}')
1✔
828
        decoded = encoded[1:]
1✔
829
        ids = [decoded[i:i+8] for i in range(0, len(decoded), 8)]
1✔
830
        return ids
1✔
831

832
    async def on_reply_channel_range(self, payload):
1✔
833
        first = payload['first_blocknum']
×
834
        num = payload['number_of_blocks']
×
835
        complete = bool(int.from_bytes(payload['sync_complete'], 'big'))
×
836
        encoded = payload['encoded_short_ids']
×
837
        ids = self.decode_short_ids(encoded)
×
838
        # self.logger.info(f"on_reply_channel_range. >>> first_block {first}, num_blocks {num}, "
839
        #                  f"num_ids {len(ids)}, complete {complete}")
840
        if self.reply_channel_range is None:
×
841
            raise Exception("received 'reply_channel_range' without corresponding 'query_channel_range'")
×
842
        while self.reply_channel_range.qsize() > 10:
×
843
            # we block process_message until the queue gets consumed
844
            self.logger.info("reply_channel_range queue is overflowing. sleeping...")
×
845
            await asyncio.sleep(0.1)
×
846
        self.reply_channel_range.put_nowait((first, num, complete, ids))
×
847

848
    async def _send_reply_short_channel_ids(self, payload: dict):
1✔
849
        async with self.network.lngossip.gossip_request_semaphore:
×
850
            requested_scids = payload['encoded_short_ids']
×
851
            decoded_scids = [ShortChannelID.normalize(scid)
×
852
                             for scid in self.decode_short_ids(requested_scids)]
853
            self.logger.debug(f"serving query_short_channel_ids request: "
×
854
                              f"requested {len(decoded_scids)} scids")
855
            chan_db = self.lnworker.channel_db
×
856
            response: Set[bytes] = set()
×
857
            for scid in decoded_scids:
×
858
                requested_msgs = chan_db.get_gossip_for_scid_request(scid)
×
859
                response.update(requested_msgs)
×
860
            self.logger.debug(f"found {len(response)} gossip messages to serve scid request")
×
861
            for index, msg in enumerate(response):
×
862
                await self.transport.send_bytes_and_drain(msg)
×
863
                if index % 250 == 0:
×
864
                    await asyncio.sleep(self.DELAY_INC_MSG_PROCESSING_SLEEP)
×
865
            self.send_message(
×
866
                'reply_short_channel_ids_end',
867
                chain_hash=constants.net.rev_genesis_bytes(),
868
                full_information=self.network.lngossip.is_synced()
869
            )
870
            self.outgoing_gossip_reply = False
×
871

872
    async def get_short_channel_ids(self, ids):
1✔
873
        #self.logger.info(f'Querying {len(ids)} short_channel_ids')
874
        assert not self.querying.is_set()
×
875
        self.query_short_channel_ids(ids)
×
876
        await self.querying.wait()
×
877
        self.querying.clear()
×
878

879
    def query_short_channel_ids(self, ids):
1✔
880
        # compression MUST NOT be used according to updated bolt
881
        # (https://github.com/lightning/bolts/pull/981)
882
        ids = sorted(ids)
×
883
        s = b''.join(ids)
×
884
        prefix = b'\x00'  # uncompressed
×
885
        self.send_message(
×
886
            'query_short_channel_ids',
887
            chain_hash=constants.net.rev_genesis_bytes(),
888
            len=1+len(s),
889
            encoded_short_ids=prefix+s)
890

891
    async def _message_loop(self):
1✔
892
        try:
1✔
893
            await util.wait_for2(self.initialize(), LN_P2P_NETWORK_TIMEOUT)
1✔
894
        except (OSError, asyncio.TimeoutError, HandshakeFailed) as e:
×
895
            raise GracefulDisconnect(f'initialize failed: {repr(e)}') from e
×
896
        async for msg in self.transport.read_messages():
1✔
897
            await self._process_message(msg)
1✔
898
            if self.DELAY_INC_MSG_PROCESSING_SLEEP:
1✔
899
                # rate-limit message-processing a bit, to make it harder
900
                # for a single peer to bog down the event loop / cpu:
901
                await asyncio.sleep(self.DELAY_INC_MSG_PROCESSING_SLEEP)
×
902
            # If receiving too much gossip from this peer, we need to slow them down.
903
            # note: if the gossip queue gets full, we will disconnect from them
904
            #       and throw away unprocessed gossip.
905
            if self.recv_gossip_queue.qsize() > self.RECV_GOSSIP_QUEUE_SOFT_MAXSIZE:
1✔
906
                sleep = self.recv_gossip_queue.qsize() / 1000
×
907
                self.logger.debug(
×
908
                    f"message_loop sleeping due to getting much gossip. qsize={self.recv_gossip_queue.qsize()}. "
909
                    f"waiting for existing gossip data to be processed first.")
910
                await asyncio.sleep(sleep)
×
911

912
    def on_reply_short_channel_ids_end(self, payload):
1✔
913
        self.querying.set()
×
914

915
    def close_and_cleanup(self):
1✔
916
        # note: This method might get called multiple times!
917
        #       E.g. if you call close_and_cleanup() to cause a disconnection from the peer,
918
        #       it will get called a second time in handle_disconnect().
919
        self.unregister_callbacks()
1✔
920
        try:
1✔
921
            if self.transport:
1✔
922
                self.transport.close()
1✔
923
                # could `await self.transport.writer.wait_closed()` with a timeout?  but not async
924
        except Exception:
1✔
925
            pass
1✔
926
        self.lnworker.lnpeermgr.peer_closed(self)
1✔
927
        self.got_disconnected.set()
1✔
928

929
    def is_shutdown_anysegwit(self):
1✔
930
        return self.features.supports(LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT)
1✔
931

932
    def accepts_zeroconf(self):
1✔
933
        return self.features.supports(LnFeatures.OPTION_ZEROCONF_OPT)
1✔
934

935
    def is_upfront_shutdown_script(self):
1✔
936
        return self.features.supports(LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT)
1✔
937

938
    def upfront_shutdown_script_from_payload(self, payload, msg_identifier: str) -> Optional[bytes]:
1✔
939
        if msg_identifier not in ['accept', 'open']:
×
940
            raise ValueError("msg_identifier must be either 'accept' or 'open'")
×
941

942
        uss_tlv = payload[msg_identifier + '_channel_tlvs'].get(
×
943
            'upfront_shutdown_script')
944

945
        if uss_tlv and self.is_upfront_shutdown_script():
×
946
            upfront_shutdown_script = uss_tlv['shutdown_scriptpubkey']
×
947
        else:
948
            upfront_shutdown_script = b''
×
949
        self.logger.info(f"upfront shutdown script received: {upfront_shutdown_script}")
×
950
        return upfront_shutdown_script
×
951

952
    def temporarily_reserve_funding_tx_change_address(func):
1✔
953
        # During the channel open flow, if we initiated, we might have used a change address
954
        # of ours in the funding tx. The funding tx is not part of the wallet history
955
        # at that point yet, but we should already consider this change address as 'used'.
956
        @functools.wraps(func)
1✔
957
        async def wrapper(self: 'Peer', *args, **kwargs):
1✔
958
            funding_tx = kwargs['funding_tx']  # type: PartialTransaction
×
959
            wallet = self.lnworker.wallet
×
960
            change_addresses = [txout.address for txout in funding_tx.outputs()
×
961
                                if wallet.is_change(txout.address)]
962
            for addr in change_addresses:
×
963
                wallet.set_reserved_state_of_address(addr, reserved=True)
×
964
            try:
×
965
                return await func(self, *args, **kwargs)
×
966
            finally:
967
                for addr in change_addresses:
×
968
                    self.lnworker.wallet.set_reserved_state_of_address(addr, reserved=False)
×
969
        return wrapper
1✔
970

971
    @temporarily_reserve_funding_tx_change_address
1✔
972
    async def channel_establishment_flow(
1✔
973
            self, *,
974
            funding_tx: 'PartialTransaction',
975
            funding_sat: int,
976
            push_msat: int,
977
            public: bool,
978
            zeroconf: bool = False,
979
            temp_channel_id: bytes,
980
            opening_fee: int = None,
981
    ) -> Tuple[Channel, 'PartialTransaction']:
982
        """Implements the channel opening flow.
983

984
        -> open_channel message
985
        <- accept_channel message
986
        -> funding_created message
987
        <- funding_signed message
988

989
        Channel configurations are initialized in this method.
990
        """
991

992
        if public and not self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS:
×
993
            raise Exception('Cannot create public channels')
×
994

995
        if not self.lnworker.wallet.can_have_lightning():
×
996
            # old wallet that cannot have lightning anymore
997
            raise Exception('This wallet cannot create new channels')
×
998

999
        # will raise if init fails
1000
        await util.wait_for2(self.initialized, LN_P2P_NETWORK_TIMEOUT)
×
1001
        # trampoline is not yet in features
1002
        if self.lnworker.uses_trampoline() and not self.lnworker.is_trampoline_peer(self.pubkey):
×
1003
            raise Exception('Not a trampoline node: ' + str(self.their_features))
×
1004

1005
        channel_flags = CF_ANNOUNCE_CHANNEL if public else 0
×
1006
        feerate: Optional[int] = self.lnworker.current_target_feerate_per_kw(
×
1007
            has_anchors=not self.config.TEST_LN_OPEN_SRK_CHANNELS,
1008
        )
1009
        if feerate is None:
×
1010
            raise NoDynamicFeeEstimates()
×
1011
        # we set a channel type for internal bookkeeping
1012
        open_channel_tlvs = {}
×
1013
        assert self.features.supports(LnFeatures.OPTION_STATIC_REMOTEKEY_OPT)
×
1014
        assert self.features.supports(LnFeatures.OPTION_ANCHORS_OPT)
×
1015
        if self.config.TEST_LN_OPEN_SRK_CHANNELS:
×
1016
            our_channel_type = ChannelType(ChannelType.OPTION_STATIC_REMOTEKEY)
×
1017
        else:  # anchors
1018
            our_channel_type = ChannelType(ChannelType.OPTION_STATIC_REMOTEKEY | ChannelType.OPTION_ANCHORS)
×
1019
        if zeroconf:
×
1020
            our_channel_type |= ChannelType(ChannelType.OPTION_ZEROCONF)
×
1021
        # We do not set the option_scid_alias bit in channel_type because LND rejects it.
1022
        # Eclair accepts channel_type with that bit, but does not require it.
1023
        assert our_channel_type.complies_with_features(self.features), f"{our_channel_type=!r}, {self.features=!r}"
×
1024

1025
        # if option_channel_type is negotiated: MUST set channel_type
1026
        # if it includes channel_type: MUST set it to a defined type representing the type it wants.
1027
        open_channel_tlvs['channel_type'] = {
×
1028
            'type': our_channel_type.to_bytes_minimal()
1029
        }
1030

1031
        if our_channel_type & ChannelType.OPTION_ANCHORS:
×
1032
            multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_we_opened(
×
1033
                funding_root_secret=self.lnworker.funding_root_keypair.privkey,
1034
                remote_node_id_or_prefix=self.pubkey,
1035
                nlocktime=funding_tx.locktime,
1036
            )
1037
        else:
1038
            multisig_funding_keypair = None
×
1039
        local_config = self.lnworker.make_local_config_for_new_channel(
×
1040
            funding_sat=funding_sat,
1041
            push_msat=push_msat,
1042
            initiator=LOCAL,
1043
            channel_type=our_channel_type,
1044
            multisig_funding_keypair=multisig_funding_keypair,
1045
            peer_features=self.features,
1046
        )
1047
        # if it includes open_channel_tlvs: MUST include upfront_shutdown_script.
1048
        open_channel_tlvs['upfront_shutdown_script'] = {
×
1049
            'shutdown_scriptpubkey': local_config.upfront_shutdown_script
1050
        }
1051
        if opening_fee:
×
1052
            # todo: maybe add payment hash
1053
            open_channel_tlvs['channel_opening_fee'] = {
×
1054
                'channel_opening_fee': opening_fee
1055
            }
1056
        # for the first commitment transaction
1057
        per_commitment_secret_first = get_per_commitment_secret_from_seed(
×
1058
            local_config.per_commitment_secret_seed,
1059
            RevocationStore.START_INDEX
1060
        )
1061
        per_commitment_point_first = secret_to_pubkey(
×
1062
            int.from_bytes(per_commitment_secret_first, 'big'))
1063

1064
        # store the temp id now, so that it is recognized for e.g. 'error' messages
1065
        self.temp_id_to_id[temp_channel_id] = None
×
1066
        self._cleanup_temp_channelids()
×
1067
        self.send_message(
×
1068
            "open_channel",
1069
            temporary_channel_id=temp_channel_id,
1070
            chain_hash=constants.net.rev_genesis_bytes(),
1071
            funding_satoshis=funding_sat,
1072
            push_msat=push_msat,
1073
            dust_limit_satoshis=local_config.dust_limit_sat,
1074
            feerate_per_kw=feerate,
1075
            max_accepted_htlcs=local_config.max_accepted_htlcs,
1076
            funding_pubkey=local_config.multisig_key.pubkey,
1077
            revocation_basepoint=local_config.revocation_basepoint.pubkey,
1078
            htlc_basepoint=local_config.htlc_basepoint.pubkey,
1079
            payment_basepoint=local_config.payment_basepoint.pubkey,
1080
            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
1081
            first_per_commitment_point=per_commitment_point_first,
1082
            to_self_delay=local_config.to_self_delay,
1083
            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
1084
            channel_flags=channel_flags,
1085
            channel_reserve_satoshis=local_config.reserve_sat,
1086
            htlc_minimum_msat=local_config.htlc_minimum_msat,
1087
            open_channel_tlvs=open_channel_tlvs,
1088
        )
1089

1090
        # <- accept_channel
1091
        payload = await self.wait_for_message('accept_channel', temp_channel_id)
×
1092
        self.logger.debug(f"received accept_channel for temp_channel_id={temp_channel_id.hex()}. {payload=}")
×
1093
        remote_per_commitment_point = payload['first_per_commitment_point']
×
1094
        funding_txn_minimum_depth = payload['minimum_depth']
×
1095
        if not zeroconf and funding_txn_minimum_depth <= 0:
×
1096
            raise Exception(f"minimum depth too low, {funding_txn_minimum_depth}")
×
1097
        if funding_txn_minimum_depth > 30:
×
1098
            raise Exception(f"minimum depth too high, {funding_txn_minimum_depth}")
×
1099

1100
        upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
×
1101
            payload, 'accept')
1102

1103
        accept_channel_tlvs = payload.get('accept_channel_tlvs')
×
1104
        if accept_channel_tlvs is None:
×
1105
            raise Exception("accept_channel_tlvs MUST be present in accept_channel, but missing")
×
1106
        their_channel_type = accept_channel_tlvs.get('channel_type')
×
1107
        if their_channel_type is None:
×
1108
            raise Exception("channel_type MUST be present in accept_channel, but missing")
×
1109
        their_channel_type = ChannelType.from_bytes(their_channel_type['type'], byteorder='big')
×
1110
        # if channel_type does not match the channel_type from open_channel:
1111
        #     MUST fail the channel.
1112
        if their_channel_type != our_channel_type:
×
1113
            raise Exception(f"channel_type is not the one that we sent. {our_channel_type=}. {their_channel_type=}.")
×
1114

1115
        remote_config = RemoteConfig(
×
1116
            payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
1117
            multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]),
1118
            htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
1119
            delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
1120
            revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
1121
            to_self_delay=payload['to_self_delay'],
1122
            dust_limit_sat=payload['dust_limit_satoshis'],
1123
            max_htlc_value_in_flight_msat=payload['max_htlc_value_in_flight_msat'],
1124
            max_accepted_htlcs=payload["max_accepted_htlcs"],
1125
            initial_msat=push_msat,
1126
            reserve_sat=payload["channel_reserve_satoshis"],
1127
            htlc_minimum_msat=payload['htlc_minimum_msat'],
1128
            next_per_commitment_point=remote_per_commitment_point,
1129
            current_per_commitment_point=None,
1130
            upfront_shutdown_script=upfront_shutdown_script,
1131
            announcement_node_sig=b'',
1132
            announcement_bitcoin_sig=b'',
1133
        )
1134
        ChannelConfig.cross_validate_params(
×
1135
            local_config=local_config,
1136
            remote_config=remote_config,
1137
            funding_sat=funding_sat,
1138
            is_local_initiator=True,
1139
            initial_feerate_per_kw=feerate,
1140
            config=self.config,
1141
            peer_features=self.features,
1142
            channel_type=our_channel_type,
1143
        )
1144

1145
        # -> funding created
1146
        # replace dummy output in funding tx
1147
        redeem_script = funding_output_script(local_config, remote_config)
×
1148
        funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
×
1149
        funding_output = PartialTxOutput.from_address_and_value(funding_address, funding_sat)
×
1150
        funding_tx.replace_output_address(DummyAddress.CHANNEL, funding_address)
×
1151
        # find and encrypt op_return data associated to funding_address
1152
        has_onchain_backup = self.lnworker and self.lnworker.has_recoverable_channels()
×
1153
        if has_onchain_backup:
×
1154
            backup_data = self.lnworker.cb_data(self.pubkey)
×
1155
            dummy_scriptpubkey = make_op_return(backup_data)
×
1156
            for o in funding_tx.outputs():
×
1157
                if o.scriptpubkey == dummy_scriptpubkey:
×
1158
                    encrypted_data = self.lnworker.encrypt_cb_data(backup_data, funding_address)
×
1159
                    assert len(encrypted_data) == len(backup_data)
×
1160
                    o.scriptpubkey = make_op_return(encrypted_data)
×
1161
                    break
×
1162
            else:
1163
                raise Exception('op_return output not found in funding tx')
×
1164
        # must not be malleable
1165
        funding_tx.set_rbf(False)
×
1166
        if not funding_tx.is_segwit():
×
1167
            raise Exception('Funding transaction is not segwit')
×
1168
        funding_txid = funding_tx.txid()
×
1169
        assert funding_txid
×
1170
        funding_index = funding_tx.outputs().index(funding_output)
×
1171
        # build remote commitment transaction
1172
        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index)
×
1173
        outpoint = Outpoint(funding_txid, funding_index)
×
1174
        constraints = ChannelConstraints(
×
1175
            flags=channel_flags,
1176
            capacity=funding_sat,
1177
            is_initiator=True,
1178
            funding_txn_minimum_depth=funding_txn_minimum_depth
1179
        )
1180
        storage = self.create_channel_storage(
×
1181
            channel_id=channel_id,
1182
            outpoint=outpoint,
1183
            local_config=local_config,
1184
            remote_config=remote_config,
1185
            constraints=constraints,
1186
            channel_type=our_channel_type,
1187
        )
1188
        # temporary channel object, not stored (storage is a dict)
1189
        temp_chan = Channel(
×
1190
            storage,
1191
            lnworker=self.lnworker,
1192
            initial_feerate=feerate
1193
        )
1194
        temp_chan.storage['funding_inputs'] = [txin.prevout.to_json() for txin in funding_tx.inputs()]
×
1195
        temp_chan.storage['has_onchain_backup'] = has_onchain_backup
×
1196
        temp_chan.storage['init_height'] = self.lnworker.network.get_local_height()
×
1197
        temp_chan.storage['init_timestamp'] = int(time.time())
×
1198
        if isinstance(self.transport, LNTransport):
×
1199
            temp_chan.add_or_update_peer_addr(self.transport.peer_addr)
×
1200
        sig_64, _ = temp_chan.sign_next_commitment()
×
1201
        self.temp_id_to_id[temp_channel_id] = channel_id
×
1202

1203
        self.send_message("funding_created",
×
1204
            temporary_channel_id=temp_channel_id,
1205
            funding_txid=funding_txid_bytes,
1206
            funding_output_index=funding_index,
1207
            signature=sig_64)
1208
        self.funding_created_sent.add(channel_id)
×
1209

1210
        # <- funding signed
1211
        payload = await self.wait_for_message('funding_signed', channel_id)
×
1212
        self.logger.info('received funding_signed')
×
1213
        remote_sig = payload['signature']
×
1214
        try:
×
1215
            temp_chan.receive_new_commitment(remote_sig, [])
×
1216
        except LNProtocolWarning as e:
×
1217
            self.send_warning(channel_id, message=str(e), close_connection=True)
×
1218
        temp_chan.open_with_first_pcp(remote_per_commitment_point, remote_sig)
×
1219
        temp_chan.set_state(ChannelState.OPENING)
×
1220
        chan = self.lnworker.add_new_channel(temp_chan)
×
1221
        if zeroconf:
×
1222
            chan.set_state(ChannelState.FUNDED)
×
1223
            self.send_channel_ready(chan)
×
1224
        return chan, funding_tx
×
1225

1226
    def create_channel_storage(
1✔
1227
        self, *,
1228
        channel_id: bytes,
1229
        outpoint: Outpoint,
1230
        local_config: LocalConfig,
1231
        remote_config: RemoteConfig,
1232
        constraints: ChannelConstraints,
1233
        channel_type: ChannelType,
1234
    ) -> dict:
1235
        chan_dict = {
×
1236
            "node_id": self.pubkey.hex(),
1237
            "channel_id": channel_id.hex(),
1238
            "short_channel_id": None,
1239
            "funding_outpoint": outpoint,
1240
            "remote_config": remote_config,
1241
            "local_config": local_config,
1242
            "constraints": constraints,
1243
            "remote_update": None,
1244
            "state": ChannelState.PREOPENING.name,
1245
            'onion_keys': {},
1246
            'data_loss_protect_remote_pcp': {},
1247
            "log": {},
1248
            "unfulfilled_htlcs": {},
1249
            "revocation_store": {},
1250
            "channel_type": channel_type,
1251
        }
1252
        return chan_dict
×
1253

1254
    @non_blocking_msg_handler
1✔
1255
    async def on_open_channel(self, payload):
1✔
1256
        """Implements the channel acceptance flow.
1257

1258
        <- open_channel message
1259
        -> accept_channel message
1260
        <- funding_created message
1261
        -> funding_signed message
1262

1263
        Channel configurations are initialized in this method.
1264
        """
1265

1266
        # <- open_channel
1267
        if payload['chain_hash'] != constants.net.rev_genesis_bytes():
×
1268
            raise Exception('wrong chain_hash')
×
1269

1270
        open_channel_tlvs = payload.get('open_channel_tlvs')
×
1271
        if open_channel_tlvs is None:
×
1272
            raise Exception("open_channel_tlvs MUST be present in open_channel, but missing")
×
1273
        channel_type = open_channel_tlvs.get('channel_type')
×
1274
        if channel_type is None:
×
1275
            raise Exception("channel_type MUST be present in open_channel, but missing")
×
1276
        # MUST fail the channel if channel_type is not suitable.
1277
        channel_type = ChannelType.from_bytes(channel_type['type'], byteorder='big')
×
1278
        if not channel_type.complies_with_features(self.features):
×
1279
            raise Exception("sender has sent a channel type we don't support")
×
1280
        assert channel_type & ChannelType.OPTION_STATIC_REMOTEKEY, "new legacy channel?!"
×
1281
        if not self.config.TEST_LN_OPEN_SRK_CHANNELS:
×
1282
            if not channel_type & ChannelType.OPTION_ANCHORS:
×
1283
                # note: BOLT-02 does NOT forbid opening new SRK chan
1284
                #       just because ANCHORS has been negotiated as peer feature
1285
                raise Exception("refusing to open new static_remotekey channel")
×
1286

1287
        is_zeroconf = bool(channel_type & ChannelType.OPTION_ZEROCONF)
×
1288
        if is_zeroconf and not self.config.ZEROCONF_TRUSTED_NODE.startswith(self.pubkey.hex()):
×
1289
            raise Exception(f"not accepting zeroconf from node {self.pubkey}")
×
1290

1291
        if self.lnworker.has_recoverable_channels() and not is_zeroconf:
×
1292
            # FIXME: we might want to keep the connection open
1293
            raise Exception('not accepting channels')
×
1294

1295
        if not self.lnworker.wallet.can_have_lightning():
×
1296
            # old wallet that cannot have lightning anymore
1297
            raise Exception('This wallet does not accept new channels')
×
1298

1299
        funding_sat = payload['funding_satoshis']
×
1300
        push_msat = payload['push_msat']
×
1301
        feerate = payload['feerate_per_kw']  # note: we are not validating this
×
1302
        temp_chan_id = payload['temporary_channel_id']
×
1303
        # store the temp id now, so that it is recognized for e.g. 'error' messages
1304
        self.temp_id_to_id[temp_chan_id] = None
×
1305
        self._cleanup_temp_channelids()
×
1306
        channel_opening_fee = open_channel_tlvs.get('channel_opening_fee', {}).get('channel_opening_fee')
×
1307
        if channel_opening_fee:  # just-in-time channel opening
×
1308
            assert is_zeroconf
×
1309
            # the opening fee consists of the fee configured by the LSP + mining fees of the funding tx
1310
            channel_opening_fee_sat = channel_opening_fee // 1000
×
1311
            if channel_opening_fee_sat > funding_sat * 0.1:
×
1312
                # TODO: if there will be some discovery channel where LSPs announce their fees
1313
                #  we should compare against the fees they announced here.
1314
                raise Exception(f"{channel_opening_fee_sat=} exceeding fee limit, rejecting channel ({funding_sat=})")
×
1315
            self.logger.info(f"just-in-time channel: {channel_opening_fee_sat=}")
×
1316

1317
        if channel_type & ChannelType.OPTION_ANCHORS:
×
1318
            multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_they_opened(
×
1319
                funding_root_secret=self.lnworker.funding_root_keypair.privkey,
1320
                remote_node_id_or_prefix=self.pubkey,
1321
                remote_funding_pubkey=payload['funding_pubkey'],
1322
            )
1323
        else:
1324
            multisig_funding_keypair = None
×
1325
        local_config = self.lnworker.make_local_config_for_new_channel(
×
1326
            funding_sat=funding_sat,
1327
            push_msat=push_msat,
1328
            initiator=REMOTE,
1329
            channel_type=channel_type,
1330
            multisig_funding_keypair=multisig_funding_keypair,
1331
            peer_features=self.features,
1332
        )
1333

1334
        upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
×
1335
            payload, 'open')
1336

1337
        remote_config = RemoteConfig(
×
1338
            payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
1339
            multisig_key=OnlyPubkeyKeypair(payload['funding_pubkey']),
1340
            htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
1341
            delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
1342
            revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
1343
            to_self_delay=payload['to_self_delay'],
1344
            dust_limit_sat=payload['dust_limit_satoshis'],
1345
            max_htlc_value_in_flight_msat=payload['max_htlc_value_in_flight_msat'],
1346
            max_accepted_htlcs=payload['max_accepted_htlcs'],
1347
            initial_msat=funding_sat * 1000 - push_msat,
1348
            reserve_sat=payload['channel_reserve_satoshis'],
1349
            htlc_minimum_msat=payload['htlc_minimum_msat'],
1350
            next_per_commitment_point=payload['first_per_commitment_point'],
1351
            current_per_commitment_point=None,
1352
            upfront_shutdown_script=upfront_shutdown_script,
1353
            announcement_node_sig=b'',
1354
            announcement_bitcoin_sig=b'',
1355
        )
1356
        ChannelConfig.cross_validate_params(
×
1357
            local_config=local_config,
1358
            remote_config=remote_config,
1359
            funding_sat=funding_sat,
1360
            is_local_initiator=False,
1361
            initial_feerate_per_kw=feerate,
1362
            config=self.config,
1363
            peer_features=self.features,
1364
            channel_type=channel_type,
1365
        )
1366

1367
        channel_flags = ord(payload['channel_flags'])
×
1368

1369
        # -> accept channel
1370
        # for the first commitment transaction
1371
        per_commitment_secret_first = get_per_commitment_secret_from_seed(
×
1372
            local_config.per_commitment_secret_seed,
1373
            RevocationStore.START_INDEX
1374
        )
1375
        per_commitment_point_first = secret_to_pubkey(
×
1376
            int.from_bytes(per_commitment_secret_first, 'big'))
1377

1378
        min_depth = 0 if is_zeroconf else 3
×
1379

1380
        accept_channel_tlvs = {
×
1381
            'upfront_shutdown_script': {
1382
                'shutdown_scriptpubkey': local_config.upfront_shutdown_script
1383
            },
1384
            'channel_type': {
1385
                'type': channel_type.to_bytes_minimal(),
1386
            },
1387
        }
1388

1389
        self.send_message(
×
1390
            'accept_channel',
1391
            temporary_channel_id=temp_chan_id,
1392
            dust_limit_satoshis=local_config.dust_limit_sat,
1393
            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
1394
            channel_reserve_satoshis=local_config.reserve_sat,
1395
            htlc_minimum_msat=local_config.htlc_minimum_msat,
1396
            minimum_depth=min_depth,
1397
            to_self_delay=local_config.to_self_delay,
1398
            max_accepted_htlcs=local_config.max_accepted_htlcs,
1399
            funding_pubkey=local_config.multisig_key.pubkey,
1400
            revocation_basepoint=local_config.revocation_basepoint.pubkey,
1401
            payment_basepoint=local_config.payment_basepoint.pubkey,
1402
            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
1403
            htlc_basepoint=local_config.htlc_basepoint.pubkey,
1404
            first_per_commitment_point=per_commitment_point_first,
1405
            accept_channel_tlvs=accept_channel_tlvs,
1406
        )
1407

1408
        # <- funding created
1409
        funding_created = await self.wait_for_message('funding_created', temp_chan_id)
×
1410

1411
        # -> funding signed
1412
        funding_idx = funding_created['funding_output_index']
×
1413
        funding_txid = funding_created['funding_txid'][::-1].hex()
×
1414
        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_idx)
×
1415
        constraints = ChannelConstraints(
×
1416
            flags=channel_flags,
1417
            capacity=funding_sat,
1418
            is_initiator=False,
1419
            funding_txn_minimum_depth=min_depth,
1420
        )
1421
        outpoint = Outpoint(funding_txid, funding_idx)
×
1422
        chan_dict = self.create_channel_storage(
×
1423
            channel_id=channel_id,
1424
            outpoint=outpoint,
1425
            local_config=local_config,
1426
            remote_config=remote_config,
1427
            constraints=constraints,
1428
            channel_type=channel_type,
1429
        )
1430
        # temporary channel object, not stored (storage is a dict)
1431
        temp_chan = Channel(
×
1432
            chan_dict,
1433
            lnworker=self.lnworker,
1434
            initial_feerate=feerate,
1435
            jit_opening_fee=channel_opening_fee,
1436
        )
1437
        temp_chan.storage['init_height'] = self.lnworker.network.get_local_height()
×
1438
        temp_chan.storage['init_timestamp'] = int(time.time())
×
1439
        if isinstance(self.transport, LNTransport):
×
1440
            temp_chan.add_or_update_peer_addr(self.transport.peer_addr)
×
1441
        remote_sig = funding_created['signature']
×
1442
        try:
×
1443
            temp_chan.receive_new_commitment(remote_sig, [])
×
1444
        except LNProtocolWarning as e:
×
1445
            self.send_warning(channel_id, message=str(e), close_connection=True)
×
1446
        sig_64, _ = temp_chan.sign_next_commitment()
×
1447
        self.send_message('funding_signed',
×
1448
            channel_id=channel_id,
1449
            signature=sig_64,
1450
        )
1451
        self.temp_id_to_id[temp_chan_id] = channel_id
×
1452
        self.funding_signed_sent.add(temp_chan.channel_id)
×
1453
        temp_chan.open_with_first_pcp(payload['first_per_commitment_point'], remote_sig)
×
1454
        temp_chan.set_state(ChannelState.OPENING)
×
1455
        chan = self.lnworker.add_new_channel(temp_chan)
×
1456
        if is_zeroconf:
×
1457
            # FIXME shouldn't we wait until funding_tx is at least in the mempool?!
1458
            #   We haven't even validated funding_tx really contains the multisig funding output!
1459
            #   This is unsafe. MUST be reworked before mainnet usage.
1460
            chan.set_state(ChannelState.FUNDED)
×
1461
            self.send_channel_ready(chan)
×
1462

1463
    def _cleanup_temp_channelids(self) -> None:
1✔
1464
        self.temp_id_to_id = {
×
1465
            tmp_id: chan_id for (tmp_id, chan_id) in self.temp_id_to_id.items()
1466
            if chan_id not in self.channels
1467
        }
1468
        if len(self.temp_id_to_id) > 25:
×
1469
            # which one of us is opening all these chans?! let's disconnect
1470
            raise Exception("temp_id_to_id is getting too large.")
×
1471

1472
    async def request_force_close(self, channel_id: bytes):
1✔
1473
        """Try to trigger the remote peer to force-close."""
1474
        await self.initialized
1✔
1475
        self.logger.info(f"trying to get remote peer to force-close chan {channel_id.hex()}")
1✔
1476
        # First, we intentionally send a "channel_reestablish" msg with an old state.
1477
        # Many nodes (but not all) automatically force-close when seeing this.
1478
        ignored_point = ecc.GENERATOR.get_public_key_bytes(compressed=True)  # ignored but valid point (BOLT2)
1✔
1479
        self.send_message(
1✔
1480
            "channel_reestablish",
1481
            channel_id=channel_id,
1482
            next_commitment_number=0,
1483
            next_revocation_number=0,
1484
            your_last_per_commitment_secret=0,
1485
            my_current_per_commitment_point=ignored_point)
1486
        # Newish nodes that have lightning/bolts/pull/950 force-close upon receiving an "error" msg,
1487
        # so send that too. E.g. old "channel_reestablish" is not enough for eclair 0.7+,
1488
        # but "error" is. see https://github.com/ACINQ/eclair/pull/2036
1489
        # The receiving node:
1490
        #   - upon receiving `error`:
1491
        #     - MUST fail the channel referred to by `channel_id`, if that channel is with the sending node.
1492
        self.send_message("error", channel_id=channel_id, data=b"", len=0)
1✔
1493

1494
    def schedule_force_closing(self, channel_id: bytes):
1✔
1495
        """ wrapper of lnworker's method, that raises if channel is not with this peer """
1496
        channels_with_peer = list(self.channels.keys())
1✔
1497
        channels_with_peer.extend(self.temp_id_to_id.values())
1✔
1498
        if channel_id not in channels_with_peer:
1✔
1499
            raise ValueError(f"channel {channel_id.hex()} does not belong to this peer")
×
1500
        chan = self.get_channel_by_id(channel_id)
1✔
1501
        if not chan:
1✔
1502
            self.logger.warning(f"tried to force-close channel {channel_id.hex()} but it is not in self.channels yet")
×
1503
        if ChanCloseOption.LOCAL_FCLOSE in chan.get_close_options():
1✔
1504
            self.lnworker.schedule_force_closing(channel_id)
1✔
1505
        else:
1506
            self.logger.info(f"tried to force-close channel {chan.get_id_for_log()} "
1✔
1507
                             f"but close option is not allowed. {chan.get_state()=!r}")
1508

1509
    async def on_channel_reestablish(self, chan: Channel, msg):
1✔
1510
        # Note: it is critical for this message handler to block processing of further messages,
1511
        #       until this msg is processed. If we are behind (lost state), and send chan_reest to the remote,
1512
        #       when the remote realizes we are behind, they might send an "error" message - but the spec mandates
1513
        #       they send chan_reest first. If we processed the error first, we might force-close and lose money!
1514
        # FIXME there are a lot of "SHOULD send an error and fail the channel" BOLT-02 cases here
1515
        #       where we don't send the error, but directly fail the channel
1516
        their_next_local_ctn = msg["next_commitment_number"]
1✔
1517
        their_oldest_unrevoked_remote_ctn = msg["next_revocation_number"]
1✔
1518
        their_local_pcp = msg["my_current_per_commitment_point"]
1✔
1519
        their_claim_of_our_last_per_commitment_secret = msg["your_last_per_commitment_secret"]
1✔
1520
        self.logger.info(
1✔
1521
            f'channel_reestablish ({chan.get_id_for_log()}): received channel_reestablish with '
1522
            f'(their_next_local_ctn={their_next_local_ctn}, '
1523
            f'their_oldest_unrevoked_remote_ctn={their_oldest_unrevoked_remote_ctn})')
1524
        if chan.get_state() >= ChannelState.FORCE_CLOSING:
1✔
1525
            self.logger.warning(
×
1526
                f"on_channel_reestablish. dropping message. illegal action. "
1527
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
1528
            return
×
1529
        # sanity checks of received values
1530
        assert their_next_local_ctn >= 0  # already done by lnmsg, as type is u64
1✔
1531
        assert their_oldest_unrevoked_remote_ctn >= 0
1✔
1532
        # ctns
1533
        oldest_unrevoked_local_ctn = chan.get_oldest_unrevoked_ctn(LOCAL)
1✔
1534
        latest_remote_ctn = chan.get_latest_ctn(REMOTE)
1✔
1535
        next_remote_ctn = chan.get_next_ctn(REMOTE)
1✔
1536
        # compare remote ctns
1537
        we_are_ahead = False
1✔
1538
        they_are_ahead_with_proof = False
1✔
1539
        they_are_ahead_without_proof = False
1✔
1540
        we_must_resend_revoke_and_ack = False
1✔
1541
        # check "next_commitment_number"
1542
        if next_remote_ctn != their_next_local_ctn:
1✔
1543
            if their_next_local_ctn == latest_remote_ctn and chan.hm.is_revack_pending(REMOTE):
1✔
1544
                # We will replay the local updates (see reestablish_channel), which should contain a commitment_signed
1545
                # (due to is_revack_pending being true), and this should remedy this situation.
1546
                pass
1✔
1547
            else:
1548
                self.logger.warning(
1✔
1549
                    f"channel_reestablish ({chan.get_id_for_log()}): "
1550
                    f"expected remote ctn {next_remote_ctn}, got {their_next_local_ctn}")
1551
                if their_next_local_ctn < next_remote_ctn:
1✔
1552
                    we_are_ahead = True
1✔
1553
                else:
1554
                    they_are_ahead_without_proof = True
1✔
1555
        # check "next_revocation_number"
1556
        if oldest_unrevoked_local_ctn != their_oldest_unrevoked_remote_ctn:
1✔
1557
            if oldest_unrevoked_local_ctn - 1 == their_oldest_unrevoked_remote_ctn:
1✔
1558
                # A node:
1559
                #    if next_revocation_number is equal to the commitment number of the last revoke_and_ack
1560
                #    the receiving node sent, AND the receiving node hasn't already received a closing_signed:
1561
                #        MUST re-send the revoke_and_ack.
1562
                we_must_resend_revoke_and_ack = True
1✔
1563
            else:
1564
                self.logger.warning(
1✔
1565
                    f"channel_reestablish ({chan.get_id_for_log()}): "
1566
                    f"expected local ctn {oldest_unrevoked_local_ctn}, got {their_oldest_unrevoked_remote_ctn}")
1567
                if their_oldest_unrevoked_remote_ctn < oldest_unrevoked_local_ctn:
1✔
1568
                    we_are_ahead = True
1✔
1569
                else:
1570
                    they_are_ahead_with_proof = True  # the claimed value will be checked against DLP
1✔
1571
        # option_data_loss_protect (DLP)
1572
        assert self.features.supports(LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT)
1✔
1573
        def are_datalossprotect_fields_valid() -> bool:
1✔
1574
            if their_oldest_unrevoked_remote_ctn > 0:
1✔
1575
                our_pcs, __ = chan.get_secret_and_point(LOCAL, their_oldest_unrevoked_remote_ctn - 1)
1✔
1576
            else:
1577
                assert their_oldest_unrevoked_remote_ctn == 0
1✔
1578
                our_pcs = bytes(32)
1✔
1579
            if our_pcs != their_claim_of_our_last_per_commitment_secret:
1✔
1580
                self.logger.error(
1✔
1581
                    f"channel_reestablish ({chan.get_id_for_log()}): "
1582
                    f"(DLP) local PCS mismatch: {our_pcs.hex()} != {their_claim_of_our_last_per_commitment_secret.hex()}")
1583
                return False
1✔
1584
            assert chan.is_static_remotekey_enabled()
1✔
1585
            return True
1✔
1586
        if not are_datalossprotect_fields_valid():
1✔
1587
            self.schedule_force_closing(chan.channel_id)
1✔
1588
            raise RemoteMisbehaving("channel_reestablish: data loss protect fields invalid")
1✔
1589
        fut = self.channel_reestablish_msg[chan.channel_id]
1✔
1590
        if they_are_ahead_with_proof:  # order matters, WE_ARE_TOXIC case must be checked first.
1✔
1591
            self.logger.warning(
1✔
1592
                f"channel_reestablish ({chan.get_id_for_log()}): "
1593
                f"remote is ahead of us (with proof)! They should force-close.")
1594
            # data_loss_protect_remote_pcp is used in lnsweep
1595
            chan.set_data_loss_protect_remote_pcp(their_next_local_ctn - 1, their_local_pcp)
1✔
1596
            chan.set_state(ChannelState.WE_ARE_TOXIC)
1✔
1597
            self.lnworker.save_channel(chan)
1✔
1598
            chan.peer_state = PeerState.BAD
1✔
1599
            # raise after we send channel_reestablish, so the remote can realize they are ahead
1600
            # FIXME what if we have multiple chans with peer? timing...
1601
            fut.set_exception(GracefulDisconnect("remote ahead of us (with proof)"))
1✔
1602
        elif they_are_ahead_without_proof:
1✔
1603
            self.logger.warning(
1✔
1604
                f"channel_reestablish ({chan.get_id_for_log()}): "
1605
                f"remote is ahead of us (without proof)! trying to force-close.")
1606
            self.schedule_force_closing(chan.channel_id)
1✔
1607
            # FIXME what if we have multiple chans with peer? timing...
1608
            fut.set_exception(GracefulDisconnect("remote ahead of us (without proof)"))
1✔
1609
        elif we_are_ahead:
1✔
1610
            self.logger.warning(f"channel_reestablish ({chan.get_id_for_log()}): we are ahead of remote! trying to force-close.")
1✔
1611
            self.schedule_force_closing(chan.channel_id)
1✔
1612
            # FIXME what if we have multiple chans with peer? timing...
1613
            fut.set_exception(GracefulDisconnect("we are ahead of remote"))
1✔
1614
        else:
1615
            # all good
1616
            fut.set_result((we_must_resend_revoke_and_ack, their_next_local_ctn))
1✔
1617
            # Block processing of further incoming messages until we finished our part of chan-reest.
1618
            # This is needed for the replaying of our local unacked updates to be sane (if the peer
1619
            # also replays some messages we must not react to them until we finished replaying our own).
1620
            # (it would be sufficient to only block messages related to this channel, but this is easier)
1621
            await self._chan_reest_finished[chan.channel_id].wait()
1✔
1622
            # Note: if the above event is never set, we won't detect if the connection was closed by remote...
1623

1624
    def _send_channel_reestablish(self, chan: Channel):
1✔
1625
        assert self.is_initialized()
1✔
1626
        chan_id = chan.channel_id
1✔
1627
        # ctns
1628
        next_local_ctn = chan.get_next_ctn(LOCAL)
1✔
1629
        oldest_unrevoked_remote_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
1✔
1630
        # send message
1631
        assert chan.is_static_remotekey_enabled()
1✔
1632
        ignored_point = ecc.GENERATOR.get_public_key_bytes(compressed=True)  # ignored but valid point (BOLT2)
1✔
1633
        if oldest_unrevoked_remote_ctn == 0:
1✔
1634
            last_rev_secret = 0
1✔
1635
        else:
1636
            last_rev_index = oldest_unrevoked_remote_ctn - 1
1✔
1637
            last_rev_secret = chan.revocation_store.retrieve_secret(RevocationStore.START_INDEX - last_rev_index)
1✔
1638
        self.send_message(
1✔
1639
            "channel_reestablish",
1640
            channel_id=chan_id,
1641
            next_commitment_number=next_local_ctn,
1642
            next_revocation_number=oldest_unrevoked_remote_ctn,
1643
            your_last_per_commitment_secret=last_rev_secret,
1644
            my_current_per_commitment_point=ignored_point)
1645
        self.logger.info(
1✔
1646
            f'channel_reestablish ({chan.get_id_for_log()}): sent channel_reestablish with '
1647
            f'(next_local_ctn={next_local_ctn}, '
1648
            f'oldest_unrevoked_remote_ctn={oldest_unrevoked_remote_ctn})')
1649

1650
    async def reestablish_channel(self, chan: Channel):
1✔
1651
        await self.initialized
1✔
1652
        chan_id = chan.channel_id
1✔
1653
        if chan.should_request_force_close:
1✔
1654
            if chan.get_state() != ChannelState.WE_ARE_TOXIC:
×
1655
                chan.set_state(ChannelState.REQUESTED_FCLOSE)
×
1656
            await self.request_force_close(chan_id)
×
1657
            chan.should_request_force_close = False
×
1658
            return
×
1659
        if chan.get_state() == ChannelState.WE_ARE_TOXIC:
1✔
1660
            # Depending on timing, the remote might not know we are behind.
1661
            # We should let them know, so that they force-close.
1662
            # We do "request force-close" with ctn=0, instead of leaking our actual ctns,
1663
            # to decrease the remote's confidence of actual data loss on our part.
1664
            await self.request_force_close(chan_id)
1✔
1665
            return
1✔
1666
        if chan.get_state() == ChannelState.FORCE_CLOSING:
1✔
1667
            # We likely got here because we found out that we are ahead (i.e. remote lost state).
1668
            # Depending on timing, the remote might not know they are behind.
1669
            # We should let them know:
1670
            self._send_channel_reestablish(chan)
1✔
1671
            return
1✔
1672
        if self.network.blockchain().is_tip_stale() \
1✔
1673
                or not self.lnworker.wallet.is_up_to_date() \
1674
                or self.lnworker.current_target_feerate_per_kw(has_anchors=chan.has_anchors()) \
1675
            is None:
1676
            # don't try to reestablish until we can do fee estimation and are up-to-date
1677
            return
×
1678
        # if we get here, we will try to do a proper reestablish
1679
        if not (ChannelState.PREOPENING < chan.get_state() < ChannelState.FORCE_CLOSING):
1✔
1680
            raise Exception(f"unexpected {chan.get_state()=} for reestablish")
×
1681
        if chan.peer_state != PeerState.DISCONNECTED:
1✔
1682
            self.logger.info(
×
1683
                f'reestablish_channel was called but channel {chan.get_id_for_log()} '
1684
                f'already in peer_state {chan.peer_state!r}')
1685
            return
×
1686
        chan.peer_state = PeerState.REESTABLISHING
1✔
1687
        util.trigger_callback('channel', self.lnworker.wallet, chan)
1✔
1688
        # ctns
1689
        oldest_unrevoked_local_ctn = chan.get_oldest_unrevoked_ctn(LOCAL)
1✔
1690
        next_local_ctn = chan.get_next_ctn(LOCAL)
1✔
1691
        oldest_unrevoked_remote_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
1✔
1692
        # BOLT-02: "A node [...] upon disconnection [...] MUST reverse any uncommitted updates sent by the other side"
1693
        chan.hm.discard_unsigned_remote_updates()
1✔
1694
        # send message
1695
        self._send_channel_reestablish(chan)
1✔
1696
        # wait until we receive their channel_reestablish
1697
        fut = self.channel_reestablish_msg[chan_id]
1✔
1698
        await fut
1✔
1699
        we_must_resend_revoke_and_ack, their_next_local_ctn = fut.result()
1✔
1700

1701
        def replay_updates_and_commitsig():
1✔
1702
            # Replay un-acked local updates (including commitment_signed) byte-for-byte.
1703
            # If we have sent them a commitment signature that they "lost" (due to disconnect),
1704
            # we need to make sure we replay the same local updates, as otherwise they could
1705
            # end up with two (or more) signed valid commitment transactions at the same ctn.
1706
            # Multiple valid ctxs at the same ctn is a major headache for pre-signing spending txns,
1707
            # e.g. for watchtowers, hence we must ensure these ctxs coincide.
1708
            # We replay the local updates even if they were not yet committed.
1709
            unacked = chan.hm.get_unacked_local_updates()
1✔
1710
            replayed_msgs = []
1✔
1711
            for ctn, messages in unacked.items():
1✔
1712
                if ctn < their_next_local_ctn:
1✔
1713
                    # They claim to have received these messages and the corresponding
1714
                    # commitment_signed, hence we must not replay them.
1715
                    continue
1✔
1716
                for raw_upd_msg in messages:
1✔
1717
                    self.transport.send_bytes(raw_upd_msg)
1✔
1718
                    replayed_msgs.append(raw_upd_msg)
1✔
1719
            self.logger.info(f'channel_reestablish ({chan.get_id_for_log()}): replayed {len(replayed_msgs)} unacked messages. '
1✔
1720
                             f'{[decode_msg(raw_upd_msg)[0] for raw_upd_msg in replayed_msgs]}')
1721

1722
        def resend_revoke_and_ack():
1✔
1723
            last_secret, last_point = chan.get_secret_and_point(LOCAL, oldest_unrevoked_local_ctn - 1)
1✔
1724
            next_secret, next_point = chan.get_secret_and_point(LOCAL, oldest_unrevoked_local_ctn + 1)
1✔
1725
            self.send_message(
1✔
1726
                "revoke_and_ack",
1727
                channel_id=chan.channel_id,
1728
                per_commitment_secret=last_secret,
1729
                next_per_commitment_point=next_point)
1730

1731
        # We need to preserve relative order of last revack and commitsig.
1732
        # note: it is not possible to recover and reestablish a channel if we are out-of-sync by
1733
        # more than one ctns, i.e. we will only ever retransmit up to one commitment_signed message.
1734
        # Hence, if we need to retransmit a revack, without loss of generality, we can either replay
1735
        # it as the first message or as the last message.
1736
        was_revoke_last = chan.hm.was_revoke_last()
1✔
1737
        if we_must_resend_revoke_and_ack and not was_revoke_last:
1✔
1738
            self.logger.info(f'channel_reestablish ({chan.get_id_for_log()}): replaying a revoke_and_ack first.')
1✔
1739
            resend_revoke_and_ack()
1✔
1740
        replay_updates_and_commitsig()
1✔
1741
        if we_must_resend_revoke_and_ack and was_revoke_last:
1✔
1742
            self.logger.info(f'channel_reestablish ({chan.get_id_for_log()}): replaying a revoke_and_ack last.')
1✔
1743
            resend_revoke_and_ack()
1✔
1744

1745
        chan.peer_state = PeerState.GOOD
1✔
1746
        self._chan_reest_finished[chan.channel_id].set()
1✔
1747
        chan_just_became_ready = (their_next_local_ctn == next_local_ctn == 1)
1✔
1748
        if chan.is_funded():
1✔
1749
            if chan_just_became_ready or self.features.supports(LnFeatures.OPTION_SCID_ALIAS_OPT):
1✔
1750
                self.send_channel_ready(chan)
1✔
1751

1752
        self.maybe_send_announcement_signatures(chan)
1✔
1753
        self.maybe_update_fee(chan)  # if needed, update fee ASAP, to avoid force-closures from this
1✔
1754
        # checks done
1755
        util.trigger_callback('channel', self.lnworker.wallet, chan)
1✔
1756
        if chan.get_state() == ChannelState.SHUTDOWN:
1✔
1757
            # if we have sent a previous shutdown, it must be retransmitted (Bolt2)
1758
            await self.taskgroup.spawn(self.send_shutdown(chan))
×
1759
        elif chan.get_state() == ChannelState.OPEN:
1✔
1760
            forwarding_enabled = self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
1✔
1761
            if forwarding_enabled and chan.short_channel_id and not chan_just_became_ready:
1✔
1762
                # send channel update so peer knows our constraints for forwarding to them
1763
                self.send_channel_update(chan)
×
1764

1765
    def send_channel_ready(self, chan: Channel):
1✔
1766
        assert chan.is_funded()
1✔
1767
        if chan.sent_channel_ready:
1✔
1768
            return
×
1769
        channel_id = chan.channel_id
1✔
1770
        per_commitment_secret_index = RevocationStore.START_INDEX - 1
1✔
1771
        second_per_commitment_point = secret_to_pubkey(int.from_bytes(
1✔
1772
            get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
1773
        channel_ready_tlvs = {}
1✔
1774
        if self.features.supports(LnFeatures.OPTION_SCID_ALIAS_OPT):
1✔
1775
            # LND requires that we send an alias if the option has been negotiated in INIT.
1776
            # otherwise, the channel will not be marked as active.
1777
            # This does not apply if the channel was previously marked active without an alias.
1778
            channel_ready_tlvs['short_channel_id'] = {'alias': chan.get_local_scid_alias(create_new_if_needed=True)}
1✔
1779
        # note: if 'channel_ready' was not yet received, we might send it multiple times
1780
        self.send_message(
1✔
1781
            "channel_ready",
1782
            channel_id=channel_id,
1783
            second_per_commitment_point=second_per_commitment_point,
1784
            channel_ready_tlvs=channel_ready_tlvs)
1785
        chan.sent_channel_ready = True
1✔
1786
        self.maybe_mark_open(chan)
1✔
1787

1788
    def on_channel_ready(self, chan: Channel, payload):
1✔
1789
        self.logger.info(f"on_channel_ready. channel: {chan.channel_id.hex()}")
1✔
1790
        if chan.peer_state != PeerState.GOOD:  # should never happen
1✔
1791
            raise Exception(f"received channel_ready in unexpected {chan.peer_state=!r}")
×
1792
        if chan.is_closed():
1✔
1793
            self.logger.warning(
×
1794
                f"on_channel_ready. dropping message. illegal action. "
1795
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
1796
            return
×
1797
        # save remote alias for use in invoices
1798
        scid_alias = payload.get('channel_ready_tlvs', {}).get('short_channel_id', {}).get('alias')
1✔
1799
        if scid_alias:
1✔
1800
            chan.save_remote_scid_alias(scid_alias)
1✔
1801
        if not chan.config[LOCAL].funding_locked_received:
1✔
1802
            their_next_point = payload["second_per_commitment_point"]
×
1803
            chan.config[REMOTE].next_per_commitment_point = their_next_point
×
1804
            chan.config[LOCAL].funding_locked_received = True
×
1805
            self.lnworker.save_channel(chan)
×
1806
        self.maybe_mark_open(chan)
1✔
1807

1808
    def send_node_announcement(self, alias:str, color_hex:str):
1✔
1809
        from .channel_db import NodeInfo
×
1810
        timestamp = int(time.time())
×
1811
        node_id = privkey_to_pubkey(self.privkey)
×
1812
        features = self.features.for_node_announcement()
×
1813
        flen = features.min_len()
×
1814
        rgb_color = bytes.fromhex(color_hex)
×
1815
        alias = bytes(alias, 'utf8')
×
1816
        alias += bytes(32 - len(alias))
×
1817
        if self.config.LIGHTNING_LISTEN is not None:
×
1818
            addr = self.config.LIGHTNING_LISTEN
×
1819
            try:
×
1820
                hostname, port = addr.split(':')
×
1821
                if port is None:  # use default port if not specified
×
1822
                    port = 9735
×
1823
                addresses = NodeInfo.to_addresses_field(hostname, int(port))
×
1824
            except Exception:
×
1825
                self.logger.exception(f"Invalid lightning_listen address: {addr}")
×
1826
                return
×
1827
        else:
1828
            addresses = b''
×
1829
        raw_msg = encode_msg(
×
1830
            "node_announcement",
1831
            flen=flen,
1832
            features=features,
1833
            timestamp=timestamp,
1834
            rgb_color=rgb_color,
1835
            node_id=node_id,
1836
            alias=alias,
1837
            addrlen=len(addresses),
1838
            addresses=addresses)
1839
        h = sha256d(raw_msg[64+2:])
×
1840
        signature = ecc.ECPrivkey(self.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s)
×
1841
        message_type, payload = decode_msg(raw_msg)
×
1842
        payload['signature'] = signature
×
1843
        raw_msg = encode_msg(message_type, **payload)
×
1844
        self.transport.send_bytes(raw_msg)
×
1845

1846
    def maybe_send_channel_announcement(self, chan: Channel):
1✔
1847
        node_sigs = [chan.config[REMOTE].announcement_node_sig, chan.config[LOCAL].announcement_node_sig]
×
1848
        bitcoin_sigs = [chan.config[REMOTE].announcement_bitcoin_sig, chan.config[LOCAL].announcement_bitcoin_sig]
×
1849
        if not bitcoin_sigs[0] or not bitcoin_sigs[1]:
×
1850
            return
×
1851
        raw_msg, is_reverse = chan.construct_channel_announcement_without_sigs()
×
1852
        if is_reverse:
×
1853
            node_sigs.reverse()
×
1854
            bitcoin_sigs.reverse()
×
1855
        message_type, payload = decode_msg(raw_msg)
×
1856
        payload['node_signature_1'] = node_sigs[0]
×
1857
        payload['node_signature_2'] = node_sigs[1]
×
1858
        payload['bitcoin_signature_1'] = bitcoin_sigs[0]
×
1859
        payload['bitcoin_signature_2'] = bitcoin_sigs[1]
×
1860
        raw_msg = encode_msg(message_type, **payload)
×
1861
        self.transport.send_bytes(raw_msg)
×
1862
        # also send channel update
1863
        self.send_channel_update(chan)
×
1864

1865
    def send_channel_update(self, chan: Channel):
1✔
1866
        chan_upd = chan.get_outgoing_gossip_channel_update()
1✔
1867
        self.transport.send_bytes(chan_upd)
1✔
1868

1869
    def maybe_mark_open(self, chan: Channel):
1✔
1870
        if not chan.sent_channel_ready:
1✔
1871
            return
×
1872
        if not chan.config[LOCAL].funding_locked_received:
1✔
1873
            return
×
1874
        self.mark_open(chan)
1✔
1875

1876
    def mark_open(self, chan: Channel):
1✔
1877
        assert chan.is_funded()
1✔
1878
        # only allow state transition from "FUNDED" to "OPEN"
1879
        old_state = chan.get_state()
1✔
1880
        if old_state == ChannelState.OPEN:
1✔
1881
            return
1✔
1882
        if old_state != ChannelState.FUNDED:
1✔
1883
            self.logger.info(f"cannot mark open ({chan.get_id_for_log()}), current state: {repr(old_state)}")
×
1884
            return
×
1885
        assert chan.config[LOCAL].funding_locked_received
1✔
1886
        chan.set_state(ChannelState.OPEN)
1✔
1887
        util.trigger_callback('channel', self.lnworker.wallet, chan)
1✔
1888
        # peer may have sent us a channel update for the incoming direction previously
1889
        pending_channel_update = self.orphan_channel_updates.get(chan.short_channel_id)
1✔
1890
        if pending_channel_update:
1✔
1891
            chan.set_remote_update(pending_channel_update)
×
1892
        self.logger.info(f"CHANNEL OPENING COMPLETED ({chan.get_id_for_log()})")
1✔
1893
        forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
1✔
1894
        if forwarding_enabled and chan.short_channel_id:
1✔
1895
            # send channel_update of outgoing edge to peer,
1896
            # so that channel can be used to receive payments
1897
            self.send_channel_update(chan)
1✔
1898

1899
    def maybe_send_announcement_signatures(self, chan: Channel, is_reply=False):
1✔
1900
        if not chan.is_public() or chan.short_channel_id is None:
1✔
1901
            return
×
1902
        if chan.sent_announcement_signatures:
1✔
1903
            return
1✔
1904
        if not is_reply and chan.config[REMOTE].announcement_node_sig:
1✔
1905
            return
×
1906
        h = chan.get_channel_announcement_hash()
1✔
1907
        bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s)
1✔
1908
        node_signature = ecc.ECPrivkey(self.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s)
1✔
1909
        self.send_message(
1✔
1910
            "announcement_signatures",
1911
            channel_id=chan.channel_id,
1912
            short_channel_id=chan.short_channel_id,
1913
            node_signature=node_signature,
1914
            bitcoin_signature=bitcoin_signature
1915
        )
1916
        chan.config[LOCAL].announcement_node_sig = node_signature
1✔
1917
        chan.config[LOCAL].announcement_bitcoin_sig = bitcoin_signature
1✔
1918
        self.lnworker.save_channel(chan)
1✔
1919
        chan.sent_announcement_signatures = True
1✔
1920

1921
    def on_update_fail_htlc(self, chan: Channel, payload):
1✔
1922
        htlc_id = payload["id"]
1✔
1923
        reason = payload["reason"]
1✔
1924
        self.logger.info(f"on_update_fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}")
1✔
1925
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
1926
            self.logger.warning(
×
1927
                f"on_update_fail_htlc. dropping message. illegal action. "
1928
                f"chan={chan.get_id_for_log()}. {htlc_id=}. {chan.get_state()=!r}. {chan.peer_state=!r}")
1929
            return
×
1930
        chan.receive_fail_htlc(htlc_id, error_bytes=reason)  # TODO handle exc and maybe fail channel (e.g. bad htlc_id)
1✔
1931

1932
    def maybe_send_commitment(self, chan: Channel) -> bool:
1✔
1933
        assert util.get_running_loop() == util.get_asyncio_loop(), f"this must be run on the asyncio thread!"
1✔
1934
        if not chan.can_update_ctx(proposer=LOCAL):
1✔
1935
            return False
×
1936
        # REMOTE should revoke first before we can sign a new ctx
1937
        if chan.hm.is_revack_pending(REMOTE):
1✔
1938
            return False
1✔
1939
        # if there are no changes, we will not (and must not) send a new commitment
1940
        if not chan.has_pending_changes(REMOTE):
1✔
1941
            return False
1✔
1942
        now = time.monotonic()
1✔
1943
        if now - self._last_commitsig_sent_time < self.MIN_TIME_BETWEEN_SENDING_COMMITSIGS:
1✔
1944
            # We recently sent "commitment_signed". Delay sending again, to allow batching updates.
1945
            # No need to set a timer, htlc_switch polling will call us again.
1946
            return False
1✔
1947
        self._last_commitsig_sent_time = now
1✔
1948
        self.logger.info(f'send_commitment. chan {chan.short_channel_id}. ctn: {chan.get_next_ctn(REMOTE)}.')
1✔
1949
        sig_64, htlc_sigs = chan.sign_next_commitment()
1✔
1950
        self.send_message("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=len(htlc_sigs), htlc_signature=b"".join(htlc_sigs))
1✔
1951
        return True
1✔
1952

1953
    def send_htlc(
1✔
1954
        self,
1955
        *,
1956
        chan: Channel,
1957
        payment_hash: bytes,
1958
        amount_msat: int,
1959
        cltv_abs: int,
1960
        onion: OnionPacket,
1961
        session_key: Optional[bytes] = None,
1962
    ) -> UpdateAddHtlc:
1963
        assert chan.can_send_update_add_htlc(), f"cannot send updates: {chan.short_channel_id}"
1✔
1964
        htlc = UpdateAddHtlc(amount_msat=amount_msat, payment_hash=payment_hash, cltv_abs=cltv_abs, timestamp=int(time.time()))
1✔
1965
        htlc = chan.add_htlc(htlc)
1✔
1966
        if session_key:
1✔
1967
            chan.set_onion_key(htlc.htlc_id, session_key) # should it be the outer onion secret?
1✔
1968
        self.logger.info(f"starting payment. htlc: {htlc}")
1✔
1969
        self.send_message(
1✔
1970
            "update_add_htlc",
1971
            channel_id=chan.channel_id,
1972
            id=htlc.htlc_id,
1973
            cltv_expiry=htlc.cltv_abs,
1974
            amount_msat=htlc.amount_msat,
1975
            payment_hash=htlc.payment_hash,
1976
            onion_routing_packet=onion.to_bytes())
1977
        self.maybe_send_commitment(chan)
1✔
1978
        return htlc
1✔
1979

1980
    def pay(self, *,
1✔
1981
            route: 'LNPaymentRoute',
1982
            chan: Channel,
1983
            amount_msat: int,
1984
            total_msat: int,
1985
            payment_hash: bytes,
1986
            min_final_cltv_delta: int,
1987
            payment_secret: bytes,
1988
            trampoline_onion: Optional[OnionPacket] = None,
1989
        ) -> UpdateAddHtlc:
1990

1991
        assert amount_msat > 0, "amount_msat is not greater zero"
1✔
1992
        assert len(route) > 0
1✔
1993
        if not chan.can_send_update_add_htlc():
1✔
1994
            raise PaymentFailure("Channel cannot send update_add_htlc")
1✔
1995
        onion, amount_msat, cltv_abs, session_key = self.lnworker.create_onion_for_route(
1✔
1996
            route=route,
1997
            amount_msat=amount_msat,
1998
            total_msat=total_msat,
1999
            payment_hash=payment_hash,
2000
            min_final_cltv_delta=min_final_cltv_delta,
2001
            payment_secret=payment_secret,
2002
            trampoline_onion=trampoline_onion
2003
        )
2004
        htlc = self.send_htlc(
1✔
2005
            chan=chan,
2006
            payment_hash=payment_hash,
2007
            amount_msat=amount_msat,
2008
            cltv_abs=cltv_abs,
2009
            onion=onion,
2010
            session_key=session_key,
2011
        )
2012
        return htlc
1✔
2013

2014
    def send_revoke_and_ack(self, chan: Channel) -> None:
1✔
2015
        if not chan.can_update_ctx(proposer=LOCAL):
1✔
2016
            return
×
2017
        self.logger.info(f'send_revoke_and_ack. chan {chan.short_channel_id}. ctn: {chan.get_oldest_unrevoked_ctn(LOCAL)}')
1✔
2018
        rev = chan.revoke_current_commitment()
1✔
2019
        self.lnworker.save_channel(chan)
1✔
2020
        self.send_message("revoke_and_ack",
1✔
2021
            channel_id=chan.channel_id,
2022
            per_commitment_secret=rev.per_commitment_secret,
2023
            next_per_commitment_point=rev.next_per_commitment_point)
2024
        self.maybe_send_commitment(chan)
1✔
2025

2026
    def on_commitment_signed(self, chan: Channel, payload) -> None:
1✔
2027
        self.logger.info(f'on_commitment_signed. chan {chan.short_channel_id}. ctn: {chan.get_next_ctn(LOCAL)}.')
1✔
2028
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2029
            self.logger.warning(
×
2030
                f"on_commitment_signed. dropping message. illegal action. "
2031
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2032
            return
×
2033
        # make sure there were changes to the ctx, otherwise the remote peer is misbehaving
2034
        if not chan.has_pending_changes(LOCAL):
1✔
2035
            # TODO if feerate changed A->B->A; so there were updates but the value is identical,
2036
            #      then it might be legal to send a commitment_signature
2037
            #      see https://github.com/lightningnetwork/lightning-rfc/pull/618
2038
            raise RemoteMisbehaving('received commitment_signed without pending changes')
×
2039
        # REMOTE should wait until we have revoked
2040
        if chan.hm.is_revack_pending(LOCAL):
1✔
2041
            raise RemoteMisbehaving('received commitment_signed before we revoked previous ctx')
×
2042
        data = payload["htlc_signature"]
1✔
2043
        htlc_sigs = list(chunks(data, 64))
1✔
2044
        chan.receive_new_commitment(payload["signature"], htlc_sigs)
1✔
2045
        self.send_revoke_and_ack(chan)
1✔
2046
        self.received_commitsig_event.set()
1✔
2047
        self.received_commitsig_event.clear()
1✔
2048

2049
    def on_update_fulfill_htlc(self, chan: Channel, payload):
1✔
2050
        preimage = payload["payment_preimage"]
1✔
2051
        payment_hash = sha256(preimage)
1✔
2052
        htlc_id = payload["id"]
1✔
2053
        self.logger.info(f"on_update_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}")
1✔
2054
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2055
            self.logger.warning(
×
2056
                f"on_update_fulfill_htlc. dropping message. illegal action. "
2057
                f"chan={chan.get_id_for_log()}. {htlc_id=}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2058
            return
×
2059
        chan.receive_htlc_settle(preimage, htlc_id)  # TODO handle exc and maybe fail channel (e.g. bad htlc_id)
1✔
2060

2061
    def on_update_fail_malformed_htlc(self, chan: Channel, payload):
1✔
2062
        htlc_id = payload["id"]
1✔
2063
        failure_code = payload["failure_code"]
1✔
2064
        self.logger.info(f"on_update_fail_malformed_htlc. chan {chan.get_id_for_log()}. "
1✔
2065
                         f"htlc_id {htlc_id}. failure_code={failure_code}")
2066
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2067
            self.logger.warning(
×
2068
                f"on_update_fail_malformed_htlc. dropping message. illegal action. "
2069
                f"chan={chan.get_id_for_log()}. {htlc_id=}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2070
            return
×
2071
        if failure_code & OnionFailureCodeMetaFlag.BADONION == 0:
1✔
2072
            self.schedule_force_closing(chan.channel_id)
×
2073
            raise RemoteMisbehaving(f"received update_fail_malformed_htlc with unexpected failure code: {failure_code}")
×
2074
        reason = OnionRoutingFailure(code=failure_code, data=payload["sha256_of_onion"])
1✔
2075
        chan.receive_fail_htlc(htlc_id, error_bytes=None, reason=reason)
1✔
2076

2077
    def on_update_add_htlc(self, chan: Channel, payload):
1✔
2078
        payment_hash = payload["payment_hash"]
1✔
2079
        htlc_id = payload["id"]
1✔
2080
        cltv_abs = payload["cltv_expiry"]
1✔
2081
        amount_msat_htlc = payload["amount_msat"]
1✔
2082
        onion_packet = payload["onion_routing_packet"]
1✔
2083
        htlc = UpdateAddHtlc(
1✔
2084
            amount_msat=amount_msat_htlc,
2085
            payment_hash=payment_hash,
2086
            cltv_abs=cltv_abs,
2087
            timestamp=int(time.time()),
2088
            htlc_id=htlc_id)
2089
        self.logger.info(f"on_update_add_htlc. chan {chan.short_channel_id}. htlc={str(htlc)}")
1✔
2090
        if chan.get_state() != ChannelState.OPEN:
1✔
2091
            raise RemoteMisbehaving(f"received update_add_htlc while chan.get_state() != OPEN. state was {chan.get_state()!r}")
×
2092
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2093
            self.logger.warning(
×
2094
                f"on_update_add_htlc. dropping message. illegal action. "
2095
                f"chan={chan.get_id_for_log()}. {htlc_id=}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2096
            return
×
2097
        if cltv_abs > bitcoin.NLOCKTIME_BLOCKHEIGHT_MAX:
1✔
2098
            self.schedule_force_closing(chan.channel_id)
×
2099
            raise RemoteMisbehaving(f"received update_add_htlc with {cltv_abs=} > BLOCKHEIGHT_MAX")
×
2100
        # add htlc
2101
        chan.receive_htlc(htlc, onion_packet)
1✔
2102
        util.trigger_callback('htlc_added', chan, htlc, RECEIVED)
1✔
2103

2104
    @staticmethod
1✔
2105
    def _check_accepted_final_htlc(
1✔
2106
            *, chan: Channel,
2107
            htlc: UpdateAddHtlc,
2108
            processed_onion: ProcessedOnionPacket,
2109
            is_trampoline_onion: bool = False,
2110
            log_fail_reason: Callable[[str], None],
2111
    ) -> tuple[bytes, int, int, OnionRoutingFailure]:
2112
        """
2113
        Perform checks that are invariant (results do not depend on height, network conditions, etc.)
2114
        for htlcs of which we are the receiver (forwarding htlcs will have their checks in maybe_forward_htlc).
2115
        May raise OnionRoutingFailure
2116
        """
2117
        assert processed_onion.are_we_final, processed_onion
1✔
2118
        if (amt_to_forward := processed_onion.amt_to_forward) is None:
1✔
2119
            log_fail_reason(f"'amt_to_forward' missing from onion")
×
2120
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
2121
        if (cltv_abs_from_onion := processed_onion.outgoing_cltv_value) is None:
1✔
2122
            log_fail_reason(f"'outgoing_cltv_value' missing from onion")
×
2123
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
2124
        if cltv_abs_from_onion > htlc.cltv_abs:
1✔
2125
            log_fail_reason(f"cltv_abs_from_onion != htlc.cltv_abs")
×
2126
            raise OnionRoutingFailure(
×
2127
                code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY,
2128
                data=htlc.cltv_abs.to_bytes(4, byteorder="big"))
2129

2130
        exc_incorrect_or_unknown_pd = OnionRoutingFailure(
1✔
2131
            code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
2132
            data=amt_to_forward.to_bytes(8, byteorder="big")) # height will be added later
2133
        if (total_msat := processed_onion.total_msat) is None:
1✔
2134
            log_fail_reason(f"'total_msat' missing from onion")
×
2135
            raise exc_incorrect_or_unknown_pd
×
2136

2137
        if chan.jit_opening_fee:
1✔
2138
            channel_opening_fee = chan.jit_opening_fee
×
2139
            total_msat -= channel_opening_fee
×
2140
            amt_to_forward -= channel_opening_fee
×
2141
        else:
2142
            channel_opening_fee = 0
1✔
2143

2144
        if not is_trampoline_onion:
1✔
2145
            # for inner trampoline onions amt_to_forward can be larger than the htlc amount
2146
            if amt_to_forward > htlc.amount_msat:
1✔
2147
                log_fail_reason(f"{amt_to_forward=} > {htlc.amount_msat=}")
×
2148
                raise OnionRoutingFailure(
×
2149
                    code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT,
2150
                    data=htlc.amount_msat.to_bytes(8, byteorder="big"))
2151

2152
        if (payment_secret_from_onion := processed_onion.payment_secret) is None:
1✔
2153
            log_fail_reason(f"'payment_secret' missing from onion")
×
2154
            raise exc_incorrect_or_unknown_pd
×
2155

2156
        return payment_secret_from_onion, total_msat, channel_opening_fee, exc_incorrect_or_unknown_pd
1✔
2157

2158
    def _check_unfulfilled_htlc(
1✔
2159
        self, *,
2160
        chan: Channel,
2161
        htlc: UpdateAddHtlc,
2162
        processed_onion: ProcessedOnionPacket,
2163
        outer_onion_payment_secret: bytes = None,  # used to group trampoline htlcs for forwarding
2164
    ) -> str:
2165
        """
2166
        Does additional checks on the incoming htlc and return the payment key if the tests pass,
2167
        otherwise raises OnionRoutingError which will get the htlc failed.
2168
        """
2169
        _log_fail_reason = self._log_htlc_fail_reason_cb(chan.channel_id, htlc, processed_onion.hop_data.payload)
1✔
2170

2171
        # Check that our blockchain tip is sufficiently recent so that we have an approx idea of the height.
2172
        # We should not release the preimage for an HTLC that its sender could already time out as
2173
        # then they might try to force-close and it becomes a race.
2174
        chain = self.network.blockchain()
1✔
2175
        local_height = chain.height()
1✔
2176
        blocks_to_expiry = max(htlc.cltv_abs - local_height, 0)
1✔
2177
        if chain.is_tip_stale():
1✔
2178
            _log_fail_reason(f"our chain tip is stale: {local_height=}")
×
2179
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
×
2180

2181
        payment_hash = htlc.payment_hash
1✔
2182
        if not processed_onion.are_we_final:
1✔
2183
            if outer_onion_payment_secret:
1✔
2184
                # this is a trampoline forwarding htlc, multiple incoming trampoline htlcs can be collected
2185
                payment_key = (payment_hash + outer_onion_payment_secret).hex()
1✔
2186
                return payment_key
1✔
2187
            # this is a regular htlc to forward, it will get its own set of size 1 keyed by htlc_key
2188
            # Additional checks required only for forwarding nodes will be done in maybe_forward_htlc().
2189
            payment_key = serialize_htlc_key(chan.get_scid_or_local_alias(), htlc.htlc_id)
1✔
2190
            return payment_key
1✔
2191

2192
        # parse parameters and perform checks that are invariant
2193
        payment_secret_from_onion, total_msat, channel_opening_fee, exc_incorrect_or_unknown_pd = (
1✔
2194
            self._check_accepted_final_htlc(
2195
                chan=chan,
2196
                htlc=htlc,
2197
                processed_onion=processed_onion,
2198
                is_trampoline_onion=bool(outer_onion_payment_secret),
2199
                log_fail_reason=_log_fail_reason,
2200
            ))
2201
        # trampoline htlcs of which we are the final receiver will first get grouped by the outer
2202
        # onions secret to allow grouping a multi-trampoline mpp in different sets. Once a trampoline
2203
        # payment part is completed (sum(htlcs) >= (trampoline-)amt_to_forward), its htlcs get moved into
2204
        # the htlc set representing the whole payment (payment key derived from trampoline/invoice secret).
2205
        payment_key = (payment_hash + (outer_onion_payment_secret or payment_secret_from_onion)).hex()
1✔
2206

2207
        # for safety, still enforce MIN_FINAL_CLTV_DELTA here even if payment_hash is in dont_expire_htlcs
2208
        if blocks_to_expiry < MIN_FINAL_CLTV_DELTA_ACCEPTED:
1✔
2209
            # this check should be done here for new htlcs and ongoing on pending sets.
2210
            # Here it is done so that invalid received htlcs will never get added to a set,
2211
            # so the set still has a chance to succeed until mpp timeout.
2212
            _log_fail_reason(f"htlc.cltv_abs is unreasonably close: {htlc.cltv_abs=}, {local_height=}")
1✔
2213
            raise exc_incorrect_or_unknown_pd
1✔
2214

2215
        # extract trampoline
2216
        if processed_onion.trampoline_onion_packet:
1✔
2217
            trampoline_onion = self._process_incoming_onion_packet(
1✔
2218
                processed_onion.trampoline_onion_packet,
2219
                payment_hash=payment_hash,
2220
                is_trampoline=True)
2221

2222
            # compare trampoline onion against outer onion according to:
2223
            # https://github.com/lightning/bolts/blob/9938ab3d6160a3ba91f3b0e132858ab14bfe4f81/04-onion-routing.md?plain=1#L547-L553
2224
            if trampoline_onion.are_we_final:
1✔
2225
                try:
1✔
2226
                    assert not processed_onion.outgoing_cltv_value < trampoline_onion.outgoing_cltv_value
1✔
2227
                    is_mpp = processed_onion.total_msat > processed_onion.amt_to_forward
1✔
2228
                    if is_mpp:
1✔
2229
                        assert not processed_onion.total_msat < trampoline_onion.amt_to_forward
1✔
2230
                    else:
2231
                        assert not processed_onion.amt_to_forward < trampoline_onion.amt_to_forward
1✔
2232
                except AssertionError:
×
2233
                    _log_fail_reason(f'incorrect trampoline onion {processed_onion=}\n{trampoline_onion=}')
×
2234
                    raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
2235

2236
            return self._check_unfulfilled_htlc(
1✔
2237
                chan=chan,
2238
                htlc=htlc,
2239
                processed_onion=trampoline_onion,
2240
                outer_onion_payment_secret=payment_secret_from_onion,
2241
            )
2242

2243
        info = self.lnworker.get_payment_info(payment_hash, direction=RECEIVED)
1✔
2244
        if info is None:
1✔
2245
            _log_fail_reason(f"no payment_info found for RHASH {payment_hash.hex()}")
×
2246
            raise exc_incorrect_or_unknown_pd
×
2247
        elif info.status == PR_PAID:
1✔
2248
            _log_fail_reason(f"invoice already paid: {payment_hash.hex()=}")
1✔
2249
            raise exc_incorrect_or_unknown_pd
1✔
2250
        elif blocks_to_expiry < info.min_final_cltv_delta:
1✔
2251
            _log_fail_reason(
1✔
2252
                f"min final cltv delta lower than requested: "
2253
                f"{payment_hash.hex()=} {htlc.cltv_abs=} {blocks_to_expiry=}"
2254
            )
2255
            raise exc_incorrect_or_unknown_pd
1✔
2256
        elif htlc.timestamp > info.expiration_ts:  # the set will get failed too if now > exp_ts
1✔
2257
            _log_fail_reason(f"not accepting htlc for expired invoice")
1✔
2258
            raise exc_incorrect_or_unknown_pd
1✔
2259
        elif not info.invoice_features.supports(LnFeatures.BASIC_MPP_OPT) and total_msat > htlc.amount_msat:
1✔
2260
            # in _check_unfulfilled_htlc_set we check the count to prevent mpp through overpayment
2261
            _log_fail_reason(f"got mpp but we requested no mpp in the invoice: {total_msat=} > {htlc.amount_msat=}")
×
2262
            raise exc_incorrect_or_unknown_pd
×
2263

2264
        expected_payment_secret = self.lnworker.get_payment_secret(payment_hash)
1✔
2265
        if not util.constant_time_compare(payment_secret_from_onion, expected_payment_secret):
1✔
2266
            _log_fail_reason(f'incorrect payment secret: {payment_secret_from_onion.hex()=}')
1✔
2267
            raise exc_incorrect_or_unknown_pd
1✔
2268

2269
        invoice_msat = info.amount_msat
1✔
2270
        if channel_opening_fee:
1✔
2271
            # deduct just-in-time channel fees from invoice amount
2272
            invoice_msat -= channel_opening_fee
×
2273

2274
        if not (invoice_msat is None or invoice_msat <= total_msat <= 2 * invoice_msat):
1✔
2275
            _log_fail_reason(f"{total_msat=} too different from {invoice_msat=}")
1✔
2276
            raise exc_incorrect_or_unknown_pd
1✔
2277

2278
        return payment_key
1✔
2279

2280
    def _fulfill_htlc_set(self, payment_key: str, preimage: bytes):
1✔
2281
        htlc_set = self.lnworker.received_mpp_htlcs[payment_key]
1✔
2282
        assert len(htlc_set.htlcs) > 0, f"{htlc_set=}"
1✔
2283
        assert htlc_set.resolution == RecvMPPResolution.SETTLING
1✔
2284
        assert htlc_set.parent_set_key is None, f"Must not settle child {htlc_set=}"
1✔
2285
        # get payment hash of any htlc in the set (they are all the same)
2286
        payment_hash = htlc_set.get_payment_hash()
1✔
2287
        assert payment_hash is not None, htlc_set
1✔
2288
        assert payment_hash.hex() not in self.lnworker.dont_settle_htlcs
1✔
2289
        self.lnworker.dont_expire_htlcs.pop(payment_hash.hex(), None)  # htlcs wont get expired anymore
1✔
2290
        for mpp_htlc in list(htlc_set.htlcs):
1✔
2291
            htlc_id = mpp_htlc.htlc.htlc_id
1✔
2292
            chan = self.get_channel_by_id(mpp_htlc.channel_id)
1✔
2293
            if chan is None:
1✔
2294
                # this htlc belongs to another peer and has to be settled in their htlc_switch
2295
                continue
1✔
2296
            if not chan.can_update_ctx(proposer=LOCAL):
1✔
2297
                continue
×
2298
            self.logger.info(f"fulfill htlc: {chan.short_channel_id}. {htlc_id=}. {payment_hash.hex()=}")
1✔
2299
            if chan.hm.was_htlc_preimage_released(htlc_id=htlc_id, htlc_proposer=REMOTE):
1✔
2300
                # this check is intended to gracefully handle stale htlcs in the set, e.g. after a crash
2301
                self.logger.debug(f"{mpp_htlc=} was already settled before, dropping it.")
×
2302
                htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
×
2303
                continue
×
2304
            self._fulfill_htlc(chan, htlc_id, preimage)
1✔
2305
            htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
1✔
2306
            # reset just-in-time opening fee of channel
2307
            chan.jit_opening_fee = None
1✔
2308

2309
        self.lnworker.received_mpp_htlcs[payment_key] = htlc_set  # save updated set
1✔
2310

2311
    def _fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
1✔
2312
        assert chan.hm.is_htlc_irrevocably_added_yet(htlc_proposer=REMOTE, htlc_id=htlc_id)
1✔
2313
        self.received_htlcs_pending_removal.add((chan, htlc_id))
1✔
2314
        chan.settle_htlc(preimage, htlc_id)
1✔
2315
        self.send_message(
1✔
2316
            "update_fulfill_htlc",
2317
            channel_id=chan.channel_id,
2318
            id=htlc_id,
2319
            payment_preimage=preimage)
2320

2321
    def _fail_htlc_set(
1✔
2322
        self,
2323
        payment_key: str,
2324
        error_tuple: Tuple[Optional[bytes], Optional[OnionFailureCode | int], Optional[bytes]],
2325
    ):
2326
        htlc_set = self.lnworker.received_mpp_htlcs[payment_key]
1✔
2327
        assert htlc_set.resolution in (RecvMPPResolution.FAILED, RecvMPPResolution.EXPIRED)
1✔
2328

2329
        raw_error, error_code, error_data = error_tuple
1✔
2330
        local_height = self.network.blockchain().height()
1✔
2331
        payment_hash = htlc_set.get_payment_hash()
1✔
2332
        assert payment_hash is not None, "Empty htlc set?"
1✔
2333
        for mpp_htlc in list(htlc_set.htlcs):
1✔
2334
            chan = self.get_channel_by_id(mpp_htlc.channel_id)
1✔
2335
            htlc_id = mpp_htlc.htlc.htlc_id
1✔
2336
            if chan is None:
1✔
2337
                # this htlc belongs to another peer and has to be settled in their htlc_switch
2338
                continue
1✔
2339
            if not chan.can_update_ctx(proposer=LOCAL):
1✔
2340
                continue
×
2341
            assert chan.hm.is_htlc_irrevocably_added_yet(htlc_proposer=REMOTE, htlc_id=htlc_id)
1✔
2342
            if chan.hm.was_htlc_failed(htlc_id=htlc_id, htlc_proposer=REMOTE):
1✔
2343
                # this check is intended to gracefully handle stale htlcs in the set, e.g. after a crash
2344
                self.logger.debug(f"{mpp_htlc=} was already failed before, dropping it.")
×
2345
                htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
×
2346
                continue
×
2347
            onion_packet = self._parse_onion_packet(mpp_htlc.unprocessed_onion)
1✔
2348
            processed_onion_packet = self._process_incoming_onion_packet(
1✔
2349
                onion_packet,
2350
                payment_hash=payment_hash,
2351
                is_trampoline=False,
2352
            )
2353
            if raw_error:
1✔
2354
                error_bytes = obfuscate_onion_error(raw_error, onion_packet.public_key, self.privkey)
1✔
2355
            else:
2356
                assert isinstance(error_code, (OnionFailureCode, int))
1✔
2357
                if error_code == OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS:
1✔
2358
                    amount_to_forward = processed_onion_packet.amt_to_forward
1✔
2359
                    # if this was a trampoline htlc we use the inner amount_to_forward as this is
2360
                    # the value known by the sender
2361
                    if processed_onion_packet.trampoline_onion_packet:
1✔
2362
                        processed_trampoline_onion_packet = self._process_incoming_onion_packet(
×
2363
                            processed_onion_packet.trampoline_onion_packet,
2364
                            payment_hash=payment_hash,
2365
                            is_trampoline=True,
2366
                        )
2367
                        amount_to_forward = processed_trampoline_onion_packet.amt_to_forward
×
2368
                    error_data = amount_to_forward.to_bytes(8, byteorder="big")
1✔
2369
                e = OnionRoutingFailure(code=error_code, data=error_data or b'')
1✔
2370
                error_bytes = e.to_wire_msg(onion_packet, self.privkey, local_height)
1✔
2371
            self.fail_htlc(
1✔
2372
                chan=chan,
2373
                htlc_id=htlc_id,
2374
                error_bytes=error_bytes,
2375
            )
2376
            htlc_set = htlc_set._replace(htlcs=htlc_set.htlcs - {mpp_htlc})
1✔
2377

2378
        self.lnworker.received_mpp_htlcs[payment_key] = htlc_set  # save updated set
1✔
2379

2380
    def fail_htlc(self, *, chan: Channel, htlc_id: int, error_bytes: bytes):
1✔
2381
        self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.")
1✔
2382
        assert chan.can_update_ctx(proposer=LOCAL), f"cannot send updates: {chan.short_channel_id}"
1✔
2383
        self.received_htlcs_pending_removal.add((chan, htlc_id))
1✔
2384
        chan.fail_htlc(htlc_id)
1✔
2385
        self.send_message(
1✔
2386
            "update_fail_htlc",
2387
            channel_id=chan.channel_id,
2388
            id=htlc_id,
2389
            len=len(error_bytes),
2390
            reason=error_bytes)
2391
        self.maybe_send_commitment(chan)
1✔
2392

2393
    def fail_malformed_htlc(self, *, chan: Channel, htlc_id: int, reason: OnionParsingError):
1✔
2394
        self.logger.info(f"fail_malformed_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}.")
1✔
2395
        assert chan.can_update_ctx(proposer=LOCAL), f"cannot send updates: {chan.short_channel_id}"
1✔
2396
        if not (reason.code & OnionFailureCodeMetaFlag.BADONION and len(reason.data) == 32):
1✔
2397
            raise Exception(f"unexpected reason when sending 'update_fail_malformed_htlc': {reason!r}")
×
2398
        self.received_htlcs_pending_removal.add((chan, htlc_id))
1✔
2399
        chan.fail_htlc(htlc_id)
1✔
2400
        self.send_message(
1✔
2401
            "update_fail_malformed_htlc",
2402
            channel_id=chan.channel_id,
2403
            id=htlc_id,
2404
            sha256_of_onion=reason.data,
2405
            failure_code=reason.code)
2406
        self.maybe_send_commitment(chan)
1✔
2407

2408
    def on_revoke_and_ack(self, chan: Channel, payload) -> None:
1✔
2409
        self.logger.info(f'on_revoke_and_ack. chan {chan.short_channel_id}. ctn: {chan.get_oldest_unrevoked_ctn(REMOTE)}')
1✔
2410
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2411
            self.logger.warning(
×
2412
                f"on_revoke_and_ack. dropping message. illegal action. "
2413
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2414
            return
×
2415
        rev = RevokeAndAck(payload["per_commitment_secret"], payload["next_per_commitment_point"])
1✔
2416
        chan.receive_revocation(rev)
1✔
2417
        self.lnworker.save_channel(chan)
1✔
2418
        self._received_revack_event.set()
1✔
2419
        self._received_revack_event.clear()
1✔
2420

2421
    @event_listener
1✔
2422
    async def on_event_fee(self, *args):
1✔
2423
        async def async_wrapper():
×
2424
            for chan in self.channels.values():
×
2425
                self.maybe_update_fee(chan)
×
2426
        await self.taskgroup.spawn(async_wrapper)
×
2427

2428
    def on_update_fee(self, chan: Channel, payload):
1✔
2429
        if not chan.can_update_ctx(proposer=REMOTE):
1✔
2430
            self.logger.warning(
×
2431
                f"on_update_fee. dropping message. illegal action. "
2432
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2433
            return
×
2434
        feerate = payload["feerate_per_kw"]
1✔
2435
        chan.update_fee(feerate, False)
1✔
2436

2437
    def maybe_update_fee(self, chan: Channel):
1✔
2438
        """
2439
        called when our fee estimates change
2440
        """
2441
        if not chan.can_update_ctx(proposer=LOCAL):
1✔
2442
            return
×
2443
        if chan.get_state() != ChannelState.OPEN:
1✔
2444
            return
×
2445
        current_feerate_per_kw: Optional[int] = self.lnworker.current_target_feerate_per_kw(
1✔
2446
            has_anchors=chan.has_anchors()
2447
        )
2448
        if current_feerate_per_kw is None:
1✔
2449
            return
×
2450
        # add some buffer to anchor chan fees as we always act at the lower end and don't
2451
        # want to get kicked out of the mempool immediately if it grows
2452
        fee_buffer = current_feerate_per_kw * 0.5 if chan.has_anchors() else 0
1✔
2453
        update_feerate_per_kw = int(current_feerate_per_kw + fee_buffer)
1✔
2454
        def does_chan_fee_need_update(chan_feerate: Union[float, int]) -> Optional[bool]:
1✔
2455
            if chan.has_anchors():
1✔
2456
                # TODO: once package relay and electrum servers with submitpackage are more common,
2457
                # TODO: we should reconsider this logic and move towards 0 fee ctx
2458
                # update if we used up half of the buffer or the fee decreased a lot again
2459
                fee_increased = current_feerate_per_kw + (fee_buffer / 2) > chan_feerate
1✔
2460
                changed_significantly = abs((chan_feerate - update_feerate_per_kw) / chan_feerate) > 0.2
1✔
2461
                return fee_increased or changed_significantly
1✔
2462
            else:
2463
                # We raise fees more aggressively than we lower them. Overpaying is not too bad,
2464
                # but lowballing can be fatal if we can't even get into the mempool...
2465
                high_fee = 2 * current_feerate_per_kw  # type: # Union[float, int]
1✔
2466
                low_fee = self.lnworker.current_low_feerate_per_kw_srk_channel()  # type: Optional[Union[float, int]]
1✔
2467
                if low_fee is None:
1✔
2468
                    return None
×
2469
                low_fee = max(low_fee, 0.75 * current_feerate_per_kw)
1✔
2470
                # make sure low_feerate and target_feerate are not too close to each other:
2471
                low_fee = min(low_fee, current_feerate_per_kw - FEERATE_PER_KW_MIN_RELAY_LIGHTNING)
1✔
2472
                assert low_fee < high_fee, (low_fee, high_fee)
1✔
2473
                return not (low_fee < chan_feerate < high_fee)
1✔
2474
        if not chan.constraints.is_initiator:
1✔
2475
            if constants.net is not constants.BitcoinRegtest:
1✔
2476
                chan_feerate = chan.get_latest_feerate(LOCAL)
1✔
2477
                ratio = chan_feerate / update_feerate_per_kw
1✔
2478
                if ratio < 0.5:
1✔
2479
                    # Note that we trust the Electrum server about fee rates
2480
                    # Thus, automated force-closing might not be a good idea
2481
                    # Maybe we should display something in the GUI instead
2482
                    self.logger.warning(
×
2483
                        f"({chan.get_id_for_log()}) feerate is {chan_feerate} sat/kw, "
2484
                        f"current recommended feerate is {update_feerate_per_kw} sat/kw, consider force closing!")
2485
            return
1✔
2486
        # it is our responsibility to update the fee
2487
        chan_fee = chan.get_next_feerate(REMOTE)
1✔
2488
        if does_chan_fee_need_update(chan_fee):
1✔
2489
            self.logger.info(f"({chan.get_id_for_log()}) onchain fees have changed considerably. updating fee.")
1✔
2490
        elif chan.get_latest_ctn(REMOTE) == 0:
1✔
2491
            # workaround eclair issue https://github.com/ACINQ/eclair/issues/1730 (fixed in 2022)
2492
            self.logger.info(f"({chan.get_id_for_log()}) updating fee to bump remote ctn")
1✔
2493
            if current_feerate_per_kw == chan_fee:
1✔
2494
                update_feerate_per_kw += 1
×
2495
        else:
2496
            return
1✔
2497
        self.logger.info(f"({chan.get_id_for_log()}) current pending feerate {chan_fee}. "
1✔
2498
                         f"new feerate {update_feerate_per_kw}")
2499
        assert update_feerate_per_kw >= FEERATE_PER_KW_MIN_RELAY_LIGHTNING, f"fee below minimum: {update_feerate_per_kw}"
1✔
2500
        chan.update_fee(update_feerate_per_kw, True)
1✔
2501
        self.send_message(
1✔
2502
            "update_fee",
2503
            channel_id=chan.channel_id,
2504
            feerate_per_kw=update_feerate_per_kw)
2505
        self.maybe_send_commitment(chan)
1✔
2506

2507
    @log_exceptions
1✔
2508
    async def close_channel(self, chan_id: bytes):
1✔
2509
        chan = self.get_channel_by_id(chan_id)
1✔
2510
        assert chan
1✔
2511
        self.shutdown_received[chan_id] = self.asyncio_loop.create_future()
1✔
2512
        await self.send_shutdown(chan)
1✔
2513
        payload = await self.shutdown_received[chan_id]
1✔
2514
        try:
1✔
2515
            txid = await self._shutdown(chan, payload, is_local=True)
1✔
2516
            self.logger.info(f'({chan.get_id_for_log()}) Channel closed {txid}')
1✔
2517
        except asyncio.TimeoutError:
1✔
2518
            txid = chan.unconfirmed_closing_txid
×
2519
            self.logger.warning(f'({chan.get_id_for_log()}) did not send closing_signed, {txid}')
×
2520
            if txid is None:
×
2521
                raise Exception('The remote peer did not send their final signature. The channel may not have been be closed')
×
2522
        return txid
1✔
2523

2524
    @non_blocking_msg_handler
1✔
2525
    async def on_shutdown(self, chan: Channel, payload):
1✔
2526
        if chan.peer_state != PeerState.GOOD:  # should never happen
1✔
2527
            raise Exception(f"received shutdown in unexpected {chan.peer_state=!r}")
×
2528
        if not self.can_send_shutdown(chan, proposer=REMOTE):
1✔
2529
            self.logger.warning(
×
2530
                f"on_shutdown. illegal action. "
2531
                f"chan={chan.get_id_for_log()}. {chan.get_state()=!r}. {chan.peer_state=!r}")
2532
            self.send_error(chan.channel_id, message="cannot process 'shutdown' in current channel state.")
×
2533
        their_scriptpubkey = payload['scriptpubkey']
1✔
2534
        their_upfront_scriptpubkey = chan.config[REMOTE].upfront_shutdown_script
1✔
2535
        # BOLT-02 check if they use the upfront shutdown script they advertised
2536
        if self.is_upfront_shutdown_script() and their_upfront_scriptpubkey:
1✔
2537
            if not (their_scriptpubkey == their_upfront_scriptpubkey):
1✔
2538
                self.send_warning(
1✔
2539
                    chan.channel_id,
2540
                    "remote didn't use upfront shutdown script it committed to in channel opening",
2541
                    close_connection=True)
2542
        else:
2543
            # BOLT-02 restrict the scriptpubkey to some templates:
2544
            if self.is_shutdown_anysegwit() and match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT):
1✔
2545
                pass
×
2546
            elif match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0):
1✔
2547
                pass
1✔
2548
            else:
2549
                self.send_warning(
×
2550
                    chan.channel_id,
2551
                    f'scriptpubkey in received shutdown message does not conform to any template: {their_scriptpubkey.hex()}',
2552
                    close_connection=True)
2553

2554
        chan_id = chan.channel_id
1✔
2555
        if chan_id in self.shutdown_received:
1✔
2556
            self.shutdown_received[chan_id].set_result(payload)
1✔
2557
        else:
2558
            await self.send_shutdown(chan)
1✔
2559
            txid = await self._shutdown(chan, payload, is_local=False)
1✔
2560
            self.logger.info(f'({chan.get_id_for_log()}) Channel closed by remote peer {txid}')
1✔
2561

2562
    def can_send_shutdown(self, chan: Channel, *, proposer: HTLCOwner) -> bool:
1✔
2563
        if chan.get_state() >= ChannelState.CLOSED:
1✔
2564
            return False
×
2565
        if chan.get_state() >= ChannelState.OPENING:
1✔
2566
            return True
1✔
2567
        if proposer == LOCAL:
×
2568
            if chan.constraints.is_initiator and chan.channel_id in self.funding_created_sent:
×
2569
                return True
×
2570
            if not chan.constraints.is_initiator and chan.channel_id in self.funding_signed_sent:
×
2571
                return True
×
2572
        else:  # proposer == REMOTE
2573
            # (from BOLT-02)
2574
            #   A receiving node:
2575
            #       - if it hasn't received a funding_signed (if it is a funder) or a funding_created (if it is a fundee):
2576
            #           - SHOULD send an error and fail the channel.
2577
            # ^ that check is equivalent to `chan.get_state() < ChannelState.OPENING`, which is already checked.
2578
            pass
×
2579
        return False
×
2580

2581
    async def send_shutdown(self, chan: Channel):
1✔
2582
        if not self.can_send_shutdown(chan, proposer=LOCAL):
1✔
2583
            raise Exception(f"cannot send shutdown. chan={chan.get_id_for_log()}. {chan.get_state()=!r}")
×
2584
        if chan.config[LOCAL].upfront_shutdown_script:
1✔
2585
            scriptpubkey = chan.config[LOCAL].upfront_shutdown_script
1✔
2586
        else:
2587
            scriptpubkey = bitcoin.address_to_script(chan.get_sweep_address())
1✔
2588
        assert scriptpubkey
1✔
2589
        # wait until no more pending updates (bolt2)
2590
        chan.set_can_send_ctx_updates(False)
1✔
2591
        while chan.has_pending_changes(REMOTE):
1✔
2592
            await asyncio.sleep(0.1)
×
2593
        self.send_message('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey)
1✔
2594
        chan.set_state(ChannelState.SHUTDOWN)
1✔
2595
        # can fulfill or fail htlcs. cannot add htlcs, because state != OPEN
2596
        chan.set_can_send_ctx_updates(True)
1✔
2597

2598
    def get_shutdown_fee_range(self, chan, closing_tx, is_local):
1✔
2599
        """ return the closing fee and fee range we initially try to enforce """
2600
        config = self.config
1✔
2601
        our_fee = None
1✔
2602
        if config.TEST_SHUTDOWN_FEE:
1✔
2603
            our_fee = config.TEST_SHUTDOWN_FEE
1✔
2604
        else:
2605
            fee_rate_per_kb = self.network.fee_estimates.eta_target_to_fee(FEE_LN_ETA_TARGET)
1✔
2606
            if fee_rate_per_kb is None:  # fallback
1✔
2607
                from .fee_policy import FeePolicy
×
2608
                fee_rate_per_kb = FeePolicy(config.FEE_POLICY).fee_per_kb(self.network)
×
2609
            if fee_rate_per_kb is not None:
1✔
2610
                our_fee = fee_rate_per_kb * closing_tx.estimated_size() // 1000
1✔
2611
            # TODO: anchors: remove this, as commitment fee rate can be below chain head fee rate?
2612
            # BOLT2: The sending node MUST set fee less than or equal to the base fee of the final ctx
2613
            max_fee = chan.get_latest_fee(LOCAL if is_local else REMOTE)
1✔
2614
            if our_fee is None:  # fallback
1✔
2615
                self.logger.warning(f"got no fee estimates for co-op close! falling back to chan.get_latest_fee")
×
2616
                our_fee = max_fee
×
2617
            our_fee = min(our_fee, max_fee)
1✔
2618
        # config modern_fee_negotiation can be set in tests
2619
        if config.TEST_SHUTDOWN_LEGACY:
1✔
2620
            our_fee_range = None
1✔
2621
        elif config.TEST_SHUTDOWN_FEE_RANGE:
1✔
2622
            our_fee_range = config.TEST_SHUTDOWN_FEE_RANGE
1✔
2623
        else:
2624
            # we aim at a fee between next block inclusion and some lower value
2625
            our_fee_range = {'min_fee_satoshis': our_fee // 2, 'max_fee_satoshis': our_fee * 2}
1✔
2626
        self.logger.info(f"Our fee range: {our_fee_range} and fee: {our_fee}")
1✔
2627
        return our_fee, our_fee_range
1✔
2628

2629
    @log_exceptions
1✔
2630
    async def _shutdown(self, chan: Channel, payload, *, is_local: bool):
1✔
2631
        # wait until no HTLCs remain in either commitment transaction
2632
        while chan.has_unsettled_htlcs():
1✔
2633
            self.logger.info(f'(chan: {chan.short_channel_id}) waiting for htlcs to settle...')
1✔
2634
            await asyncio.sleep(1)
1✔
2635
        # if no HTLCs remain, we must not send updates
2636
        chan.set_can_send_ctx_updates(False)
1✔
2637
        their_scriptpubkey = payload['scriptpubkey']
1✔
2638
        if chan.config[LOCAL].upfront_shutdown_script:
1✔
2639
            our_scriptpubkey = chan.config[LOCAL].upfront_shutdown_script
1✔
2640
        else:
2641
            our_scriptpubkey = bitcoin.address_to_script(chan.get_sweep_address())
1✔
2642
        assert our_scriptpubkey
1✔
2643
        # estimate fee of closing tx
2644
        dummy_sig, dummy_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0)
1✔
2645
        our_sig = None  # type: Optional[bytes]
1✔
2646
        closing_tx = None  # type: Optional[PartialTransaction]
1✔
2647
        is_initiator = chan.constraints.is_initiator
1✔
2648
        our_fee, our_fee_range = self.get_shutdown_fee_range(chan, dummy_tx, is_local)
1✔
2649

2650
        def send_closing_signed(our_fee, our_fee_range, drop_remote):
1✔
2651
            nonlocal our_sig, closing_tx
2652
            if our_fee_range:
1✔
2653
                closing_signed_tlvs = {'fee_range': our_fee_range}
1✔
2654
            else:
2655
                closing_signed_tlvs = {}
1✔
2656
            our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=our_fee, drop_remote=drop_remote)
1✔
2657
            self.logger.info(f"Sending fee range: {closing_signed_tlvs} and fee: {our_fee}")
1✔
2658
            self.send_message(
1✔
2659
                'closing_signed',
2660
                channel_id=chan.channel_id,
2661
                fee_satoshis=our_fee,
2662
                signature=our_sig,
2663
                closing_signed_tlvs=closing_signed_tlvs,
2664
            )
2665

2666
        def verify_signature(tx: 'PartialTransaction', sig) -> bool:
1✔
2667
            their_pubkey = chan.config[REMOTE].multisig_key.pubkey
1✔
2668
            pre_hash = tx.serialize_preimage(0)
1✔
2669
            msg_hash = sha256d(pre_hash)
1✔
2670
            return ECPubkey(their_pubkey).ecdsa_verify(sig, msg_hash)
1✔
2671

2672
        async def receive_closing_signed():
1✔
2673
            nonlocal our_sig, closing_tx
2674
            try:
1✔
2675
                cs_payload = await self.wait_for_message('closing_signed', chan.channel_id)
1✔
2676
            except asyncio.exceptions.TimeoutError:
1✔
2677
                self.schedule_force_closing(chan.channel_id)
1✔
2678
                raise Exception("closing_signed not received, force closing.")
1✔
2679
            their_fee = cs_payload['fee_satoshis']
1✔
2680
            their_fee_range = cs_payload['closing_signed_tlvs'].get('fee_range')
1✔
2681
            their_sig = cs_payload['signature']
1✔
2682
            # perform checks
2683
            our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=False)
1✔
2684
            if verify_signature(closing_tx, their_sig):
1✔
2685
                drop_remote = False
1✔
2686
            else:
2687
                our_sig, closing_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=their_fee, drop_remote=True)
×
2688
                if verify_signature(closing_tx, their_sig):
×
2689
                    drop_remote = True
×
2690
                else:
2691
                    # this can happen if we consider our output too valuable to drop,
2692
                    # but the remote drops it because it violates their dust limit
2693
                    raise Exception('failed to verify their signature')
×
2694
            # at this point we know how the closing tx looks like
2695
            # check that their output is above their scriptpubkey's network dust limit
2696
            to_remote_set = closing_tx.get_output_idxs_from_scriptpubkey(their_scriptpubkey)
1✔
2697
            if not drop_remote and to_remote_set:
1✔
2698
                to_remote_idx = to_remote_set.pop()
1✔
2699
                to_remote_amount = closing_tx.outputs()[to_remote_idx].value
1✔
2700
                transaction.check_scriptpubkey_template_and_dust(their_scriptpubkey, to_remote_amount)
1✔
2701
            return their_fee, their_fee_range, their_sig, drop_remote
1✔
2702

2703
        def choose_new_fee(our_fee, our_fee_range, their_fee, their_fee_range, their_previous_fee):
1✔
2704
            assert our_fee != their_fee
1✔
2705
            fee_range_sent = our_fee_range and (is_initiator or (their_previous_fee is not None))
1✔
2706

2707
            # The sending node, if it is not the funder:
2708
            if our_fee_range and their_fee_range and not is_initiator and not self.config.TEST_SHUTDOWN_FEE_RANGE:
1✔
2709
                # SHOULD set max_fee_satoshis to at least the max_fee_satoshis received
2710
                our_fee_range['max_fee_satoshis'] = max(their_fee_range['max_fee_satoshis'], our_fee_range['max_fee_satoshis'])
×
2711
                # SHOULD set min_fee_satoshis to a fairly low value
2712
                our_fee_range['min_fee_satoshis'] = min(their_fee_range['min_fee_satoshis'], our_fee_range['min_fee_satoshis'])
×
2713
                # Note: the BOLT describes what the sending node SHOULD do.
2714
                # However, this assumes that we have decided to send 'funding_signed' in response to their fee_range.
2715
                # In practice, we might prefer to fail the channel in some cases (TODO)
2716

2717
            # the receiving node, if fee_satoshis matches its previously sent fee_range,
2718
            if fee_range_sent and (our_fee_range['min_fee_satoshis'] <= their_fee <= our_fee_range['max_fee_satoshis']):
1✔
2719
                # SHOULD reply with a closing_signed with the same fee_satoshis value if it is different from its previously sent fee_satoshis
2720
                our_fee = their_fee
1✔
2721

2722
            # the receiving node, if the message contains a fee_range
2723
            elif our_fee_range and their_fee_range:
1✔
2724
                overlap_min = max(our_fee_range['min_fee_satoshis'], their_fee_range['min_fee_satoshis'])
1✔
2725
                overlap_max = min(our_fee_range['max_fee_satoshis'], their_fee_range['max_fee_satoshis'])
1✔
2726
                # if there is no overlap between that and its own fee_range
2727
                if overlap_min > overlap_max:
1✔
2728
                    # TODO: the receiving node should first send a warning, and fail the channel
2729
                    # only if it doesn't receive a satisfying fee_range after a reasonable amount of time
2730
                    self.schedule_force_closing(chan.channel_id)
1✔
2731
                    raise Exception("There is no overlap between between their and our fee range.")
1✔
2732
                # otherwise, if it is the funder
2733
                if is_initiator:
1✔
2734
                    # if fee_satoshis is not in the overlap between the sent and received fee_range:
2735
                    if not (overlap_min <= their_fee <= overlap_max):
×
2736
                        # MUST fail the channel
2737
                        self.schedule_force_closing(chan.channel_id)
×
2738
                        raise Exception("Their fee is not in the overlap region, we force closed.")
×
2739
                    # otherwise, MUST reply with the same fee_satoshis.
2740
                    our_fee = their_fee
×
2741
                # otherwise (it is not the funder):
2742
                else:
2743
                    # if it has already sent a closing_signed:
2744
                    if fee_range_sent:
1✔
2745
                        # fee_satoshis is not the same as the value we sent, we MUST fail the channel
2746
                        self.schedule_force_closing(chan.channel_id)
×
2747
                        raise Exception("Expected the same fee as ours, we force closed.")
×
2748
                    # otherwise:
2749
                    # MUST propose a fee_satoshis in the overlap between received and (about-to-be) sent fee_range.
2750
                    our_fee = (overlap_min + overlap_max) // 2
1✔
2751
            else:
2752
                # otherwise, if fee_satoshis is not strictly between its last-sent fee_satoshis
2753
                # and its previously-received fee_satoshis, UNLESS it has since reconnected:
2754
                if their_previous_fee and not (min(our_fee, their_previous_fee) < their_fee < max(our_fee, their_previous_fee)):
1✔
2755
                    # SHOULD fail the connection.
2756
                    raise Exception('Their fee is not between our last sent and their last sent fee.')
×
2757
                # accept their fee if they are very close
2758
                if abs(their_fee - our_fee) < 2:
1✔
2759
                    our_fee = their_fee
1✔
2760
                else:
2761
                    # this will be "strictly between" (as in BOLT2) previous values because of the above
2762
                    our_fee = (our_fee + their_fee) // 2
1✔
2763

2764
            return our_fee, our_fee_range
1✔
2765

2766
        # Fee negotiation: both parties exchange 'funding_signed' messages.
2767
        # The funder sends the first message, the non-funder sends the last message.
2768
        # In the 'modern' case, at most 3 messages are exchanged, because choose_new_fee of the funder either returns their_fee or fails
2769
        their_fee = None
1✔
2770
        drop_remote = False  # does the peer drop its to_local output or not?
1✔
2771
        if is_initiator:
1✔
2772
            send_closing_signed(our_fee, our_fee_range, drop_remote)
1✔
2773
        while True:
1✔
2774
            their_previous_fee = their_fee
1✔
2775
            their_fee, their_fee_range, their_sig, drop_remote = await receive_closing_signed()
1✔
2776
            if our_fee == their_fee:
1✔
2777
                break
1✔
2778
            our_fee, our_fee_range = choose_new_fee(our_fee, our_fee_range, their_fee, their_fee_range, their_previous_fee)
1✔
2779
            if not is_initiator and our_fee == their_fee:
1✔
2780
                break
×
2781
            send_closing_signed(our_fee, our_fee_range, drop_remote)
1✔
2782
            if is_initiator and our_fee == their_fee:
1✔
2783
                break
1✔
2784
        if not is_initiator:
1✔
2785
            send_closing_signed(our_fee, our_fee_range, drop_remote)
1✔
2786

2787
        # add signatures
2788
        closing_tx.add_signature_to_txin(
1✔
2789
            txin_idx=0,
2790
            signing_pubkey=chan.config[LOCAL].multisig_key.pubkey,
2791
            sig=ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL))
2792
        closing_tx.add_signature_to_txin(
1✔
2793
            txin_idx=0,
2794
            signing_pubkey=chan.config[REMOTE].multisig_key.pubkey,
2795
            sig=ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL))
2796
        # save local transaction and set state
2797
        try:
1✔
2798
            self.lnworker.wallet.adb.add_transaction(closing_tx)
1✔
2799
        except UnrelatedTransactionException:
1✔
2800
            pass  # this can happen if (~all the balance goes to REMOTE)
1✔
2801
        chan.set_state(ChannelState.CLOSING)
1✔
2802
        # broadcast
2803
        await self.network.try_broadcasting(closing_tx, 'closing')
1✔
2804
        return closing_tx.txid()
1✔
2805

2806
    async def htlc_switch(self):
1✔
2807
        await self.initialized
1✔
2808
        # don't context switch in a htlc switch iteration as htlc sets are shared between peers
2809
        assert not inspect.iscoroutinefunction(self._run_htlc_switch_iteration)
1✔
2810
        while True:
1✔
2811
            await self.ping_if_required()
1✔
2812
            self._htlc_switch_iterdone_event.set()
1✔
2813
            self._htlc_switch_iterdone_event.clear()
1✔
2814
            # We poll every 0.1 sec to check if there is work to do,
2815
            # or we can also be triggered via events.
2816
            # When forwarding an HTLC originating from this peer (the upstream),
2817
            # we can get triggered for events that happen on the downstream peer.
2818
            # TODO: trampoline forwarding relies on the polling
2819
            async with ignore_after(0.1):
1✔
2820
                async with OldTaskGroup(wait=any) as group:
1✔
2821
                    await group.spawn(self._received_revack_event.wait())
1✔
2822
                    await group.spawn(self.downstream_htlc_resolved_event.wait())
1✔
2823
            self._htlc_switch_iterstart_event.set()
1✔
2824
            self._htlc_switch_iterstart_event.clear()
1✔
2825
            try:
1✔
2826
                self._run_htlc_switch_iteration()
1✔
2827
            except Exception as e:
×
2828
                # this is code with many asserts and dense logic so it seems useful to allow the user
2829
                # report to exceptions that otherwise might go unnoticed for some time
2830
                reported_exc = type(e)("redacted")  # text could contain onions, payment hashes etc.
×
2831
                reported_exc.__traceback__ = e.__traceback__
×
2832
                util.send_exception_to_crash_reporter(reported_exc)
×
2833
                raise e
×
2834

2835
    @util.profiler(min_threshold=0.02)
1✔
2836
    def _run_htlc_switch_iteration(self):
1✔
2837
        self._maybe_cleanup_received_htlcs_pending_removal()
1✔
2838
        # htlc processing happens in two steps:
2839
        # 1. Step: Iterating through all channels and their pending htlcs, doing validation
2840
        #    feasible for single htlcs (some checks only make sense on the whole mpp set) and
2841
        #    then collecting these htlcs in a mpp set by payment key.
2842
        #    HTLCs failing these checks will get failed directly and won't be added to any set.
2843
        #    No htlcs will get settled in this step, settling only happens on complete mpp sets.
2844
        #    If a new htlc belongs to a set which has already been failed, the htlc will be failed
2845
        #    and not added to any set.
2846
        #    Each htlc is only supposed to go through this first loop once when being received.
2847
        for chan_id, chan in self.channels.items():
1✔
2848
            if not chan.can_update_ctx(proposer=LOCAL):
1✔
2849
                continue
1✔
2850
            self.maybe_send_commitment(chan)
1✔
2851
            unfulfilled = chan.unfulfilled_htlcs
1✔
2852
            for htlc_id, onion_packet_hex in list(unfulfilled.items()):
1✔
2853
                if not chan.hm.is_htlc_irrevocably_added_yet(htlc_proposer=REMOTE, htlc_id=htlc_id):
1✔
2854
                    continue
1✔
2855

2856
                htlc = chan.hm.get_htlc_by_id(REMOTE, htlc_id)
1✔
2857
                try:
1✔
2858
                    onion_packet = self._parse_onion_packet(onion_packet_hex)
1✔
2859
                except OnionParsingError as e:
×
2860
                    self.fail_malformed_htlc(
×
2861
                        chan=chan,
2862
                        htlc_id=htlc.htlc_id,
2863
                        reason=e,
2864
                    )
2865
                    del unfulfilled[htlc_id]
×
2866
                    continue
×
2867

2868
                try:
1✔
2869
                    processed_onion_packet = self._process_incoming_onion_packet(
1✔
2870
                        onion_packet,
2871
                        payment_hash=htlc.payment_hash,
2872
                        is_trampoline=False,
2873
                    )
2874
                    payment_key: str = self._check_unfulfilled_htlc(
1✔
2875
                        chan=chan,
2876
                        htlc=htlc,
2877
                        processed_onion=processed_onion_packet,
2878
                    )
2879
                    self.lnworker.update_or_create_mpp_with_received_htlc(
1✔
2880
                        payment_key=payment_key,
2881
                        channel_id=chan.channel_id,
2882
                        htlc=htlc,
2883
                        unprocessed_onion_packet=onion_packet_hex,  # outer onion if trampoline
2884
                    )
2885
                except OnionParsingError as e:  # could be raised when parsing the inner trampoline onion
1✔
2886
                    self.fail_malformed_htlc(
1✔
2887
                        chan=chan,
2888
                        htlc_id=htlc.htlc_id,
2889
                        reason=e,
2890
                    )
2891
                except Exception as e:
1✔
2892
                    # Fail the htlc directly if it fails to pass these tests, it will not get added to a htlc set.
2893
                    # https://github.com/lightning/bolts/blob/14272b1bd9361750cfdb3e5d35740889a6b510b5/04-onion-routing.md?plain=1#L388
2894
                    reraise = False
1✔
2895
                    if isinstance(e, OnionRoutingFailure):
1✔
2896
                        orf = e
1✔
2897
                    else:
2898
                        orf = OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
×
2899
                        reraise = True  # propagate this out, as this might suggest a bug
×
2900
                    error_bytes = orf.to_wire_msg(onion_packet, self.privkey, self.network.get_local_height())
1✔
2901
                    self.fail_htlc(
1✔
2902
                        chan=chan,
2903
                        htlc_id=htlc.htlc_id,
2904
                        error_bytes=error_bytes,
2905
                    )
2906
                    if reraise:
1✔
2907
                        raise
×
2908
                finally:
2909
                    del unfulfilled[htlc_id]
1✔
2910

2911
        # 2. Step: Acting on sets of htlcs.
2912
        #    Doing further checks that have to be done on sets of htlcs (e.g. total amount checks)
2913
        #    and checks that have to be done continuously like checking for timeout.
2914
        #    A set marked as failed once must never settle any htlcs associated to it.
2915
        #    The sets are shared between all peers, so each peers htlc_switch acts on the same sets.
2916
        for payment_key, htlc_set in list(self.lnworker.received_mpp_htlcs.items()):
1✔
2917
            any_error, preimage, callback = self._check_unfulfilled_htlc_set(payment_key, htlc_set)
1✔
2918
            assert bool(any_error) + bool(preimage) + bool(callback) <= 1, \
1✔
2919
                        f"{any_error=}, {bool(preimage)=}, {callback=}"
2920
            if any_error:
1✔
2921
                error_tuple = self.lnworker.set_htlc_set_error(payment_key, any_error)
1✔
2922
                self._fail_htlc_set(payment_key, error_tuple)
1✔
2923
            if preimage:
1✔
2924
                if self.lnworker.enable_htlc_settle:
1✔
2925
                    self.lnworker.set_request_status(htlc_set.get_payment_hash(), PR_PAID)
1✔
2926
                    self._fulfill_htlc_set(payment_key, preimage)
1✔
2927
            if callback:
1✔
2928
                task = asyncio.create_task(callback())
1✔
2929
                task.add_done_callback(  # handle exceptions occurring in callback
1✔
2930
                    lambda t: (util.send_exception_to_crash_reporter(t.exception()) if t.exception() else None)
2931
                )
2932

2933
            if len(self.lnworker.received_mpp_htlcs[payment_key].htlcs) == 0:
1✔
2934
                self.logger.debug(f"deleting resolved mpp set: {payment_key=}")
1✔
2935
                del self.lnworker.received_mpp_htlcs[payment_key]
1✔
2936
                self.lnworker.maybe_cleanup_forwarding(payment_key)
1✔
2937

2938
    def _maybe_cleanup_received_htlcs_pending_removal(self) -> None:
1✔
2939
        done = set()
1✔
2940
        for chan, htlc_id in self.received_htlcs_pending_removal:
1✔
2941
            if chan.hm.is_htlc_irrevocably_removed_yet(htlc_proposer=REMOTE, htlc_id=htlc_id):
1✔
2942
                done.add((chan, htlc_id))
1✔
2943
        if done:
1✔
2944
            for key in done:
1✔
2945
                self.received_htlcs_pending_removal.remove(key)
1✔
2946
            self.received_htlc_removed_event.set()
1✔
2947
            self.received_htlc_removed_event.clear()
1✔
2948

2949
    async def wait_one_htlc_switch_iteration(self) -> None:
1✔
2950
        """Waits until the HTLC switch does a full iteration or the peer disconnects,
2951
        whichever happens first.
2952
        """
2953
        async def htlc_switch_iteration():
1✔
2954
            await self._htlc_switch_iterstart_event.wait()
1✔
2955
            await self._htlc_switch_iterdone_event.wait()
1✔
2956

2957
        async with OldTaskGroup(wait=any) as group:
1✔
2958
            await group.spawn(htlc_switch_iteration())
1✔
2959
            await group.spawn(self.got_disconnected.wait())
1✔
2960

2961
    def _log_htlc_fail_reason_cb(
1✔
2962
        self,
2963
        channel_id: bytes,
2964
        htlc: UpdateAddHtlc,
2965
        onion_payload: dict
2966
    ) -> Callable[[str], None]:
2967
        def _log_fail_reason(reason: str) -> None:
1✔
2968
            scid = self.lnworker.get_channel_by_id(channel_id).short_channel_id
1✔
2969
            self.logger.info(f"will FAIL HTLC: {str(scid)=}. {reason=}. {str(htlc)=}. {onion_payload=}")
1✔
2970
        return _log_fail_reason
1✔
2971

2972
    def _log_htlc_set_fail_reason_cb(self, mpp_set: ReceivedMPPStatus) -> Callable[[str], None]:
1✔
2973
        def log_fail_reason(reason: str):
1✔
2974
            for mpp_htlc in mpp_set.htlcs:
1✔
2975
                try:
1✔
2976
                    processed_onion = self._process_incoming_onion_packet(
1✔
2977
                        onion_packet=self._parse_onion_packet(mpp_htlc.unprocessed_onion),
2978
                        payment_hash=mpp_htlc.htlc.payment_hash,
2979
                        is_trampoline=False,
2980
                    )
2981
                    onion_payload = processed_onion.hop_data.payload
1✔
2982
                except Exception:
×
2983
                    onion_payload = {}
×
2984

2985
                self._log_htlc_fail_reason_cb(
1✔
2986
                    mpp_htlc.channel_id,
2987
                    mpp_htlc.htlc,
2988
                    onion_payload,
2989
                )(f"mpp set {id(mpp_set)} failed: {reason}")
2990

2991
        return log_fail_reason
1✔
2992

2993
    def _check_unfulfilled_htlc_set(
1✔
2994
        self,
2995
        payment_key: str,
2996
        mpp_set: ReceivedMPPStatus
2997
    ) -> Tuple[
2998
        Optional[Union[OnionRoutingFailure, OnionFailureCode, bytes]],  # error types used to fail the set
2999
        Optional[bytes],  # preimage to settle the set
3000
        Optional[Callable[[], Coroutine[Any, Any, None]]],  # callback
3001
    ]:
3002
        """
3003
        Returns what to do next with the given set of htlcs:
3004
            * Fail whole set -> returns error code
3005
            * Settle whole set -> Returns preimage
3006
            * call callback (e.g. forwarding, hold invoice)
3007
        May modify the mpp set in lnworker.received_mpp_htlcs (e.g. by setting its resolution to COMPLETE).
3008
        """
3009
        _log_fail_reason = self._log_htlc_set_fail_reason_cb(mpp_set)
1✔
3010

3011
        if (final_state := self._check_final_mpp_set_state(payment_key, mpp_set)) is not None:
1✔
3012
            return final_state
1✔
3013

3014
        assert mpp_set.resolution in (RecvMPPResolution.WAITING, RecvMPPResolution.COMPLETE)
1✔
3015
        chain = self.network.blockchain()
1✔
3016
        local_height = chain.height()
1✔
3017
        if chain.is_tip_stale():
1✔
3018
            _log_fail_reason(f"our chain tip is stale: {local_height=}")
×
3019
            return OnionFailureCode.TEMPORARY_NODE_FAILURE, None, None
×
3020

3021
        amount_msat: int = 0  # sum(amount_msat of each htlc)
1✔
3022
        total_msat = None  # type: Optional[int]
1✔
3023
        payment_hash = mpp_set.get_payment_hash()
1✔
3024
        closest_cltv_abs = mpp_set.get_closest_cltv_abs()
1✔
3025
        first_htlc_timestamp = mpp_set.get_first_htlc_timestamp()
1✔
3026
        processed_onions = {}  # type: dict[ReceivedMPPHtlc, Tuple[ProcessedOnionPacket, Optional[ProcessedOnionPacket]]]
1✔
3027
        for mpp_htlc in mpp_set.htlcs:
1✔
3028
            processed_onion = self._process_incoming_onion_packet(
1✔
3029
                onion_packet=self._parse_onion_packet(mpp_htlc.unprocessed_onion),
3030
                payment_hash=payment_hash,
3031
                is_trampoline=False,  # this is always the outer onion
3032
            )
3033
            processed_onions[mpp_htlc] = (processed_onion, None)
1✔
3034
            inner_onion = None
1✔
3035
            if processed_onion.trampoline_onion_packet:
1✔
3036
                inner_onion = self._process_incoming_onion_packet(
1✔
3037
                    onion_packet=processed_onion.trampoline_onion_packet,
3038
                    payment_hash=payment_hash,
3039
                    is_trampoline=True,
3040
                )
3041
                processed_onions[mpp_htlc] = (processed_onion, inner_onion)
1✔
3042

3043
            total_msat_outer_onion = processed_onion.total_msat
1✔
3044
            total_msat_inner_onion = inner_onion.total_msat if inner_onion else None
1✔
3045
            if total_msat is None:
1✔
3046
                total_msat = total_msat_inner_onion or total_msat_outer_onion
1✔
3047

3048
            # check total_msat is equal for all htlcs of the set
3049
            if total_msat != (total_msat_inner_onion or total_msat_outer_onion):
1✔
3050
                _log_fail_reason(f"total_msat is not uniform: {total_msat=} != {processed_onion.total_msat=}")
×
3051
                return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
×
3052

3053
            amount_msat += mpp_htlc.htlc.amount_msat
1✔
3054

3055
        # If the set contains outer onions with different payment secrets, the set's payment_key is
3056
        # derived from the trampoline/invoice/inner payment secret, so it is the second stage of a
3057
        # multi-trampoline payment in which all the trampoline parts/htlcs got combined.
3058
        # In this case the amt_to_forward cannot be compared as it may differ between the trampoline parts.
3059
        # However, amt_to_forward should be similar for all onions of a single trampoline part and gets
3060
        # compared in the first stage where the htlc set represents a single trampoline part.
3061
        outer_onions = [onions[0] for onions in processed_onions.values()]
1✔
3062
        can_have_different_amt_to_fwd = not all(o.payment_secret == outer_onions[0].payment_secret for o in outer_onions)
1✔
3063
        trampoline_onions = iter(onions[1] for onions in processed_onions.values())
1✔
3064
        if not lnonion.compare_trampoline_onions(trampoline_onions, exclude_amt_to_fwd=can_have_different_amt_to_fwd):
1✔
3065
            _log_fail_reason(f"got inconsistent {trampoline_onions=}")
1✔
3066
            return OnionFailureCode.INVALID_ONION_PAYLOAD, None, None
1✔
3067

3068
        if len(processed_onions) == 1:
1✔
3069
            outer_onion, inner_onion = next(iter(processed_onions.values()))
1✔
3070
            if not outer_onion.are_we_final:
1✔
3071
                assert inner_onion is None, f"{outer_onion=}\n{inner_onion=}"
1✔
3072
                if not self.lnworker.enable_htlc_forwarding:
1✔
3073
                    return None, None, None
1✔
3074
                # this is a single (non-trampoline) htlc set which needs to be forwarded.
3075
                # set to settling state so it will not be failed or forwarded twice.
3076
                self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.SETTLING)
1✔
3077
                fwd_cb = lambda: self.lnworker.maybe_forward_htlc_set(payment_key, processed_htlc_set=processed_onions)
1✔
3078
                return None, None, fwd_cb
1✔
3079

3080
        assert payment_hash is not None and total_msat is not None
1✔
3081
        # check for expiry over time and potentially fail the whole set if any
3082
        # htlc's cltv becomes too close
3083
        blocks_to_expiry = max(0, closest_cltv_abs - local_height)
1✔
3084
        accepted_expiry_delta = self.lnworker.dont_expire_htlcs.get(payment_hash.hex(), MIN_FINAL_CLTV_DELTA_ACCEPTED)
1✔
3085
        if accepted_expiry_delta is not None and blocks_to_expiry < accepted_expiry_delta:
1✔
3086
            _log_fail_reason(f"htlc.cltv_abs is unreasonably close")
1✔
3087
            return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
1✔
3088

3089
        # check for mpp expiry (if incomplete and expired -> fail)
3090
        if mpp_set.resolution == RecvMPPResolution.WAITING \
1✔
3091
                or not self.lnworker.is_payment_bundle_complete(payment_key):
3092
            # maybe this set is COMPLETE but the bundle is not yet completed, so the bundle can be considered WAITING
3093
            if int(time.time()) - first_htlc_timestamp > self.lnworker.MPP_EXPIRY \
1✔
3094
                    or self.lnworker.lnpeermgr.stopping_soon:
3095
                _log_fail_reason(f"MPP TIMEOUT (> {self.lnworker.MPP_EXPIRY} sec)")
1✔
3096
                return OnionFailureCode.MPP_TIMEOUT, None, None
1✔
3097

3098
        if mpp_set.resolution == RecvMPPResolution.WAITING:
1✔
3099
            # calculate the sum of just in time channel opening fees, note jit only supports
3100
            # single part payments for now, this is enforced by checking against the invoice features
3101
            htlc_channels = [self.lnworker.get_channel_by_id(channel_id) for channel_id in set(h.channel_id for h in mpp_set.htlcs)]
1✔
3102
            jit_opening_fees_msat = sum((c.jit_opening_fee or 0) for c in htlc_channels)
1✔
3103

3104
            # check if set is first stage multi-trampoline payment to us
3105
            # first stage trampoline payment:
3106
            # is a trampoline payment + we_are_final + payment key is derived from outer onion's payment secret
3107
            # (so it is not the payment secret we requested in the invoice, but some secret set by a
3108
            # trampoline forwarding node on the route).
3109
            # if it is first stage, check if sum(htlcs) >= amount_to_forward of the trampoline_payload.
3110
            # If this part is complete, move the htlcs to the overall mpp set of the payment (keyed by inner secret).
3111
            # Once the second stage set (the set containing all htlcs of the separate trampoline parts)
3112
            # is complete, the payment gets fulfilled.
3113
            trampoline_payment_key = None
1✔
3114
            any_trampoline_onion = next(iter(processed_onions.values()))[1]
1✔
3115
            if any_trampoline_onion and any_trampoline_onion.are_we_final:
1✔
3116
                trampoline_payment_secret = any_trampoline_onion.payment_secret
1✔
3117
                assert trampoline_payment_secret == self.lnworker.get_payment_secret(payment_hash)
1✔
3118
                trampoline_payment_key = (payment_hash + trampoline_payment_secret).hex()
1✔
3119

3120
            if trampoline_payment_key and trampoline_payment_key != payment_key:
1✔
3121
                if jit_opening_fees_msat:
1✔
3122
                    # for jit openings we only accept a single htlc
3123
                    expected_amount_first_stage = any_trampoline_onion.total_msat - jit_opening_fees_msat
×
3124
                else:
3125
                    expected_amount_first_stage = any_trampoline_onion.amt_to_forward
1✔
3126

3127
                # first stage of trampoline payment, the first stage must never get set COMPLETE
3128
                if amount_msat >= expected_amount_first_stage:
1✔
3129
                    # setting the parent key will mark the htlcs to be moved to the parent set
3130
                    self.logger.debug(f"trampoline part complete. {len(mpp_set.htlcs)=}, "
1✔
3131
                                      f"{amount_msat=}. setting parent key: {trampoline_payment_key}")
3132
                    self.lnworker.received_mpp_htlcs[payment_key] = mpp_set._replace(
1✔
3133
                        parent_set_key=trampoline_payment_key,
3134
                    )
3135
            elif amount_msat >= (total_msat - jit_opening_fees_msat):  # regular mpp or 2nd stage trampoline
1✔
3136
                # set mpp_set as completed as we have received the full total_msat
3137
                mpp_set = self.lnworker.set_mpp_resolution(
1✔
3138
                    payment_key=payment_key,
3139
                    new_resolution=RecvMPPResolution.COMPLETE,
3140
                )
3141

3142
        # check if this set is a trampoline forwarding and potentially return forwarding callback
3143
        # note: all inner trampoline onions are equal (enforced above)
3144
        _, any_inner_onion = next(iter(processed_onions.values()))
1✔
3145
        if any_inner_onion and not any_inner_onion.are_we_final:
1✔
3146
            # this is a trampoline forwarding
3147
            can_forward = mpp_set.resolution == RecvMPPResolution.COMPLETE and self.lnworker.enable_htlc_forwarding
1✔
3148
            if not can_forward:
1✔
3149
                return None, None, None
×
3150
            self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.SETTLING)
1✔
3151
            fwd_cb = lambda: self.lnworker.maybe_forward_htlc_set(payment_key, processed_htlc_set=processed_onions)
1✔
3152
            return None, None, fwd_cb
1✔
3153

3154
        #  -- from here on it's assumed this set is a payment for us (not something to forward) --
3155
        payment_info = self.lnworker.get_payment_info(payment_hash, direction=RECEIVED)
1✔
3156
        if payment_info is None:
1✔
3157
            _log_fail_reason(f"payment info has been deleted")
×
3158
            return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
×
3159
        elif not payment_info.invoice_features.supports(LnFeatures.BASIC_MPP_OPT) and len(mpp_set.htlcs) > 1:
1✔
3160
            # in _check_unfulfilled_htlc we already check amount == total_amount, however someone could
3161
            # send us multiple htlcs that all pay the full amount, so we also check the htlc count
3162
            _log_fail_reason(f"got mpp but we requested no mpp in the invoice: {len(mpp_set.htlcs)=}")
×
3163
            return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
×
3164

3165
        # check invoice expiry, fail set if the invoice has expired before it was completed
3166
        if mpp_set.resolution == RecvMPPResolution.WAITING:
1✔
3167
            if int(time.time()) > payment_info.expiration_ts:
1✔
3168
                _log_fail_reason(f"invoice is expired {payment_info.expiration_ts=}")
×
3169
                return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
×
3170
            return None, None, None
1✔
3171

3172
        preimage = self.lnworker.get_preimage(payment_hash)
1✔
3173
        settling_blocked = preimage is not None and payment_hash.hex() in self.lnworker.dont_settle_htlcs
1✔
3174
        waiting_for_preimage = preimage is None and payment_hash.hex() in self.lnworker.dont_expire_htlcs
1✔
3175
        if settling_blocked or waiting_for_preimage:
1✔
3176
            # used by hold invoice cli and JIT channels to prevent the htlcs from getting fulfilled automatically
3177
            return None, None, None
1✔
3178

3179
        hold_invoice_callback = self.lnworker.hold_invoice_callbacks.get(payment_hash)
1✔
3180
        if not preimage and not hold_invoice_callback:
1✔
3181
            _log_fail_reason(f"cannot settle, no preimage or callback found for {payment_hash.hex()=}")
1✔
3182
            return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
1✔
3183

3184
        if not self.lnworker.is_payment_bundle_complete(payment_key):
1✔
3185
            # don't allow settling before all sets of the bundle are COMPLETE
3186
            return None, None, None
1✔
3187
        else:
3188
            # If this set is part of a bundle now all parts are COMPLETE so the bundle can be deleted
3189
            # so the individual sets will get fulfilled.
3190
            self.lnworker.delete_payment_bundle(payment_key=bytes.fromhex(payment_key))
1✔
3191

3192
        assert mpp_set.resolution == RecvMPPResolution.COMPLETE, "should return earlier if set is incomplete"
1✔
3193
        if not preimage:
1✔
3194
            assert hold_invoice_callback is not None, "should have been failed before"
1✔
3195
            async def callback():
1✔
3196
                try:
1✔
3197
                    await hold_invoice_callback(payment_hash)
1✔
3198
                except OnionRoutingFailure as e:  # todo: should this catch all exceptions?
1✔
3199
                    _log_fail_reason(f"hold invoice callback raised {e}")
1✔
3200
                    self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.FAILED)
1✔
3201
            # mpp set must not be failed unless the consumer calls unregister_hold_invoice and
3202
            # callback must only be called once. This is enforced by setting the set to SETTLING.
3203
            self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.SETTLING)
1✔
3204
            return None, None, callback
1✔
3205

3206
        # settle htlc set
3207
        self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.SETTLING)
1✔
3208
        return None, preimage, None
1✔
3209

3210
    def _check_final_mpp_set_state(
1✔
3211
        self,
3212
        payment_key: str,
3213
        mpp_set: ReceivedMPPStatus,
3214
    ) -> Optional[Tuple[
3215
            Optional[Union[OnionRoutingFailure, OnionFailureCode, bytes]],  # error types used to fail the set
3216
            Optional[bytes],  # preimage to settle the set
3217
            None,  # callback
3218
        ]]:
3219
        """
3220
        handle sets that are already in a state eligible for fulfillment or failure and shouldn't
3221
        go through another iteration of _check_unfulfilled_htlc_set.
3222
        """
3223
        if len(mpp_set.htlcs) == 0:
1✔
3224
            # stale set, will get deleted on the next iteration
3225
            return None, None, None
×
3226

3227
        if mpp_set.resolution == RecvMPPResolution.FAILED:
1✔
3228
            error_bytes, failure_message = self.lnworker.get_forwarding_failure(payment_key)
1✔
3229
            if error_bytes or failure_message:
1✔
3230
                return error_bytes or failure_message, None, None
1✔
3231
            return OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, None, None
1✔
3232
        elif mpp_set.resolution == RecvMPPResolution.EXPIRED:
1✔
3233
            return OnionFailureCode.MPP_TIMEOUT, None, None
1✔
3234

3235
        if mpp_set.parent_set_key:
1✔
3236
            # this is a complete trampoline part of a multi trampoline payment. Move the htlcs to parent.
3237
            parent = self.lnworker.received_mpp_htlcs.get(mpp_set.parent_set_key)
1✔
3238
            if not parent:
1✔
3239
                parent = ReceivedMPPStatus(
1✔
3240
                    resolution=RecvMPPResolution.WAITING,
3241
                    htlcs=frozenset(),
3242
                )
3243
            self.lnworker.received_mpp_htlcs[mpp_set.parent_set_key] = parent._replace(
1✔
3244
                htlcs=parent.htlcs | mpp_set.htlcs
3245
            )
3246
            self.lnworker.received_mpp_htlcs[payment_key] = mpp_set._replace(htlcs=frozenset())
1✔
3247
            return None, None, None  # this set will get deleted as there are no htlcs in it anymore
1✔
3248

3249
        assert not mpp_set.parent_set_key
1✔
3250
        if mpp_set.resolution == RecvMPPResolution.SETTLING:
1✔
3251
            # this is an ongoing forwarding, or a set that has not yet been fully settled (and removed).
3252
            # note the htlcs in SETTLING will not get failed automatically,
3253
            # even if timeout comes close, so either a forwarding failure or preimage has to be set
3254
            error_bytes, failure_message = self.lnworker.get_forwarding_failure(payment_key)
1✔
3255
            if error_bytes or failure_message:
1✔
3256
                # this was a forwarding set and it failed
3257
                self.lnworker.set_mpp_resolution(payment_key, RecvMPPResolution.FAILED)
1✔
3258
                return error_bytes or failure_message, None, None
1✔
3259
            payment_hash = mpp_set.get_payment_hash()
1✔
3260
            if payment_hash.hex() in self.lnworker.dont_settle_htlcs:
1✔
3261
                return None, None, None
1✔
3262
            preimage = self.lnworker.get_preimage(payment_hash)
1✔
3263
            return None, preimage, None
1✔
3264

3265
        return None
1✔
3266

3267
    def _parse_onion_packet(self, onion_packet_hex: str) -> OnionPacket:
1✔
3268
        """
3269
        https://github.com/lightning/bolts/blob/14272b1bd9361750cfdb3e5d35740889a6b510b5/02-peer-protocol.md?plain=1#L2352
3270
        """
3271
        onion_packet_bytes = None
1✔
3272
        try:
1✔
3273
            onion_packet_bytes = bytes.fromhex(onion_packet_hex)
1✔
3274
            onion_packet = OnionPacket.from_bytes(onion_packet_bytes)
1✔
3275
        except Exception as parsing_exc:
×
3276
            self.logger.warning(f"unable to parse onion: {str(parsing_exc)}")
×
3277
            onion_parsing_error = OnionParsingError(
×
3278
                data=sha256(onion_packet_bytes or b''),
3279
            )
3280
            raise onion_parsing_error
×
3281
        return onion_packet
1✔
3282

3283
    def _process_incoming_onion_packet(
1✔
3284
            self,
3285
            onion_packet: OnionPacket, *,
3286
            payment_hash: bytes,
3287
            is_trampoline: bool = False) -> ProcessedOnionPacket:
3288
        onion_hash = onion_packet.onion_hash
1✔
3289
        cache_key = sha256(onion_hash + payment_hash + bytes([is_trampoline]))  # type: ignore
1✔
3290
        if cached_onion := self._processed_onion_cache.get(cache_key):
1✔
3291
            return cached_onion
1✔
3292
        try:
1✔
3293
            processed_onion = lnonion.process_onion_packet(
1✔
3294
                onion_packet,
3295
                our_onion_private_key=self.privkey,
3296
                associated_data=payment_hash,
3297
                is_trampoline=is_trampoline)
3298
            self._processed_onion_cache[cache_key] = processed_onion
1✔
3299
        except UnsupportedOnionPacketVersion:
×
3300
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=onion_hash)
×
3301
        except InvalidOnionPubkey:
×
3302
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_KEY, data=onion_hash)
×
3303
        except InvalidOnionMac:
×
3304
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_HMAC, data=onion_hash)
×
3305
        except Exception as e:
×
3306
            self.logger.warning(f"error processing onion packet: {e!r}")
×
3307
            raise OnionParsingError(data=onion_hash)
×
3308
        if self.config.TEST_FAIL_HTLCS_AS_MALFORMED:
1✔
3309
            raise OnionParsingError(data=onion_hash)
1✔
3310
        if self.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE:
1✔
3311
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
1✔
3312
        return processed_onion
1✔
3313

3314
    def on_onion_message(self, payload):
1✔
3315
        if hasattr(self.lnworker, 'onion_message_manager'):  # only on LNWallet
×
3316
            self.lnworker.onion_message_manager.on_onion_message(payload)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc