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

spesmilo / electrum / 35374450208

18 Sep 2026 05:27PM UTC coverage: 69.868% (-0.03%) from 69.898%
35374450208

push

github

web-flow
Merge pull request #10975 from f321x/friendlier_strings_qt_tx_dialog

qt: TxEditor: friendlier strings for send change to lightning

1 of 17 new or added lines in 1 file covered. (5.88%)

11 existing lines in 4 files now uncovered.

27285 of 39052 relevant lines covered (69.87%)

0.7 hits per line

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

74.51
/electrum/lnworker.py
1
# Copyright (C) 2018 The Electrum developers
2
# Distributed under the MIT software license, see the accompanying
3
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
4

5
import asyncio
1✔
6
import os
1✔
7
from decimal import Decimal
1✔
8
import random
1✔
9
import time
1✔
10
from enum import IntEnum
1✔
11
from typing import (
1✔
12
    Optional, Sequence, Tuple, List, Set, Dict, TYPE_CHECKING, NamedTuple, Mapping, Any, Iterable, AsyncGenerator,
13
    Callable, Awaitable, Union,
14
)
15
from types import MappingProxyType
1✔
16
import threading
1✔
17
import socket
1✔
18
from functools import partial
1✔
19
from collections import defaultdict
1✔
20
import concurrent
1✔
21
from concurrent import futures
1✔
22
import urllib.parse
1✔
23
import itertools
1✔
24
import dataclasses
1✔
25
from math import ceil
1✔
26

27
import aiohttp
1✔
28
import dns.asyncresolver
1✔
29
import dns.exception
1✔
30
from aiorpcx import run_in_thread, NetAddress, ignore_after
1✔
31

32
from .logging import Logger
1✔
33
from .i18n import _
1✔
34
from .channel_db import UpdateStatus, ChannelDBNotLoaded, get_mychannel_info, get_mychannel_policy
1✔
35

36
from . import constants, util, lnutil
1✔
37
from . import bitcoin
1✔
38
from . import crandom
1✔
39
from .util import (
1✔
40
    profiler, OldTaskGroup, ESocksProxy, NetworkRetryManager, JsonRPCClient, NotEnoughFunds, EventListener,
41
    event_listener, bfh, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions, ignore_exceptions,
42
    make_aiohttp_session, random_shuffled_copy, is_private_netaddress,
43
    UnrelatedTransactionException, LightningHistoryItem, get_asyncio_loop,
44
)
45
from .fee_policy import (
1✔
46
    FeePolicy, FEERATE_FALLBACK_STATIC_FEE, FEE_LN_ETA_TARGET, FEE_LN_LOW_ETA_TARGET,
47
    FEERATE_PER_KW_MIN_RELAY_LIGHTNING, FEE_LN_MINIMUM_ETA_TARGET
48
)
49
from .invoices import (Invoice, Request, PR_UNPAID, PR_PAID, PR_INFLIGHT, PR_FAILED, LN_EXPIRY_NEVER,
1✔
50
                       BaseInvoice)
51
from .bitcoin import COIN, opcodes, make_op_return, address_to_scripthash, DummyAddress
1✔
52
from .bip32 import BIP32Node
1✔
53
from .address_synchronizer import TX_HEIGHT_LOCAL
1✔
54
from .transaction import (
1✔
55
    Transaction, get_script_type_from_output_script, PartialTxOutput, PartialTransaction, PartialTxInput
56
)
57
from .crypto import (
1✔
58
    sha256, chacha20_encrypt, chacha20_decrypt, pw_encode_with_version_and_mac, pw_decode_with_version_and_mac
59
)
60

61
from .onion_message import OnionMessageManager
1✔
62
from .lntransport import (
1✔
63
    LNTransport, LNResponderTransport, LNTransportBase, LNPeerAddr, split_host_port, extract_nodeid,
64
    ConnStringFormatError
65
)
66
from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
1✔
67
from .bolt11 import encode_bolt11_invoice, BOLT11Addr, decode_bolt11_invoice
1✔
68
from .lnchannel import Channel, AbstractChannel, ChannelState, PeerState, HTLCWithStatus, ChannelBackup
1✔
69
from .lnrater import LNRater
1✔
70
from .lnutil import (
1✔
71
    get_compressed_pubkey_from_bech32, serialize_htlc_key, deserialize_htlc_key, PaymentFailure, generate_keypair,
72
    LnKeyFamily, LOCAL, REMOTE, MIN_FINAL_CLTV_DELTA_ACCEPTED, SENT, RECEIVED, HTLCOwner, UpdateAddHtlc, LnFeatures,
73
    ShortChannelID, HtlcLog, NoPathFound, InvalidGossipMsg, FeeBudgetExceeded, ImportedChannelBackupStorage,
74
    OnchainChannelBackupStorage, ln_compare_features, IncompatibleLightningFeatures, PaymentFeeBudget,
75
    NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE, GossipForwardingMessage, MIN_FUNDING_SAT,
76
    MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE, RecvMPPResolution, ReceivedMPPStatus, ReceivedMPPHtlc,
77
    PaymentSuccess, ChannelType, LocalConfig, Keypair, ZEROCONF_TIMEOUT,
78
)
79
from .lnonion import (
1✔
80
    decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket,
81
    ProcessedOnionPacket, calc_hops_data_for_payment, new_onion_packet,
82
)
83
from .lnmsg import decode_msg
1✔
84
from .lnrouter import (
1✔
85
    RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_within_budget, NoChannelPolicy,
86
    LNPathInconsistent, fee_for_edge_msat,
87
)
88
from .lnwatcher import LNWatcher
1✔
89
from .submarine_swaps import SwapManager
1✔
90
from .mpp_split import suggest_splits, SplitConfigRating
1✔
91
from .trampoline import (
1✔
92
    create_trampoline_route_and_onion, is_legacy_relay, trampolines_by_id, hardcoded_trampoline_nodes,
93
    is_hardcoded_trampoline, decode_routing_info, encode_next_trampolines, decode_next_trampolines
94
)
95
from .stored_dict import StoredDict
1✔
96

97
if TYPE_CHECKING:
98
    from .network import Network
99
    from .wallet import Abstract_Wallet, WalletWarning
100
    from .channel_db import ChannelDB
101
    from .simple_config import SimpleConfig
102

103

104
SAVED_PR_STATUS = [PR_PAID, PR_UNPAID]  # status that are persisted
1✔
105

106
NUM_PEERS_TARGET = 4
1✔
107

108
# onchain channel backup data
109
CB_VERSION = 0
1✔
110
CB_MAGIC_BYTES = bytes([0, 0, 0, CB_VERSION])
1✔
111
NODE_ID_PREFIX_LEN = 16
1✔
112

113

114
class PaymentDirection(IntEnum):
1✔
115
    SENT = 0
1✔
116
    RECEIVED = 1
1✔
117
    SELF_PAYMENT = 2
1✔
118
    FORWARDING = 3
1✔
119

120

121
@dataclasses.dataclass(frozen=True, kw_only=True)
1✔
122
class PaymentInfo:
1✔
123
    """Information required to handle incoming htlcs for a payment request.
124

125
    - Historically, we used to store "bolt11, direction, status", but deserializing bolt11 was too slow.
126
      (even deserializing just once - all bolt11 during wallet-open - was slow)
127
      - note: the deserialization code in bolt11.py has been significantly sped up since
128
    - For incoming payments, for unpaid requests, ~every time the user displays the unpaid bolt11,
129
      we get a chance to display a new bolt11, with same payment_hash/amount, but with updated
130
      routing_hints (channels might get closed/opened, or just liquidity changed drastically).
131
    """
132
    payment_hash: bytes
1✔
133
    amount_msat: Optional[int]
1✔
134
    direction: lnutil.Direction
1✔
135
    status: int
1✔
136
    min_final_cltv_delta: int
1✔
137
    expiry_delay: int
1✔
138
    creation_ts: int = dataclasses.field(default_factory=lambda: int(time.time()))
1✔
139
    invoice_features: LnFeatures
1✔
140

141
    @property
1✔
142
    def expiration_ts(self):
1✔
143
        return self.creation_ts + self.expiry_delay
1✔
144

145
    def validate(self):
1✔
146
        assert isinstance(self.payment_hash, bytes) and len(self.payment_hash) == 32
1✔
147
        assert isinstance(self.direction, int)
1✔
148
        assert self.amount_msat is None or isinstance(self.amount_msat, int)
1✔
149
        if self.direction == RECEIVED:
1✔
150
            assert self.amount_msat != 0  # use amount_msat=None instead!
1✔
151
        assert isinstance(self.status, int)
1✔
152
        assert isinstance(self.min_final_cltv_delta, int)
1✔
153
        assert isinstance(self.expiry_delay, int) and self.expiry_delay > 0, repr(self.expiry_delay)
1✔
154
        assert isinstance(self.creation_ts, int)
1✔
155
        assert isinstance(self.invoice_features, LnFeatures)
1✔
156

157
    def __post_init__(self):
1✔
158
        self.validate()
1✔
159

160
    @property
1✔
161
    def db_key(self) -> str:
1✔
162
        return self.calc_db_key(payment_hash_hex=self.payment_hash.hex(), direction=self.direction)
1✔
163

164
    @classmethod
1✔
165
    def calc_db_key(cls, *, payment_hash_hex: str, direction: lnutil.Direction) -> str:
1✔
166
        return f"{payment_hash_hex}:{int(direction)}"
1✔
167

168

169
SentHtlcKey = Tuple[bytes, ShortChannelID, int]  # RHASH, scid, htlc_id
1✔
170

171

172
class SentHtlcInfo(NamedTuple):
1✔
173
    route: LNPaymentRoute
1✔
174
    payment_secret_orig: bytes
1✔
175
    payment_secret_bucket: bytes
1✔
176
    amount_msat: int
1✔
177
    bucket_msat: int
1✔
178
    amount_receiver_msat: int
1✔
179
    trampoline_fee_level: Optional[int]
1✔
180
    trampoline_route: Optional[LNPaymentRoute]
1✔
181

182

183
class ErrorAddingPeer(Exception): pass
1✔
184

185

186
# set some feature flags as baseline for both LNWallet and LNGossip
187
# note that e.g. DATA_LOSS_PROTECT and OPTION_CHANNEL_TYPE_OPT are needed for LNGossip as many peers require it
188
BASE_FEATURES = (
1✔
189
    LnFeatures(0)
190
    | LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
191
    | LnFeatures.OPTION_STATIC_REMOTEKEY_OPT
192
    | LnFeatures.VAR_ONION_OPT
193
    | LnFeatures.PAYMENT_SECRET_OPT
194
    | LnFeatures.OPTION_ANCHORS_OPT
195
    | LnFeatures.OPTION_UPFRONT_SHUTDOWN_SCRIPT_OPT
196
    | LnFeatures.OPTION_CHANNEL_TYPE_OPT
197
)
198

199
# we do not want to receive unrequested gossip (see lnpeer.maybe_save_remote_update)
200
# NOTE: Also update lnutil.LN_FEATURES_IMPLEMENTED when adding a new feature
201
LNWALLET_FEATURES = (
1✔
202
    BASE_FEATURES
203
    | LnFeatures.OPTION_DATA_LOSS_PROTECT_REQ
204
    | LnFeatures.OPTION_STATIC_REMOTEKEY_REQ
205
    | LnFeatures.OPTION_ANCHORS_REQ
206
    | LnFeatures.VAR_ONION_REQ
207
    | LnFeatures.PAYMENT_SECRET_REQ
208
    | LnFeatures.BASIC_MPP_OPT
209
    | LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
210
    | LnFeatures.OPTION_SHUTDOWN_ANYSEGWIT_OPT
211
    | LnFeatures.OPTION_SCID_ALIAS_OPT
212
    | LnFeatures.OPTION_SUPPORT_LARGE_CHANNEL_OPT
213
    | LnFeatures.OPTION_CHANNEL_TYPE_REQ
214
)
215

216
LNGOSSIP_FEATURES = (
1✔
217
    BASE_FEATURES
218
    # LNGossip doesn't serve gossip but weirdly have to signal so
219
    # that peers satisfy our queries
220
    | LnFeatures.GOSSIP_QUERIES_REQ
221
    | LnFeatures.GOSSIP_QUERIES_OPT
222
)
223

224

225
class LNPeerManager(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
1✔
226

227
    def __init__(
1✔
228
        self, node_keypair,
229
        *,
230
        lnwallet_or_lngossip: 'LNWallet | LNGossip',
231
        features: LnFeatures,
232
        config: 'SimpleConfig',
233
    ):
234
        NetworkRetryManager.__init__(
1✔
235
            self,
236
            max_retry_delay_normal=3600,
237
            init_retry_delay_normal=600,
238
            max_retry_delay_urgent=300,
239
            init_retry_delay_urgent=4,
240
        )
241
        self.lock = threading.RLock()
1✔
242
        self.node_keypair = node_keypair
1✔
243
        self._lnwallet_or_lngossip = lnwallet_or_lngossip
1✔
244
        Logger.__init__(self)
1✔
245
        self._peers = {}  # type: Dict[bytes, Peer]  # pubkey -> Peer  # needs self.lock
1✔
246
        self._channelless_incoming_peers = set()  # type: Set[bytes]  # node_ids  # needs self.lock
1✔
247
        self.taskgroup = OldTaskGroup()
1✔
248
        self.listen_server = None  # type: Optional[asyncio.AbstractServer]
1✔
249
        self.features = features
1✔
250
        self.network = None  # type: Optional[Network]
1✔
251
        self.config = config
1✔
252
        self.stopping_soon = False  # whether we are being shut down
1✔
253
        self.register_callbacks()
1✔
254

255
    def diagnostic_name(self):
1✔
256
        lnw = self._lnwallet_or_lngossip
1✔
257
        return lnw.diagnostic_name() or lnw.__class__.__name__
1✔
258

259
    @property
1✔
260
    def channel_db(self) -> 'ChannelDB':
1✔
261
        return self.network.channel_db if self.network else None
1✔
262

263
    def uses_trampoline(self) -> bool:
1✔
264
        return not bool(self.channel_db)
1✔
265

266
    @property
1✔
267
    def peers(self) -> Mapping[bytes, Peer]:
1✔
268
        """Returns a read-only copy of peers."""
269
        with self.lock:
1✔
270
            return self._peers.copy()
1✔
271

272
    def channels_for_peer(self, node_id: bytes) -> Dict[bytes, Channel]:
1✔
273
        return self._lnwallet_or_lngossip.channels_for_peer(node_id)
1✔
274

275
    def get_peer_by_pubkey(self, pubkey: bytes) -> Optional[Peer]:
1✔
276
        return self._peers.get(pubkey)
1✔
277

278
    def get_node_alias(self, node_id: bytes) -> Optional[str]:
1✔
279
        """Returns the alias of the node, or None if unknown."""
280
        node_alias = None
×
281
        if not self.uses_trampoline():
×
282
            node_info = self.channel_db.get_node_info_for_node_id(node_id)
×
283
            if node_info:
×
284
                node_alias = node_info.alias
×
285
        else:
286
            for k, v in hardcoded_trampoline_nodes().items():
×
287
                if v.pubkey.startswith(node_id):
×
288
                    node_alias = k
×
289
                    break
×
290
        return node_alias
×
291

292
    async def maybe_listen(self):
1✔
293
        # FIXME: only one LNPeerManager can listen at a time (single port)
294
        listen_addr = self.config.LIGHTNING_LISTEN
1✔
295
        if listen_addr:
1✔
296
            self.logger.info(f'lightning_listen enabled. will try to bind: {listen_addr!r}')
1✔
297
            try:
1✔
298
                netaddr = NetAddress.from_string(listen_addr)
1✔
299
            except Exception as e:
×
300
                self.logger.error(f"failed to parse config key '{self.config.cv.LIGHTNING_LISTEN.key()}'. got: {e!r}")
×
301
                return
×
302
            addr = str(netaddr.host)
1✔
303

304
            async def cb(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
1✔
305
                transport = LNResponderTransport(self.node_keypair.privkey, reader, writer)
1✔
306
                try:
1✔
307
                    node_id = await transport.handshake()
1✔
308
                except Exception as e:
×
309
                    self.logger.info(f'handshake failure from incoming connection: {e!r}')
×
310
                    return
×
311
                peername = writer.get_extra_info('peername')
1✔
312
                self.logger.debug(f"handshake done for incoming peer: {peername=}, node_id={node_id.hex()}")
1✔
313
                await self._add_peer_from_transport(node_id=node_id, transport=transport)
1✔
314
            try:
1✔
315
                self.listen_server = await asyncio.start_server(cb, addr, netaddr.port)
1✔
316
            except OSError as e:
×
317
                self.logger.error(f"cannot listen for lightning p2p. error: {e!r}")
×
318

319
    async def main_loop(self):
1✔
320
        self.logger.info("starting taskgroup.")
1✔
321
        try:
1✔
322
            async with self.taskgroup as group:
1✔
323
                await group.spawn(asyncio.Event().wait)  # run forever (until cancel)
1✔
324
        except Exception as e:
1✔
325
            self.logger.exception("taskgroup died.")
×
326
        finally:
327
            self.logger.info("taskgroup stopped.")
1✔
328

329
    async def _maintain_connectivity(self):
1✔
330
        while True:
×
331
            await asyncio.sleep(1)
×
332
            if self.stopping_soon:
×
333
                return
×
334
            now = time.time()
×
335
            if len(self._peers) >= NUM_PEERS_TARGET:
×
336
                continue
×
337
            peers = await self._get_next_peers_to_try()
×
338
            for peer in peers:
×
339
                if self._can_retry_addr(peer, now=now):
×
340
                    try:
×
341
                        await self._add_peer(peer.host, peer.port, peer.pubkey)
×
342
                    except ErrorAddingPeer as e:
×
343
                        self.logger.info(f"failed to add peer: {peer}. exc: {e!r}")
×
344

345
    async def _add_peer(self, host: str, port: int, node_id: bytes) -> Peer:
1✔
346
        if node_id in self._peers:
1✔
347
            return self._peers[node_id]
×
348
        port = int(port)
1✔
349
        peer_addr = LNPeerAddr(host, port, node_id)
1✔
350
        self._trying_addr_now(peer_addr)
1✔
351
        self.logger.info(f"adding peer {peer_addr}")
1✔
352
        if node_id == self.node_keypair.pubkey or self.is_our_lnwallet(node_id):
1✔
353
            raise ErrorAddingPeer("cannot connect to self")
×
354
        transport = LNTransport(self.node_keypair.privkey, peer_addr,
1✔
355
                                e_proxy=ESocksProxy.from_network_settings(self.network))
356
        peer = await self._add_peer_from_transport(node_id=node_id, transport=transport)
1✔
357
        assert peer
1✔
358
        return peer
1✔
359

360
    async def _add_peer_from_transport(self, *, node_id: bytes, transport: LNTransportBase) -> Optional[Peer]:
1✔
361
        with self.lock:
1✔
362
            existing_peer = self._peers.get(node_id)
1✔
363
            if existing_peer:
1✔
364
                # Two instances of the same wallet are attempting to connect simultaneously.
365
                # If we let the new connection replace the existing one, the two instances might
366
                # both keep trying to reconnect, resulting in neither being usable.
367
                if existing_peer.is_initialized():
×
368
                    # give priority to the existing connection
369
                    transport.close()
×
370
                    return None
×
371
                else:
372
                    # Use the new connection. (e.g. old peer might be an outgoing connection
373
                    # for an outdated host/port that will never connect)
374
                    existing_peer.close_and_cleanup()
×
375
            # limit max number of incoming channel-less peers.
376
            # what to do if limit is reached?
377
            # - chosen strategy: we don't allow new connections.
378
            #   - drawback: attacker can use up all our slots
379
            # - alternative: kick oldest channel-less peer
380
            #   - drawback: if many legit peers want to connect to us, we will keep kicking them
381
            #               in round-robin, and they will keep reconnecting. no stable state -> we self-DOS
382
            # TODO make slots IP-based?
383
            if isinstance(transport, LNResponderTransport):
1✔
384
                assert node_id not in self._channelless_incoming_peers
1✔
385
                chans = [chan for chan in self.channels_for_peer(node_id).values() if chan.is_funded()]
1✔
386
                if not chans:
1✔
387
                    if len(self._channelless_incoming_peers) > 100:
1✔
388
                        transport.close()
×
389
                        return None
×
390
                    self._channelless_incoming_peers.add(node_id)
1✔
391
            # checks done: we are adding this peer.
392
            peer = Peer(self._lnwallet_or_lngossip, node_id, transport)
1✔
393
            assert node_id not in self._peers
1✔
394
            self._peers[node_id] = peer
1✔
395
        await self.taskgroup.spawn(peer.main_loop())
1✔
396
        return peer
1✔
397

398
    def peer_closed(self, peer: Peer) -> None:
1✔
399
        if isinstance(self._lnwallet_or_lngossip, LNWallet):
1✔
400
            for chan in self.channels_for_peer(peer.pubkey).values():
1✔
401
                chan.peer_state = PeerState.DISCONNECTED
1✔
402
                util.trigger_callback('channel', self._lnwallet_or_lngossip.wallet, chan)
1✔
403
        with self.lock:
1✔
404
            peer2 = self._peers.get(peer.pubkey)
1✔
405
            if peer2 is peer:
1✔
406
                self._peers.pop(peer.pubkey)
1✔
407
            self._channelless_incoming_peers.discard(peer.pubkey)
1✔
408

409
    def num_peers(self) -> int:
1✔
410
        return sum([p.is_initialized() for p in self.peers.values()])
×
411

412
    def is_our_lnwallet(self, node_id: bytes) -> bool:
1✔
413
        """Check if node_id is one of our own wallets"""
414
        wallets = self.network.daemon.get_wallets()
1✔
415
        for wallet in wallets.values():
1✔
416
            if wallet.lnworker and wallet.lnworker.node_keypair.pubkey == node_id:
×
417
                return True
×
418
        return False
1✔
419

420
    def start_network(
1✔
421
        self, network: 'Network', *,
422
        listen: bool = False,
423
        maintain_random_peers: bool = False,
424
    ) -> None:
425
        assert network
1✔
426
        assert self.network is None, "already started"
1✔
427
        self.network = network
1✔
428
        assert network.config is self.config
1✔
429
        self._add_peers_from_config()
1✔
430
        asyncio.run_coroutine_threadsafe(self.main_loop(), get_asyncio_loop())
1✔
431
        if listen:
1✔
432
            tg_coro = self.taskgroup.spawn(self.maybe_listen())
1✔
433
            asyncio.run_coroutine_threadsafe(tg_coro, get_asyncio_loop())
1✔
434
        if maintain_random_peers:
1✔
435
            tg_coro = self.taskgroup.spawn(self._maintain_connectivity())
×
436
            asyncio.run_coroutine_threadsafe(tg_coro, get_asyncio_loop())
×
437

438
    async def stop(self):
1✔
439
        self.stopping_soon = True
1✔
440
        if self.listen_server:
1✔
441
            self.listen_server.close()
1✔
442
        self.unregister_callbacks()
1✔
443
        await self.taskgroup.cancel_remaining()
1✔
444

445
    def _add_peers_from_config(self):
1✔
446
        peer_list = self.config.LIGHTNING_PEERS or []
1✔
447
        for host, port, pubkey in peer_list:
1✔
448
            asyncio.run_coroutine_threadsafe(
×
449
                self._add_peer(host, int(port), bfh(pubkey)),
450
                get_asyncio_loop())
451

452
    def is_good_peer(self, peer: LNPeerAddr) -> bool:
1✔
453
        # the purpose of this method is to filter peers that advertise the desired feature bits
454
        # it is disabled for now, because feature bits published in node announcements seem to be unreliable
455
        return True
×
456
        node_id = peer.pubkey
457
        node = self.channel_db._nodes.get(node_id)
458
        if not node:
459
            return False
460
        try:
461
            ln_compare_features(self.features, node.features)
462
        except IncompatibleLightningFeatures:
463
            return False
464
        #self.logger.info(f'is_good {peer.host}')
465
        return True
466

467
    def on_peer_successfully_established(self, peer: Peer) -> None:
1✔
468
        if isinstance(peer.transport, LNTransport):
1✔
469
            peer_addr = peer.transport.peer_addr
1✔
470
            # reset connection attempt count
471
            self._on_connection_successfully_established(peer_addr)
1✔
472
            if not self.uses_trampoline():
1✔
473
                # add into channel db
474
                self.channel_db.add_recent_peer(peer_addr)
×
475
            # save network address into channels we might have with peer
476
            for chan in peer.channels.values():
1✔
477
                chan.add_or_update_peer_addr(peer_addr)
×
478

479
    async def _get_next_peers_to_try(self) -> Sequence[LNPeerAddr]:
1✔
480
        now = time.time()
×
481
        await self.channel_db.data_loaded.wait()
×
482
        # first try from recent peers
483
        recent_peers = self.channel_db.get_recent_peers()
×
484
        for peer in recent_peers:
×
485
            if not peer:
×
486
                continue
×
487
            if peer.pubkey in self._peers:
×
488
                continue
×
489
            if not self._can_retry_addr(peer, now=now):
×
490
                continue
×
491
            if not self.is_good_peer(peer):
×
492
                continue
×
493
            if peer.is_onion() and not self.network.is_proxy_tor:
×
494
                continue
×
495
            return [peer]
×
496
        # try random peer from graph
497
        unconnected_nodes = self.channel_db.get_200_randomly_sorted_nodes_not_in(self.peers.keys())
×
498
        if unconnected_nodes:
×
499
            for node_id in unconnected_nodes:
×
500
                addrs = self.channel_db.get_node_addresses(node_id)
×
501
                if not addrs:
×
502
                    continue
×
503
                address = self.choose_preferred_address(list(addrs))
×
504
                if not address:
×
505
                    continue
×
506
                host, port, timestamp = address
×
507
                try:
×
508
                    peer = LNPeerAddr(host, port, node_id)
×
509
                except ValueError:
×
510
                    continue
×
511
                if not self._can_retry_addr(peer, now=now):
×
512
                    continue
×
513
                if not self.is_good_peer(peer):
×
514
                    continue
×
515
                #self.logger.info('taking random ln peer from our channel db')
516
                return [peer]
×
517

518
        # getting desperate... let's try hardcoded fallback list of peers
519
        fallback_list = constants.net.FALLBACK_LN_NODES
×
520
        fallback_list = [peer for peer in fallback_list if self._can_retry_addr(peer, now=now)]
×
521
        if fallback_list:
×
522
            return [random.choice(fallback_list)]
×
523

524
        # last resort: try dns seeds (BOLT-10)
525
        return await self._get_peers_from_dns_seeds()
×
526

527
    async def _get_peers_from_dns_seeds(self) -> Sequence[LNPeerAddr]:
1✔
528
        # Return several peers to reduce the number of dns queries.
529
        if not constants.net.LN_DNS_SEEDS:
×
530
            return []
×
531
        dns_seed = random.choice(constants.net.LN_DNS_SEEDS)
×
532
        self.logger.info('asking dns seed "{}" for ln peers'.format(dns_seed))
×
533
        try:
×
534
            # note: this might block for several seconds
535
            # this will include bech32-encoded-pubkeys and ports
536
            srv_answers = await resolve_dns_srv('r{}.{}'.format(
×
537
                constants.net.LN_REALM_BYTE, dns_seed))
538
        except dns.exception.DNSException as e:
×
539
            self.logger.info(f'failed querying (1) dns seed "{dns_seed}" for ln peers: {repr(e)}')
×
540
            return []
×
541
        random.shuffle(srv_answers)
×
542
        num_peers = 2 * NUM_PEERS_TARGET
×
543
        srv_answers = srv_answers[:num_peers]
×
544
        # we now have pubkeys and ports but host is still needed
545
        peers = []
×
546
        for srv_ans in srv_answers:
×
547
            try:
×
548
                # note: this might take several seconds
549
                answers = await dns.asyncresolver.resolve(srv_ans['host'])
×
550
            except dns.exception.DNSException as e:
×
551
                self.logger.info(f'failed querying (2) dns seed "{dns_seed}" for ln peers: {repr(e)}')
×
552
                continue
×
553
            try:
×
554
                ln_host = str(answers[0])
×
555
                port = int(srv_ans['port'])
×
556
                bech32_pubkey = srv_ans['host'].split('.')[0]
×
557
                pubkey = get_compressed_pubkey_from_bech32(bech32_pubkey)
×
558
                peers.append(LNPeerAddr(ln_host, port, pubkey))
×
559
            except Exception as e:
×
560
                self.logger.info(f'error with parsing peer from dns seed: {repr(e)}')
×
561
                continue
×
562
        self.logger.info(f'got {len(peers)} ln peers from dns seed')
×
563
        return peers
×
564

565
    def choose_preferred_address(self, addr_list: Sequence[Tuple[str, int, int]]) -> Optional[Tuple[str, int, int]]:
1✔
566
        assert len(addr_list) >= 1
1✔
567
        # choose the most recent one that is an IP
568
        for host, port, timestamp in sorted(addr_list, key=lambda a: -a[2]):
1✔
569
            if is_ip_address(host):
1✔
570
                return host, port, timestamp
1✔
571
        if not self.network.is_proxy_tor:
1✔
572
            addr_list = [(h, p, ts) for h, p, ts in addr_list if not h.endswith('.onion')]
1✔
573
        if not addr_list:
1✔
574
            return None
1✔
575
        # otherwise choose one at random
576
        choice = random.choice(addr_list)
1✔
577
        return choice
1✔
578

579
    @event_listener
1✔
580
    def on_event_proxy_set(self, *args):
1✔
581
        for peer in self.peers.values():
×
582
            peer.close_and_cleanup()
×
583
        self._clear_addr_retry_times()
×
584

585
    @log_exceptions
1✔
586
    async def add_peer(self, connect_str: str) -> Peer:
1✔
587
        node_id, rest = extract_nodeid(connect_str)
1✔
588
        peer = self._peers.get(node_id)
1✔
589
        if not peer:
1✔
590
            if rest is not None:
1✔
591
                host, port = split_host_port(rest)
1✔
592
            else:
593
                if self.uses_trampoline():
1✔
594
                    addr = trampolines_by_id().get(node_id)
1✔
595
                    if not addr:
1✔
596
                        raise ConnStringFormatError(_('Address unknown for node:') + ' ' + node_id.hex())
1✔
597
                    host, port = addr.host, addr.port
×
598
                else:
599
                    addrs = self.channel_db.get_node_addresses(node_id)
1✔
600
                    if not addrs or not (address := self.choose_preferred_address(list(addrs))):
1✔
601
                        raise ConnStringFormatError(_('Don\'t know any addresses for node:') + ' ' + node_id.hex())
1✔
602
                    host, port, timestamp = address
×
603
            port = int(port)
1✔
604

605
            if not self.network.proxy or not self.network.proxy.enabled:
1✔
606
                # Try DNS-resolving the host (if needed). This is simply so that
607
                # the caller gets a nice exception if it cannot be resolved.
608
                # (we don't do the DNS lookup if a proxy is set, to avoid a DNS-leak)
609
                if host.endswith('.onion'):
1✔
610
                    raise ConnStringFormatError(_('.onion address, but no proxy configured'))
1✔
611
                try:
1✔
612
                    await asyncio.get_running_loop().getaddrinfo(host, port)
1✔
613
                except socket.gaierror:
1✔
614
                    raise ConnStringFormatError(_('Hostname does not resolve (getaddrinfo failed)'))
1✔
615

616
            # add peer
617
            peer = await self._add_peer(host, port, node_id)
1✔
618
        return peer
1✔
619

620
    async def reestablish_peer_for_given_channel(self, chan: Channel) -> None:
1✔
621
        await self.taskgroup.spawn(self._reestablish_peer_for_given_channel(chan))
1✔
622

623
    @ignore_exceptions
1✔
624
    @log_exceptions
1✔
625
    async def _reestablish_peer_for_given_channel(self, chan: Channel) -> None:
1✔
626
        now = time.time()
1✔
627
        peer_addresses = []
1✔
628
        if self.uses_trampoline():
1✔
629
            addr = trampolines_by_id().get(chan.node_id)
1✔
630
            if addr:
1✔
631
                peer_addresses.append(addr)
×
632
        else:
633
            # will try last good address first, from gossip
634
            last_good_addr = self.channel_db.get_last_good_address(chan.node_id)
×
635
            if last_good_addr:
×
636
                peer_addresses.append(last_good_addr)
×
637
            # will try addresses for node_id from gossip
638
            addrs_from_gossip = self.channel_db.get_node_addresses(chan.node_id) or []
×
639
            for host, port, ts in addrs_from_gossip:
×
640
                peer_addresses.append(LNPeerAddr(host, port, chan.node_id))
×
641
        # will try addresses stored in channel storage
642
        peer_addresses += list(chan.get_peer_addresses())
1✔
643
        # Done gathering addresses.
644
        # Now select first one that has not failed recently.
645
        for peer in peer_addresses:
1✔
646
            if self._can_retry_addr(peer, urgent=True, now=now):
×
647
                await self._add_peer(peer.host, peer.port, peer.pubkey)
×
648
                return
×
649

650
    async def reestablish_peer_for_zero_conf_trusted_node(self) -> None:
1✔
651
        if self.config.ZEROCONF_TRUSTED_NODE:
1✔
652
            peer = LNPeerAddr.from_str(self.config.ZEROCONF_TRUSTED_NODE)
×
653
            if self._can_retry_addr(peer, urgent=True):
×
654
                await self._add_peer(peer.host, peer.port, peer.pubkey)
×
655

656

657
class LNGossip(Logger):
1✔
658
    """The LNGossip class is a separate, unannounced Lightning node with random id that is just querying
659
    gossip from other nodes. The LNGossip node does not satisfy gossip queries, this is done by the
660
    LNWallet class(es). LNWallets are the advertised nodes used for actual payments and only satisfy
661
    peer queries without fetching gossip themselves. This separation is done so that gossip can be queried
662
    independently of the active LNWallets. LNGossip keeps a curated batch of gossip in _forwarding_gossip
663
    that is fetched by the LNWallets for regular forwarding."""
664
    max_age = 14*24*3600
1✔
665

666
    def __init__(self, config: 'SimpleConfig'):
1✔
667
        self.config = config
×
668
        seed = crandom.get_rand_bytes(32)
×
669
        node = BIP32Node.from_rootseed(seed, xtype='standard')
×
670
        xprv = node.to_xprv()
×
671
        node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY)
×
672
        Logger.__init__(self)
×
673
        self.lnpeermgr = LNPeerManager(node_keypair, features=LNGOSSIP_FEATURES, config=self.config, lnwallet_or_lngossip=self)
×
674
        self.taskgroup = OldTaskGroup()
×
675
        self.unknown_ids = set()
×
676
        self._forwarding_gossip = []  # type: List[GossipForwardingMessage]
×
677
        self._last_gossip_batch_ts = 0  # type: int
×
678
        self._forwarding_gossip_lock = asyncio.Lock()
×
679
        self.gossip_request_semaphore = asyncio.Semaphore(5)
×
680
        # statistics
681
        self._num_chan_ann = 0
×
682
        self._num_node_ann = 0
×
683
        self._num_chan_upd = 0
×
684
        self._num_chan_upd_good = 0
×
685

686
    @property
1✔
687
    def features(self) -> 'LnFeatures':
1✔
688
        return self.lnpeermgr.features
×
689

690
    @property
1✔
691
    def network(self) -> Optional['Network']:
1✔
692
        return self.lnpeermgr.network
×
693

694
    @property
1✔
695
    def channel_db(self) -> 'ChannelDB':
1✔
696
        return self.network.channel_db if self.network else None
×
697

698
    def uses_trampoline(self) -> bool:
1✔
699
        return not bool(self.channel_db)
×
700

701
    async def main_loop(self):
1✔
702
        self.logger.info("starting taskgroup.")
×
703
        try:
×
704
            async with self.taskgroup as group:
×
705
                await group.spawn(asyncio.Event().wait)  # run forever (until cancel)
×
706
        except Exception as e:
×
707
            self.logger.exception("taskgroup died.")
×
708
        finally:
709
            self.logger.info("taskgroup stopped.")
×
710

711
    def start_network(self, network: 'Network'):
1✔
712
        asyncio.run_coroutine_threadsafe(self.main_loop(), get_asyncio_loop())
×
713
        self.lnpeermgr.start_network(network, maintain_random_peers=True)
×
714
        for coro in [
×
715
                self.maintain_db(),
716
                self._maintain_forwarding_gossip()
717
        ]:
718
            tg_coro = self.taskgroup.spawn(coro)
×
719
            asyncio.run_coroutine_threadsafe(tg_coro, get_asyncio_loop())
×
720

721
    async def stop(self):
1✔
722
        await self.lnpeermgr.stop()
×
723
        await self.taskgroup.cancel_remaining()
×
724

725
    async def maintain_db(self):
1✔
726
        await self.channel_db.data_loaded.wait()
×
727
        while True:
×
728
            if len(self.unknown_ids) == 0:
×
729
                def _maintain():
×
730
                    self.channel_db.prune_old_policies(self.max_age)
×
731
                    self.channel_db.prune_orphaned_channels()
×
732
                await asyncio.to_thread(_maintain)
×
733
            await asyncio.sleep(120)
×
734

735
    async def _maintain_forwarding_gossip(self):
1✔
736
        await self.channel_db.data_loaded.wait()
×
737
        await self.wait_for_sync()
×
738
        while True:
×
739
            async with self._forwarding_gossip_lock:
×
740
                self._forwarding_gossip = self.channel_db.get_forwarding_gossip_batch()
×
741
                self._last_gossip_batch_ts = int(time.time())
×
742
            self.logger.debug(f"{len(self._forwarding_gossip)} gossip messages available to forward")
×
743
            await asyncio.sleep(60)
×
744

745
    async def get_forwarding_gossip(self) -> tuple[List[GossipForwardingMessage], int]:
1✔
746
        async with self._forwarding_gossip_lock:
×
747
            return self._forwarding_gossip, self._last_gossip_batch_ts
×
748

749
    async def add_new_ids(self, ids: Iterable[bytes]):
1✔
750
        known = self.channel_db.get_channel_ids()
×
751
        new = set(ids) - set(known)
×
752
        self.unknown_ids.update(new)
×
753
        util.trigger_callback('unknown_channels', len(self.unknown_ids))
×
754
        util.trigger_callback('gossip_peers', self.lnpeermgr.num_peers())
×
755
        util.trigger_callback('ln_gossip_sync_progress')
×
756

757
    def get_ids_to_query(self) -> Sequence[bytes]:
1✔
758
        N = 500
×
759
        l = list(self.unknown_ids)
×
760
        self.unknown_ids = set(l[N:])
×
761
        util.trigger_callback('unknown_channels', len(self.unknown_ids))
×
762
        util.trigger_callback('ln_gossip_sync_progress')
×
763
        return l[0:N]
×
764

765
    def get_sync_progress_estimate(self) -> Tuple[Optional[int], Optional[int], Optional[int]]:
1✔
766
        """Estimates the gossip synchronization process and returns the number
767
        of synchronized channels, the total channels in the network and a
768
        rescaled percentage of the synchronization process."""
769
        if self.lnpeermgr.num_peers() == 0:
×
770
            return None, None, None
×
771
        nchans_with_0p, nchans_with_1p, nchans_with_2p = self.channel_db.get_num_channels_partitioned_by_policy_count()
×
772
        num_db_channels = nchans_with_0p + nchans_with_1p + nchans_with_2p
×
773
        num_nodes = self.channel_db.num_nodes
×
774
        num_nodes_associated_to_chans = max(len(self.channel_db._channels_for_node.keys()), 1)
×
775
        # some channels will never have two policies (only one is in gossip?...)
776
        # so if we have at least 1 policy for a channel, we consider that channel "complete" here
777
        current_est = num_db_channels - nchans_with_0p
×
778
        total_est = len(self.unknown_ids) + num_db_channels
×
779

780
        progress_chans = current_est / total_est if total_est and current_est else 0
×
781
        # consider that we got at least 10% of the node anns of node ids we know about
782
        progress_nodes = min((num_nodes / num_nodes_associated_to_chans) * 10, 1)
×
783
        progress = (progress_chans * 3 + progress_nodes) / 4  # weigh the channel progress higher
×
784
        # self.logger.debug(f"Sync process chans: {progress_chans} | Progress nodes: {progress_nodes} | "
785
        #                   f"Total progress: {progress} | NUM_NODES: {num_nodes} / {num_nodes_associated_to_chans}")
786
        progress_percent = (1.0 / 0.95 * progress) * 100
×
787
        progress_percent = min(progress_percent, 100)
×
788
        progress_percent = round(progress_percent)
×
789
        # take a minimal number of synchronized channels to get a more accurate
790
        # percentage estimate
791
        if current_est < 200:
×
792
            progress_percent = 0
×
793
        return current_est, total_est, progress_percent
×
794

795
    @ignore_exceptions
1✔
796
    @log_exceptions
1✔
797
    async def process_gossip(self, chan_anns, node_anns, chan_upds):
1✔
798
        # note: we run in the originating peer's TaskGroup, so we can safely raise here
799
        #       and disconnect only from that peer
800
        await self.channel_db.data_loaded.wait()
×
801

802
        # channel announcements
803
        def process_chan_anns():
×
804
            for payload in chan_anns:
×
805
                self.channel_db.verify_channel_announcement(payload)
×
806
            self.channel_db.add_channel_announcements(chan_anns)
×
807
        await run_in_thread(process_chan_anns)
×
808

809
        # node announcements
810
        def process_node_anns():
×
811
            for payload in node_anns:
×
812
                self.channel_db.verify_node_announcement(payload)
×
813
            self.channel_db.add_node_announcements(node_anns)
×
814
        await run_in_thread(process_node_anns)
×
815
        # channel updates
816
        categorized_chan_upds = await run_in_thread(partial(
×
817
            self.channel_db.add_channel_updates,
818
            chan_upds,
819
            max_age=self.max_age))
820
        orphaned = categorized_chan_upds.orphaned
×
821
        if orphaned:
×
822
            self.logger.info(f'adding {len(orphaned)} unknown channel ids')
×
823
            orphaned_ids = [c['short_channel_id'] for c in orphaned]
×
824
            await self.add_new_ids(orphaned_ids)
×
825

826
        self._num_chan_ann += len(chan_anns)
×
827
        self._num_node_ann += len(node_anns)
×
828
        self._num_chan_upd += len(chan_upds)
×
829
        self._num_chan_upd_good += len(categorized_chan_upds.good)
×
830

831
    def is_synced(self) -> bool:
1✔
832
        _, _, percentage_synced = self.get_sync_progress_estimate()
×
833
        if percentage_synced is not None and percentage_synced >= 100:
×
834
            return True
×
835
        return False
×
836

837
    async def wait_for_sync(self, times_to_check: int = 3):
1✔
838
        """Check if we have 100% sync progress `times_to_check` times in a row (because the
839
        estimate often jumps back after some seconds when doing initial sync)."""
840
        while True:
×
841
            if self.is_synced():
×
842
                times_to_check -= 1
×
843
                if times_to_check <= 0:
×
844
                    return
×
845
            await asyncio.sleep(10)
×
846
            # flush the gossip queue so we don't forward old gossip after sync is complete
847
            self.channel_db.get_forwarding_gossip_batch()
×
848

849
    def channels_for_peer(self, node_id: bytes) -> Dict[bytes, Channel]:
1✔
850
        return {}
×
851

852

853
class PaySession(Logger):
1✔
854

855
    # how long we wait for another htlc to resolve after receiving a failure for one sent htlc.
856
    TIMEOUT_WAIT_FOR_NEXT_RESOLVED_HTLC = 0.5
1✔
857

858
    def __init__(
1✔
859
            self,
860
            *,
861
            payment_hash: bytes,
862
            payment_secret: bytes,
863
            initial_trampoline_fee_level: int,
864
            invoice_features: int,
865
            r_tags,
866
            min_final_cltv_delta: int,  # delta for last node (typically from invoice)
867
            amount_to_pay: int,  # total payment amount final receiver will get
868
            invoice_pubkey: bytes,
869
            uses_trampoline: bool,  # whether sender uses trampoline or gossip
870
    ):
871
        assert payment_hash
1✔
872
        assert payment_secret
1✔
873
        self.payment_hash = payment_hash
1✔
874
        self.payment_secret = payment_secret
1✔
875
        self.payment_key = payment_hash + payment_secret
1✔
876
        Logger.__init__(self)
1✔
877

878
        self.invoice_features = LnFeatures(invoice_features)
1✔
879
        self.r_tags = r_tags
1✔
880
        self.min_final_cltv_delta = min_final_cltv_delta
1✔
881
        self.amount_to_pay = amount_to_pay
1✔
882
        self.invoice_pubkey = invoice_pubkey
1✔
883

884
        self.sent_htlcs_q = asyncio.Queue()  # type: asyncio.Queue[HtlcLog]
1✔
885
        self.start_time = time.time()
1✔
886

887
        self.uses_trampoline = uses_trampoline
1✔
888
        self.trampoline_fee_level = initial_trampoline_fee_level
1✔
889
        self.failed_trampoline_routes = []
1✔
890
        self.next_trampolines = dict() # node_id -> next_trampoline -> tuple
1✔
891
        self._sent_buckets = dict()  # psecret_bucket -> (amount_sent, amount_failed)
1✔
892

893
        self._amount_inflight = 0  # what we sent in htlcs (that receiver gets, without fees)
1✔
894
        self._nhtlcs_inflight = 0
1✔
895
        self.is_active = True  # is still trying to send new htlcs?
1✔
896

897
    def diagnostic_name(self):
1✔
898
        pkey = sha256(self.payment_key)
1✔
899
        return f"{self.payment_hash[:4].hex()}-{pkey[:2].hex()}"
1✔
900

901
    @property
1✔
902
    def number_htlcs_inflight(self) -> int:
1✔
903
        return self._nhtlcs_inflight
1✔
904

905
    def maybe_raise_trampoline_fee(self, htlc_log: HtlcLog):
1✔
906
        if htlc_log.trampoline_fee_level == self.trampoline_fee_level:
1✔
907
            self.trampoline_fee_level += 1
1✔
908
            self.failed_trampoline_routes = []
1✔
909
            self.logger.info(f'raising trampoline fee level {self.trampoline_fee_level}')
1✔
910
        else:
911
            self.logger.info(f'NOT raising trampoline fee level, already at {self.trampoline_fee_level}')
×
912

913
    def handle_failed_trampoline_htlc(self, *, node_id, htlc_log: HtlcLog, failure_msg: OnionRoutingFailure):
1✔
914
        # FIXME The trampoline nodes in the path are chosen randomly.
915
        #       Some of the errors might depend on how we have chosen them.
916
        #       Having more attempts is currently useful in part because of the randomness,
917
        #       instead we should give feedback to create_routes_for_payment.
918
        # Sometimes the trampoline node fails to send a payment and returns
919
        # TEMPORARY_CHANNEL_FAILURE, while it succeeds with a higher trampoline fee.
920
        if failure_msg.code in (
1✔
921
                OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT,
922
                OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON,
923
                OnionFailureCode.TEMPORARY_CHANNEL_FAILURE):
924
            # TODO: parse the node policy here (not returned by eclair yet)
925
            # TODO: erring node is always the first trampoline even if second
926
            #  trampoline demands more fees, we can't influence this
927
            self.maybe_raise_trampoline_fee(htlc_log)
1✔
928
        elif failure_msg.code in (
1✔
929
                OnionFailureCode.UNKNOWN_NEXT_PEER,
930
                OnionFailureCode.TEMPORARY_NODE_FAILURE):
931
            trampoline_route = htlc_log.route
1✔
932
            r = []
1✔
933
            for hop in trampoline_route:
1✔
934
                r.append(hop.end_node.hex())
1✔
935
                if hop.end_node == node_id:
1✔
936
                    # we break at the node sending the error, so that
937
                    # _choose_next_trampoline can discard the last item
938
                    break
1✔
939
            self.logger.info(f'failed trampoline route: {r}')
1✔
940
            if r not in self.failed_trampoline_routes:
1✔
941
                self.failed_trampoline_routes.append(r)
1✔
942
            else:
943
                pass  # maybe the route was reused between different MPP parts
1✔
944
            if failure_msg.code == OnionFailureCode.UNKNOWN_NEXT_PEER:
1✔
945
                self.next_trampolines[node_id] = decode_next_trampolines(failure_msg.data)
1✔
946
                self.logger.info(f'received {self.next_trampolines[node_id]=}')
1✔
947
        else:
948
            raise PaymentFailure(failure_msg.code_name())
1✔
949

950
    async def wait_for_one_htlc_to_resolve(self) -> HtlcLog:
1✔
951
        self.logger.info(f"waiting... amount_inflight={self._amount_inflight}. nhtlcs_inflight={self._nhtlcs_inflight}")
1✔
952
        htlc_log = await self.sent_htlcs_q.get()
1✔
953
        self._amount_inflight -= htlc_log.amount_msat
1✔
954
        self._nhtlcs_inflight -= 1
1✔
955
        if self._amount_inflight < 0 or self._nhtlcs_inflight < 0:
1✔
956
            raise Exception(f"amount_inflight={self._amount_inflight}, nhtlcs_inflight={self._nhtlcs_inflight}. both should be >= 0 !")
×
957
        return htlc_log
1✔
958

959
    def add_new_htlc(self, sent_htlc_info: SentHtlcInfo):
1✔
960
        self._nhtlcs_inflight += 1
1✔
961
        self._amount_inflight += sent_htlc_info.amount_receiver_msat
1✔
962
        if self._amount_inflight > self.amount_to_pay:  # safety belts
1✔
963
            raise Exception(f"amount_inflight={self._amount_inflight} > amount_to_pay={self.amount_to_pay}")
×
964
        shi = sent_htlc_info
1✔
965
        bkey = shi.payment_secret_bucket
1✔
966
        # if we sent MPP to a trampoline, add item to sent_buckets
967
        if self.uses_trampoline and shi.amount_msat != shi.bucket_msat:
1✔
968
            if bkey not in self._sent_buckets:
1✔
969
                self._sent_buckets[bkey] = (0, 0)
1✔
970
            amount_sent, amount_failed = self._sent_buckets[bkey]
1✔
971
            amount_sent += shi.amount_receiver_msat
1✔
972
            self._sent_buckets[bkey] = amount_sent, amount_failed
1✔
973

974
    def on_htlc_fail_get_fail_amt_to_propagate(self, sent_htlc_info: SentHtlcInfo) -> Optional[int]:
1✔
975
        shi = sent_htlc_info
1✔
976
        # check sent_buckets if we use trampoline
977
        bkey = shi.payment_secret_bucket
1✔
978
        if self.uses_trampoline and bkey in self._sent_buckets:
1✔
979
            amount_sent, amount_failed = self._sent_buckets[bkey]
1✔
980
            amount_failed += shi.amount_receiver_msat
1✔
981
            self._sent_buckets[bkey] = amount_sent, amount_failed
1✔
982
            if amount_sent != amount_failed:
1✔
983
                self.logger.info('bucket still active...')
1✔
984
                return None
1✔
985
            self.logger.info('bucket failed')
1✔
986
            return amount_sent
1✔
987
        # not using trampoline buckets
988
        return shi.amount_receiver_msat
1✔
989

990
    def get_outstanding_amount_to_send(self) -> int:
1✔
991
        return self.amount_to_pay - self._amount_inflight
1✔
992

993
    def can_be_deleted(self) -> bool:
1✔
994
        """Returns True iff finished sending htlcs AND all pending htlcs have resolved."""
995
        if self.is_active:
1✔
996
            return False
1✔
997
        # note: no one is consuming from sent_htlcs_q anymore
998
        nhtlcs_resolved = self.sent_htlcs_q.qsize()
1✔
999
        assert nhtlcs_resolved <= self._nhtlcs_inflight
1✔
1000
        return nhtlcs_resolved == self._nhtlcs_inflight
1✔
1001

1002

1003
class LNWallet(Logger):
1✔
1004

1005
    lnwatcher: Optional['LNWatcher']
1✔
1006
    MPP_EXPIRY = 120
1✔
1007
    TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 3  # seconds
1✔
1008
    PAYMENT_TIMEOUT = 120
1✔
1009
    MPP_SPLIT_PART_FRACTION = 0.2
1✔
1010
    MPP_SPLIT_PART_MINAMT_MSAT = 5_000_000
1✔
1011

1012
    def __init__(self, wallet: 'Abstract_Wallet', xprv, *, features: LnFeatures = None):
1✔
1013
        self.wallet = wallet
1✔
1014
        self.config = wallet.config
1✔
1015
        self.db = wallet.db
1✔
1016
        self.instantiation_timestamp = int(time.time())
1✔
1017
        self.node_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NODE_KEY)
1✔
1018
        self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey
1✔
1019
        self.static_payment_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_BASE)
1✔
1020
        self.payment_secret_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_SECRET_KEY).privkey
1✔
1021
        self.funding_root_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.FUNDING_ROOT_KEY)
1✔
1022
        Logger.__init__(self)
1✔
1023
        if features is None:
1✔
1024
            features = LNWALLET_FEATURES
1✔
1025
            if self.config.OPEN_ZEROCONF_CHANNELS:
1✔
1026
                features |= LnFeatures.OPTION_ZEROCONF_OPT
×
1027
            if self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS or self.config.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS:
1✔
1028
                features |= LnFeatures.OPTION_ONION_MESSAGE_OPT
×
1029
            if self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS and self.config.LIGHTNING_USE_GOSSIP:
1✔
1030
                features |= LnFeatures.GOSSIP_QUERIES_OPT  # signal we have gossip to fetch
×
1031
        self.lock = threading.RLock()
1✔
1032
        self.lnpeermgr = LNPeerManager(self.node_keypair, features=features, config=self.config, lnwallet_or_lngossip=self)
1✔
1033
        self.taskgroup = OldTaskGroup()
1✔
1034
        self.lnwatcher = LNWatcher(self)
1✔
1035
        self.lnrater: LNRater = None
1✔
1036
        # "RHASH:direction" -> amount_msat, status, min_final_cltv_delta, expiry_delay, creation_ts, invoice_features
1037
        self.payment_info = self.db.get_dict('lightning_payments')  # type: dict[str, Tuple[Optional[int], int, int, int, int, int]]
1✔
1038
        self._preimages = self.db.get_dict('lightning_preimages')   # RHASH -> (preimage, is_public)
1✔
1039
        self._bolt11_cache = {}
1✔
1040
        # note: this sweep_address is only used as fallback; as it might result in address-reuse
1041
        self.logs = defaultdict(list)  # type: Dict[str, List[HtlcLog]]  # key is RHASH  # (not persisted)
1✔
1042
        # used in tests
1043
        self.enable_htlc_settle = True
1✔
1044
        self.enable_htlc_forwarding = True
1✔
1045

1046
        # note: accessing channels (besides simple lookup) needs self.lock!
1047
        self._channels = {}  # type: Dict[bytes, Channel]
1✔
1048
        channels = self.db.get_dict("channels")
1✔
1049
        for channel_id, c in random_shuffled_copy(channels.items()):
1✔
1050
            self._channels[bfh(channel_id)] = chan = Channel(c, lnworker=self)
1✔
1051
            self.wallet.set_reserved_addresses_for_chan(chan, reserved=True)
1✔
1052

1053
        self._channel_backups = {}  # type: Dict[bytes, ChannelBackup]
1✔
1054
        # order is important: imported should overwrite onchain
1055
        for name in ["onchain_channel_backups", "imported_channel_backups"]:
1✔
1056
            channel_backups = self.db.get_dict(name)
1✔
1057
            for channel_id, storage in channel_backups.items():
1✔
1058
                if isinstance(storage, str):
1✔
1059
                    storage = ImportedChannelBackupStorage.from_bytes(bytes.fromhex(storage))
1✔
1060
                assert isinstance(storage, (OnchainChannelBackupStorage, ImportedChannelBackupStorage))
1✔
1061
                self._channel_backups[bfh(channel_id)] = cb = ChannelBackup(storage, lnworker=self)
1✔
1062
                self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
1✔
1063

1064
        self._paysessions = dict()                      # type: Dict[bytes, PaySession]
1✔
1065
        self.sent_htlcs_info = dict()                   # type: Dict[SentHtlcKey, SentHtlcInfo]
1✔
1066
        self.received_mpp_htlcs = self.db.get_dict('received_mpp_htlcs')   # type: Dict[str, ReceivedMPPStatus]  # payment_key -> ReceivedMPPStatus
1✔
1067
        self._channel_sending_capacity_lock = asyncio.Lock()
1✔
1068

1069
        # detect inflight payments
1070
        self.inflight_payments = set()  # type: set[str]  # (not persisted) keys of invoices that are in PR_INFLIGHT state
1✔
1071
        for payment_hash in self.get_payments_with_unresolved_sent_htlcs():
1✔
1072
            self.set_invoice_status(payment_hash.hex(), PR_INFLIGHT)
×
1073

1074
        # payment forwarding
1075
        self.active_forwardings = self.db.get_dict('active_forwardings')    # type: Dict[str, List[str]]        # Dict: payment_key -> list of htlc_keys
1✔
1076
        self.forwarding_failures = self.db.get_dict('forwarding_failures')  # type: Dict[str, Tuple[str, str]]  # Dict: payment_key -> (error_bytes, error_message)
1✔
1077
        self.downstream_to_upstream_htlc = {}                               # type: Dict[str, str]              # Dict: htlc_key -> htlc_key (not persisted)
1✔
1078

1079
        # k: payment_hashes of htlcs that we should not expire even if we don't know the preimage
1080
        # v: If `None` the htlcs won't get expired and potentially get timed out in a force close.
1081
        #    Note: it might not be safe to release the preimage shortly before expiry as this would allow the
1082
        #          remote node to ignore our fulfill_htlc, wait until expiry and try to time out the htlc onchain
1083
        #          in a fee race against us and then use our released preimage to fulfill upstream.
1084
        # v: If `int`: Overwrites `MIN_FINAL_CLTV_DELTA_ACCEPTED` in htlc switch and allows to set custom
1085
        #              expiration delta. The htlcs will get expired if their blocks left to expiry are
1086
        #              below the specified expiration delta.
1087
        # htlcs will get settled as soon as the preimage becomes available
1088
        self.dont_expire_htlcs = self.db.get_dict('dont_expire_htlcs')      # type: Dict[str, Optional[int]]
1✔
1089

1090
        # k: payment_hash of payments for which we don't want to release the preimage, no matter
1091
        # how close to expiry. Doesn't prevent htlcs from getting expired or failed if there is no
1092
        # preimage available. Might be used in combination with dont_expire_htlcs.
1093
        self.dont_settle_htlcs = self.db.get_dict('dont_settle_htlcs')  # type: Dict[str, None]
1✔
1094

1095
        # payment_hash -> callback:
1096
        self.hold_invoice_callbacks = {}                # type: Dict[bytes, Callable[[bytes], Awaitable[None]]]
1✔
1097
        self._payment_bundles_pkey_to_canon = {}       # type: Dict[bytes, bytes]            # TODO: persist
1✔
1098
        self._payment_bundles_canon_to_pkeylist = {}   # type: Dict[bytes, Sequence[bytes]]  # TODO: persist
1✔
1099

1100
        self.nostr_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.NOSTR_KEY)
1✔
1101
        self.swap_manager = SwapManager(wallet=self.wallet, lnworker=self)
1✔
1102
        self.onion_message_manager = OnionMessageManager(self)
1✔
1103
        self.subscribe_to_channels()
1✔
1104

1105
    def subscribe_to_channels(self):
1✔
1106
        for chan in self.channels.values():
1✔
1107
            self.lnwatcher.add_channel(chan)
1✔
1108
        for cb in self.channel_backups.values():
1✔
1109
            self.lnwatcher.add_channel(cb)
1✔
1110

1111
    def has_deterministic_node_id(self) -> bool:
1✔
1112
        return bool(self.db.get('lightning_xprv'))
1✔
1113

1114
    def can_have_recoverable_channels(self) -> bool:
1✔
1115
        return (self.has_deterministic_node_id()
1✔
1116
                and not self.config.LIGHTNING_LISTEN)
1117

1118
    def has_recoverable_channels(self) -> bool:
1✔
1119
        """Whether *future* channels opened by this wallet would be recoverable
1120
        from seed (via putting OP_RETURN outputs into funding txs).
1121
        """
1122
        return (self.can_have_recoverable_channels()
1✔
1123
                and self.config.LIGHTNING_USE_RECOVERABLE_CHANNELS)
1124

1125
    def has_anchor_channels(self) -> bool:
1✔
1126
        """Returns True if any active channel is an anchor channel."""
1127
        return any(chan.has_anchors() and not chan.is_closed()
1✔
1128
                   for chan in self.channels.values())
1129

1130
    def get_lightning_startup_warnings(self) -> Sequence['WalletWarning']:
1✔
1131
        from .wallet import WalletWarning
×
1132
        warnings = []
×
1133
        if any(isinstance(cb.cb, ImportedChannelBackupStorage) and cb.cb.backup_version == 0 for cb in self.channel_backups.values()):
×
1134
            warnings.append(WalletWarning(
×
1135
                key='ln_chan_backup_pre_v1_gh-8536',
1136
                title=_('Outdated channel backups') + ' [gh-8536]',
1137
                show_once=False,  # show on every startup
1138
                message=''.join([
1139
                    _("This wallet contains old (v0) channel backups that can only be used to recover channel funds "
1140
                      "in some scenarios. They were exported with an older version of Electrum."), ' ',
1141
                    _("Please import new backups, exported by the wallet these channels belong to."),
1142
                ])))
1143
        if not self.has_deterministic_node_id() and self.has_anchor_channels():
×
1144
            # backups exported before we started storing the payment_basepoint privkey
1145
            # (backup v3) cannot sweep the to_remote output of an anchor channel
1146
            warnings.append(WalletWarning(
×
1147
                key='ln_chan_backups_pre_v3_gh-10852-1',
1148
                title=_('Outdated channel backups') + ' [gh-10852-1]',
1149
                show_once=True,
1150
                message=''.join([
1151
                    _("The Lightning channels of this wallet cannot be recovered from seed."), ' ',
1152
                    _("Channel backups that were exported with an older version of Electrum "
1153
                      "cannot be used to request a force close of these channels."), '\n\n',
1154
                    _("Please export new channel backups and store them in a safe place."),
1155
                ])))
1156
        if any(not cb.can_sweep_their_ctx_to_remote() for cb in self.channel_backups.values()):
×
1157
            warnings.append(WalletWarning(
×
1158
                key='ln_chan_backups_pre_v3_gh-10852-2',
1159
                title=_('Unusable channel backups') + ' [gh-10852-2]',
1160
                show_once=False,  # show on every startup
1161
                message=''.join([
1162
                    _("This wallet contains old (v2) channel backups that cannot be used to request a force close, "
1163
                      "because they were exported with an older version of Electrum."), ' ',
1164
                    _("Please import new backups, exported by the wallet these channels belong to."), '\n\n',
1165
                    _("If you have lost access to that wallet, please open an issue on GitHub."),
1166
                ])))
1167
        return warnings
×
1168

1169
    @property
1✔
1170
    def features(self) -> 'LnFeatures':
1✔
1171
        return self.lnpeermgr.features
1✔
1172

1173
    @property
1✔
1174
    def network(self) -> Optional['Network']:
1✔
1175
        return self.lnpeermgr.network
1✔
1176

1177
    @property
1✔
1178
    def channel_db(self) -> 'ChannelDB':
1✔
1179
        return self.network.channel_db if self.network else None
1✔
1180

1181
    def uses_trampoline(self) -> bool:
1✔
1182
        return not bool(self.channel_db)
1✔
1183

1184
    @property
1✔
1185
    def channels(self) -> Mapping[bytes, Channel]:
1✔
1186
        """Returns a read-only copy of channels."""
1187
        with self.lock:
1✔
1188
            return self._channels.copy()
1✔
1189

1190
    @property
1✔
1191
    def channel_backups(self) -> Mapping[bytes, ChannelBackup]:
1✔
1192
        """Returns a read-only copy of channels."""
1193
        with self.lock:
1✔
1194
            return self._channel_backups.copy()
1✔
1195

1196
    def get_channel_objects(self) -> Mapping[bytes, AbstractChannel]:
1✔
1197
        r = self.channel_backups
×
1198
        r.update(self.channels)
×
1199
        return r
×
1200

1201
    def get_channel_by_id(self, channel_id: bytes) -> Optional[Channel]:
1✔
1202
        return self._channels.get(channel_id, None)
1✔
1203

1204
    def diagnostic_name(self):
1✔
1205
        return self.wallet.diagnostic_name()
1✔
1206

1207
    @ignore_exceptions
1✔
1208
    @log_exceptions
1✔
1209
    async def sync_with_remote_watchtower(self):
1✔
1210
        self.watchtower_ctns = {}
1✔
1211
        while True:
1✔
1212
            # periodically poll if the user updated 'watchtower_url'
1213
            await asyncio.sleep(5)
1✔
1214
            watchtower_url = self.config.WATCHTOWER_CLIENT_URL
×
1215
            if not watchtower_url:
×
1216
                continue
×
1217
            parsed_url = urllib.parse.urlparse(watchtower_url)
×
1218
            if not (parsed_url.scheme == 'https' or is_private_netaddress(parsed_url.hostname)):
×
1219
                self.logger.warning(f"got watchtower URL for remote tower but we won't use it! "
×
1220
                                    f"can only use HTTPS (except if private IP): not using {watchtower_url!r}")
1221
                continue
×
1222
            # try to sync with the remote watchtower
1223
            try:
×
1224
                async with make_aiohttp_session(proxy=self.network.proxy) as session:
×
1225
                    watchtower = JsonRPCClient(session, watchtower_url)
×
1226
                    watchtower.add_method('get_ctn')
×
1227
                    watchtower.add_method('add_sweep_tx')
×
1228
                    for chan in self.channels.values():
×
1229
                        await self.sync_channel_with_watchtower(chan, watchtower)
×
1230
            except aiohttp.ClientError:
×
1231
                self.logger.info(f'could not contact remote watchtower {watchtower_url}')
×
1232

1233
    def get_watchtower_ctn(self, channel_point):
1✔
1234
        return self.watchtower_ctns.get(channel_point)
×
1235

1236
    async def sync_channel_with_watchtower(self, chan: Channel, watchtower):
1✔
1237
        outpoint = chan.funding_outpoint.to_str()
×
1238
        addr = chan.get_funding_address()
×
1239
        current_ctn = chan.get_oldest_unrevoked_ctn(REMOTE)
×
1240
        watchtower_ctn = await watchtower.get_ctn(outpoint, addr)
×
1241
        for ctn in range(watchtower_ctn + 1, current_ctn):
×
1242
            sweeptxs = chan.create_sweeptxs_for_watchtower(ctn)
×
1243
            for tx in sweeptxs:
×
1244
                await watchtower.add_sweep_tx(outpoint, ctn, tx.inputs()[0].prevout.to_str(), tx.serialize())
×
1245
            self.watchtower_ctns[outpoint] = ctn
×
1246

1247
    async def main_loop(self):
1✔
1248
        self.logger.info("starting taskgroup.")
1✔
1249
        try:
1✔
1250
            async with self.taskgroup as group:
1✔
1251
                await group.spawn(asyncio.Event().wait)  # run forever (until cancel)
1✔
1252
        except Exception as e:
1✔
1253
            self.logger.exception("taskgroup died.")
×
1254
        finally:
1255
            self.logger.info("taskgroup stopped.")
1✔
1256

1257
    def start_network(self, network: 'Network'):
1✔
1258
        assert network.config is self.config
1✔
1259
        asyncio.run_coroutine_threadsafe(self.main_loop(), get_asyncio_loop())
1✔
1260
        self.lnpeermgr.start_network(network, listen=True)
1✔
1261
        self.lnwatcher.start_network(network)
1✔
1262
        self.swap_manager.start_network(network)
1✔
1263
        self.lnrater = LNRater(self, network)
1✔
1264
        self.onion_message_manager.start_network(network=network)
1✔
1265

1266
        for coro in [
1✔
1267
                self.reestablish_peers_and_channels(),
1268
                self.sync_with_remote_watchtower(),
1269
        ]:
1270
            tg_coro = self.taskgroup.spawn(coro)
1✔
1271
            asyncio.run_coroutine_threadsafe(tg_coro, get_asyncio_loop())
1✔
1272

1273
    async def stop(self):
1✔
1274
        self.lnpeermgr.stopping_soon = True
1✔
1275
        if self.lnpeermgr.listen_server:  # stop accepting new peers
1✔
1276
            self.lnpeermgr.listen_server.close()
1✔
1277
        async with ignore_after(self.TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS):
1✔
1278
            await self.wait_for_received_pending_htlcs_to_get_removed()
1✔
1279
        await self.lnpeermgr.stop()
1✔
1280
        if self.lnwatcher:
1✔
1281
            await self.lnwatcher.stop()
1✔
1282
            self.lnwatcher = None
1✔
1283
        if self.swap_manager and self.swap_manager.network:  # may not be present in tests
1✔
1284
            await self.swap_manager.stop()
1✔
1285
        if self.onion_message_manager:
1✔
1286
            await self.onion_message_manager.stop()
1✔
1287
        await self.taskgroup.cancel_remaining()
1✔
1288

1289
    async def wait_for_received_pending_htlcs_to_get_removed(self):
1✔
1290
        assert self.lnpeermgr.stopping_soon is True
1✔
1291
        # We try to fail pending MPP HTLCs, and wait a bit for them to get removed.
1292
        # Note: even without MPP, if we just failed/fulfilled an HTLC, it is good
1293
        #       to wait a bit for it to become irrevocably removed.
1294
        # Note: we don't wait for *all htlcs* to get removed, only for those
1295
        #       that we can already fail/fulfill. e.g. forwarded htlcs cannot be removed
1296
        async with OldTaskGroup() as group:
1✔
1297
            for peer in self.lnpeermgr.peers.values():
1✔
1298
                if peer.is_initialized():
1✔
1299
                    await group.spawn(peer.wait_one_htlc_switch_iteration())
1✔
1300
        while True:
1✔
1301
            if all(not peer.received_htlcs_pending_removal for peer in self.lnpeermgr.peers.values()):
1✔
1302
                break
1✔
1303
            async with OldTaskGroup(wait=any) as group:
×
1304
                for peer in self.lnpeermgr.peers.values():
×
1305
                    await group.spawn(peer.received_htlc_removed_event.wait())
×
1306

1307
    def get_payments(
1✔
1308
        self, *,
1309
        status: Optional[str] = None,
1310
        direction: Optional[lnutil.Direction] = None,
1311
    ) -> Mapping[bytes, List[HTLCWithStatus]]:
1312
        out = defaultdict(list)
1✔
1313
        for chan in self.channels.values():
1✔
1314
            d = chan.get_payments(status=status, direction=direction)
1✔
1315
            for payment_hash, plist in d.items():
1✔
1316
                out[payment_hash] += plist
1✔
1317
        return out
1✔
1318

1319
    def has_unresolved_sent_htlcs(self, payment_hash: bytes) -> bool:
1✔
1320
        """Returns whether there are htlcs we sent for this payment that have neither
1321
        been failed nor fulfilled yet, i.e. the receiver might still take the money.
1322
        """
1323
        return payment_hash in self.get_payments_with_unresolved_sent_htlcs()
1✔
1324

1325
    def get_payments_with_unresolved_sent_htlcs(self) -> Set[bytes]:
1✔
1326
        # set of payment hashes
1327
        out = set()
1✔
1328
        for chan in self.channels.values():
1✔
1329
            if chan.is_redeemed():
1✔
1330
                continue  # skip channel
1✔
1331
            for htlc in chan.hm.get_all_not_irrevocably_removed_htlcs(htlc_proposer=LOCAL):
1✔
1332
                out.add(htlc.payment_hash)
1✔
1333
        return out
1✔
1334

1335
    def get_payment_value(
1✔
1336
            self, sent_info: Optional['PaymentInfo'],
1337
            plist: List[HTLCWithStatus]
1338
    ) -> Tuple[PaymentDirection, int, Optional[int], int]:
1339
        """ fee_msat is included in amount_msat"""
1340
        assert plist
1✔
1341
        amount_msat = sum(int(x.direction) * x.htlc.amount_msat for x in plist)
1✔
1342
        if all(x.direction == SENT for x in plist):
1✔
1343
            direction = PaymentDirection.SENT
1✔
1344
            fee_msat = (- sent_info.amount_msat - amount_msat) if sent_info else None
1✔
1345
        elif all(x.direction == RECEIVED for x in plist):
1✔
1346
            direction = PaymentDirection.RECEIVED
1✔
1347
            fee_msat = None
1✔
1348
        elif amount_msat < 0:
×
1349
            direction = PaymentDirection.SELF_PAYMENT
×
1350
            fee_msat = - amount_msat
×
1351
        else:
1352
            direction = PaymentDirection.FORWARDING
×
1353
            fee_msat = - amount_msat
×
1354
        timestamp = min([htlc_with_status.htlc.timestamp for htlc_with_status in plist])
1✔
1355
        return direction, amount_msat, fee_msat, timestamp
1✔
1356

1357
    def get_lightning_history(self) -> Dict[str, LightningHistoryItem]:
1✔
1358
        """
1359
        side effect: sets defaults labels
1360
        note that the result is not ordered
1361
        """
1362
        out = {}
1✔
1363
        for payment_hash, plist in self.get_payments(status='settled').items():
1✔
1364
            if len(plist) == 0:
1✔
1365
                continue
×
1366
            key = payment_hash.hex()
1✔
1367
            sent_info = self.get_payment_info(payment_hash, direction=SENT)
1✔
1368
            # note: just after successfully paying an invoice using MPP, amount and fee values might be shifted
1369
            #       temporarily: the amount only considers 'settled' htlcs (see plist above), but we might also
1370
            #       have some inflight htlcs still. Until all relevant htlcs settle, the amount will be lower than
1371
            #       expected and the fee higher (the inflight htlcs will be effectively counted as fees).
1372
            direction, amount_msat, fee_msat, timestamp = self.get_payment_value(sent_info, plist)
1✔
1373
            label = self.wallet.get_label_for_rhash(key)
1✔
1374
            if not label and direction == PaymentDirection.FORWARDING:
1✔
1375
                label = _('Forwarding')
×
1376
            preimage = self.get_preimage(payment_hash).hex()
1✔
1377
            group_id = self.swap_manager.get_group_id_for_payment_hash(payment_hash)
1✔
1378
            item = LightningHistoryItem(
1✔
1379
                type='payment',
1380
                payment_hash=payment_hash.hex(),
1381
                preimage=preimage,
1382
                amount_msat=amount_msat,
1383
                fee_msat=fee_msat,
1384
                group_id=group_id,
1385
                timestamp=timestamp or 0,
1386
                label=label,
1387
                direction=direction,
1388
            )
1389
            out[payment_hash.hex()] = item
1✔
1390
        now = int(time.time())
1✔
1391
        for chan in itertools.chain(self.channels.values(), self.channel_backups.values()):  # type: AbstractChannel
1✔
1392
            item = chan.get_funding_height()
1✔
1393
            if item is None:
1✔
1394
                continue
×
1395
            funding_txid, funding_height, funding_timestamp = item
1✔
1396
            label = _('Open channel') + ' ' + chan.get_id_for_log()
1✔
1397
            self.wallet.set_default_label(funding_txid, label)
1✔
1398
            self.wallet.set_group_label(funding_txid, label)
1✔
1399
            item = LightningHistoryItem(
1✔
1400
                type='channel_opening',
1401
                label=label,
1402
                group_id=funding_txid,
1403
                timestamp=funding_timestamp or now,
1404
                amount_msat=chan.balance(LOCAL, ctn=0),
1405
                fee_msat=None,
1406
                payment_hash=None,
1407
                preimage=None,
1408
                direction=None,
1409
            )
1410
            out[funding_txid] = item
1✔
1411
            item = chan.get_closing_height()
1✔
1412
            if item is None:
1✔
1413
                continue
×
1414
            closing_txid, closing_height, closing_timestamp = item
1✔
1415
            label = _('Close channel') + ' ' + chan.get_id_for_log()
1✔
1416
            self.wallet.set_default_label(closing_txid, label)
1✔
1417
            self.wallet.set_group_label(closing_txid, label)
1✔
1418
            item = LightningHistoryItem(
1✔
1419
                type='channel_closing',
1420
                label=label,
1421
                group_id=closing_txid,
1422
                timestamp=closing_timestamp or now,
1423
                amount_msat=-chan.balance(LOCAL),
1424
                fee_msat=None,
1425
                payment_hash=None,
1426
                preimage=None,
1427
                direction=None,
1428
            )
1429
            out[closing_txid] = item
1✔
1430

1431
        # sanity check
1432
        balance_msat = sum([x.amount_msat for x in out.values()])
1✔
1433
        lb = sum(chan.balance(LOCAL) if not chan.is_closed_or_closing() else 0
1✔
1434
                 for chan in self.channels.values())
1435
        if balance_msat != lb:
1✔
1436
            # this typically happens when a channel is recently force closed
1437
            self.logger.info(f'get_lightning_history: balance mismatch {balance_msat - lb}')
×
1438
        return out
1✔
1439

1440
    def get_groups_for_onchain_history(self) -> Dict[str, str]:
1✔
1441
        """
1442
        returns dict: txid -> group_id
1443
        side effect: sets default labels
1444
        """
1445
        groups = {}
1✔
1446
        # add funding events
1447
        for chan in itertools.chain(self.channels.values(), self.channel_backups.values()):  # type: AbstractChannel
1✔
1448
            item = chan.get_funding_height()
1✔
1449
            if item is None:
1✔
1450
                continue
×
1451
            funding_txid, funding_height, funding_timestamp = item
1✔
1452
            groups[funding_txid] = funding_txid
1✔
1453
            item = chan.get_closing_height()
1✔
1454
            if item is None:
1✔
1455
                continue
×
1456
            closing_txid, closing_height, closing_timestamp = item
1✔
1457
            groups[closing_txid] = closing_txid
1✔
1458

1459
        d = self.swap_manager.get_groups_for_onchain_history()
1✔
1460
        for txid, v in d.items():
1✔
1461
            group_id = v['group_id']
×
1462
            label = v.get('label')
×
1463
            group_label = v.get('group_label') or label
×
1464
            groups[txid] = group_id
×
1465
            if label:
×
1466
                self.wallet.set_default_label(txid, label)
×
1467
            if group_label:
×
1468
                self.wallet.set_group_label(group_id, group_label)
×
1469

1470
        return groups
1✔
1471

1472
    def channel_peers(self) -> List[bytes]:
1✔
1473
        node_ids = [chan.node_id for chan in self.channels.values() if not chan.is_closed()]
×
1474
        return node_ids
×
1475

1476
    def channels_for_peer(self, node_id: bytes) -> Dict[bytes, Channel]:
1✔
1477
        assert type(node_id) is bytes
1✔
1478
        return {chan_id: chan for (chan_id, chan) in self.channels.items()
1✔
1479
                if chan.node_id == node_id}
1480

1481
    def channel_state_changed(self, chan: Channel):
1✔
1482
        if type(chan) is Channel:
1✔
1483
            self.save_channel(chan)
1✔
1484
        self.clear_invoices_cache()
1✔
1485
        if chan._state == ChannelState.REDEEMED:
1✔
1486
            self.maybe_cleanup_mpp(chan)
1✔
1487
        util.trigger_callback('channel', self.wallet, chan)
1✔
1488

1489
    def save_channel(self, chan: Channel):
1✔
1490
        assert type(chan) is Channel
1✔
1491
        if chan.config[REMOTE].next_per_commitment_point == chan.config[REMOTE].current_per_commitment_point:
1✔
1492
            raise Exception("Tried to save channel with next_point == current_point, this should not happen")
×
1493
        self.wallet.save_db()
1✔
1494
        util.trigger_callback('channel', self.wallet, chan)
1✔
1495

1496
    def channel_by_txo(self, txo: str) -> Optional[AbstractChannel]:
1✔
1497
        for chan in self.channels.values():
1✔
1498
            if chan.funding_outpoint.to_str() == txo:
1✔
1499
                return chan
1✔
1500
        for chan in self.channel_backups.values():
1✔
1501
            if chan.funding_outpoint.to_str() == txo:
1✔
1502
                return chan
1✔
1503
        return None
×
1504

1505
    async def handle_onchain_state(self, chan: Channel):
1✔
1506
        if self.network is None:
1✔
1507
            # network not started yet
1508
            return
1✔
1509

1510
        if type(chan) is ChannelBackup:
1✔
1511
            util.trigger_callback('channel', self.wallet, chan)
1✔
1512
            return
1✔
1513

1514
        if (chan.get_state() in (ChannelState.OPEN, ChannelState.SHUTDOWN)
1✔
1515
                and chan.should_be_closed_due_to_expiring_htlcs(self.wallet.adb.get_local_height())):
1516
            self.logger.info(f"force-closing due to expiring htlcs")
×
1517
            await self.schedule_force_closing(chan.channel_id)
×
1518

1519
        elif chan.get_state() == ChannelState.FUNDED:
1✔
1520
            peer = self.lnpeermgr.get_peer_by_pubkey(chan.node_id)
1✔
1521
            if peer and peer.is_initialized() and chan.peer_state == PeerState.GOOD:
1✔
1522
                peer.send_channel_ready(chan)
1✔
1523

1524
        elif chan.get_state() == ChannelState.OPEN:
1✔
1525
            peer = self.lnpeermgr.get_peer_by_pubkey(chan.node_id)
1✔
1526
            if peer and peer.is_initialized() and chan.peer_state == PeerState.GOOD:
1✔
1527
                peer.maybe_update_fee(chan)
1✔
1528
                peer.maybe_send_announcement_signatures(chan)
1✔
1529

1530
        elif chan.get_state() == ChannelState.FORCE_CLOSING:
1✔
1531
            force_close_tx = chan.force_close_tx()
1✔
1532
            txid = force_close_tx.txid()
1✔
1533
            height = self.lnwatcher.adb.get_tx_height(txid).height()
1✔
1534
            if height == TX_HEIGHT_LOCAL:
1✔
1535
                self.logger.info('REBROADCASTING CLOSING TX')
1✔
1536
                await self.network.try_broadcasting(force_close_tx, 'force-close')
1✔
1537

1538
    def get_peer_by_static_jit_scid_alias(self, scid_alias: bytes) -> Optional[Peer]:
1✔
1539
        for nodeid, peer in self.lnpeermgr.peers.items():
×
1540
            if scid_alias == self._scid_alias_of_node(nodeid):
×
1541
                return peer
×
1542
        return None
×
1543

1544
    def _scid_alias_of_node(self, nodeid: bytes) -> bytes:
1✔
1545
        # scid alias for just-in-time channels
1546
        return sha256(b'Electrum' + nodeid)[0:8]
×
1547

1548
    def get_static_jit_scid_alias(self) -> bytes:
1✔
1549
        return self._scid_alias_of_node(self.node_keypair.pubkey)
×
1550

1551
    @log_exceptions
1✔
1552
    async def open_channel_just_in_time(
1✔
1553
        self,
1554
        *,
1555
        next_peer: Peer,
1556
        next_amount_msat_htlc: int,
1557
        next_cltv_abs: int,
1558
        payment_hash: bytes,
1559
        next_onion: OnionPacket,
1560
    ) -> str:
1561
        assert self.config.OPEN_ZEROCONF_CHANNELS
1✔
1562
        # if an exception is raised during negotiation, we raise an OnionRoutingFailure.
1563
        # this will cancel the incoming HTLC
1564

1565
        next_chan: Optional[Channel] = None
1✔
1566
        # prevent settling the htlc until the channel opening was successful so we can fail it if needed
1567
        self.dont_settle_htlcs[payment_hash.hex()] = None
1✔
1568
        try:
1✔
1569
            assert self.config.ZEROCONF_CHANNEL_SIZE_PERCENT >= 120, "ZEROCONF_CHANNEL_SIZE_PERCENT below min of 120%"
1✔
1570
            assert self.config.ZEROCONF_OPENING_FEE_PPM >= 0, f"invalid {self.config.ZEROCONF_OPENING_FEE_PPM=}"
1✔
1571
            funding_sat = (self.config.ZEROCONF_CHANNEL_SIZE_PERCENT * (next_amount_msat_htlc // 1000)) // 100
1✔
1572
            password = self.wallet.get_unlocked_password() if self.wallet.has_password() else None
1✔
1573
            channel_opening_base_fee_msat = (next_amount_msat_htlc * self.config.ZEROCONF_OPENING_FEE_PPM) // 1_000_000
1✔
1574
            if channel_opening_base_fee_msat // 1000 < self.config.ZEROCONF_MIN_OPENING_FEE:
1✔
1575
                self.logger.info(
×
1576
                    f'rejecting JIT channel: {(channel_opening_base_fee_msat // 1000)=} < {self.config.ZEROCONF_MIN_OPENING_FEE=}'
1577
                )
1578
                raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'payment too low')
×
1579
            next_chan, funding_tx = await self.open_channel_with_peer(
1✔
1580
                next_peer, funding_sat,
1581
                push_sat=0,
1582
                zeroconf=True,
1583
                public=False,
1584
                opening_base_fee_msat=channel_opening_base_fee_msat,
1585
                password=password,
1586
            )
1587
            async def wait_for_channel():
1✔
1588
                while not next_chan.is_open():
1✔
1589
                    await asyncio.sleep(1)
×
1590
            await util.wait_for2(wait_for_channel(), LN_P2P_NETWORK_TIMEOUT)
1✔
1591
            self.logger.info(f'JIT channel is open (will forward htlc and await preimage now)')
1✔
1592
            self.logger.info(f'channel opening fee (sats): {channel_opening_base_fee_msat//1000} + {funding_tx.get_fee()} mining fee')
1✔
1593
            next_amount_msat_htlc -= channel_opening_base_fee_msat + funding_tx.get_fee() * 1000
1✔
1594
            if next_amount_msat_htlc < 1_000:
1✔
1595
                self.logger.info(f'rejecting JIT channel: payment too low after deducting mining fees')
×
1596
                raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'payment too low after deducting mining fees')
×
1597
            # fixme: some checks are missing
1598
            htlc = next_peer.send_htlc(
1✔
1599
                chan=next_chan,
1600
                payment_hash=payment_hash,
1601
                amount_msat=next_amount_msat_htlc,
1602
                cltv_abs=next_cltv_abs,
1603
                onion=next_onion)
1604
            async def wait_for_preimage():
1✔
1605
                while self.get_preimage(payment_hash) is None:
1✔
1606
                    await asyncio.sleep(1)
1✔
1607
            await util.wait_for2(wait_for_preimage(), LN_P2P_NETWORK_TIMEOUT)
1✔
1608

1609
            # We have been paid and can broadcast.
1610
            # Channel providers should run their own, trusted Electrum server as
1611
            # we could lose funds here if the server broadcasts the tx but omits it from us
1612
            first_broadcast_ts = time.time()
1✔
1613
            while time.time() - first_broadcast_ts < ZEROCONF_TIMEOUT * 0.75:
1✔
1614
                if await self.network.try_broadcasting(funding_tx, "jit channel funding"):
1✔
1615
                    break
1✔
1616
                await asyncio.sleep(30)
×
1617
                # we cannot rely on success of try_broadcasting to determine broadcasting success
1618
                # as broadcasting might fail with some harmless error like 'transaction already in mempool'
1619
                tx_mined_info = self.wallet.adb.get_tx_height(funding_tx.txid())
×
1620
                if tx_mined_info.height() > TX_HEIGHT_LOCAL:
×
1621
                    self.logger.debug(f"found our jit channel funding tx: {tx_mined_info.height()=}")
×
1622
                    break
×
1623
            else:
1624
                raise OnionRoutingFailure(
×
1625
                    code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
1626
                    data=b'failed to broadcast funding transaction',
1627
                )
1628
        except Exception as e:
1✔
1629
            self.logger.warning(f"failed to open just in time channel: {repr(e)}")
1✔
1630
            if next_chan:
1✔
1631
                await self._cleanup_failed_jit_channel(next_chan)
1✔
1632
            self._preimages.pop(payment_hash.hex(), None)
1✔
1633
            if isinstance(e, OnionRoutingFailure):
1✔
1634
                raise
×
1635
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
1✔
1636
        finally:
1637
            del self.dont_settle_htlcs[payment_hash.hex()]
1✔
1638

1639
        htlc_key = serialize_htlc_key(next_chan.get_scid_or_local_alias(), htlc.htlc_id)
1✔
1640
        return htlc_key
1✔
1641

1642
    async def _cleanup_failed_jit_channel(self, chan: Channel):
1✔
1643
        """
1644
        Removes a just in time channel where we didn't broadcast the funding
1645
        transaction, e.g. when the client didn't release the preimage.
1646
        """
1647
        funding_height = chan.get_funding_height()
1✔
1648
        if funding_height is not None and funding_height[1] > TX_HEIGHT_LOCAL:
1✔
1649
            raise Exception("must not delete the channel if it has been broadcast")
×
1650
        # try to be nice and send shutdown to signal peer that this channel is dead
1651
        try:
1✔
1652
            await util.wait_for2(self.close_channel(chan.channel_id), LN_P2P_NETWORK_TIMEOUT)
1✔
1653
        except Exception:
1✔
1654
            self.logger.debug(f"sending chan shutdown to failed zeroconf peer failed ", exc_info=True)
1✔
1655
        chan.set_state(ChannelState.REDEEMED, force=True)
1✔
1656
        self.lnwatcher.adb.remove_transaction(chan.funding_outpoint.txid)
1✔
1657
        self.remove_channel(chan.channel_id)
1✔
1658

1659
    @log_exceptions
1✔
1660
    async def open_channel_with_peer(
1✔
1661
            self, peer, funding_sat, *,
1662
            push_sat: int = 0,
1663
            public: bool = False,
1664
            zeroconf: bool = False,
1665
            opening_base_fee_msat: Optional[int] = None,
1666
            password=None):
1667
        self.wallet.unlock(password)
1✔
1668
        coins = self.wallet.get_spendable_coins(None)
1✔
1669
        node_id = peer.pubkey
1✔
1670
        fee_policy = FeePolicy(self.config.FEE_POLICY)
1✔
1671
        funding_tx = self.mktx_for_open_channel(
1✔
1672
            coins=coins,
1673
            funding_sat=funding_sat,
1674
            node_id=node_id,
1675
            fee_policy=fee_policy)
1676
        if opening_base_fee_msat:
1✔
1677
            # add funding tx fee on top of the opening fee to avoid opening channels at a loss
1678
            opening_base_fee_msat += funding_tx.get_fee() * 1000
×
1679
        chan, funding_tx = await self._open_channel_coroutine(
1✔
1680
            peer=peer,
1681
            funding_tx=funding_tx,
1682
            funding_sat=funding_sat,
1683
            push_sat=push_sat,
1684
            public=public,
1685
            zeroconf=zeroconf,
1686
            opening_fee=opening_base_fee_msat,
1687
            password=password)
1688
        return chan, funding_tx
1✔
1689

1690
    @log_exceptions
1✔
1691
    async def _open_channel_coroutine(
1✔
1692
            self, *,
1693
            peer: Peer,
1694
            funding_tx: PartialTransaction,
1695
            funding_sat: int,
1696
            push_sat: int,
1697
            public: bool,
1698
            zeroconf=False,
1699
            opening_fee=None,
1700
            password: Optional[str],
1701
    ) -> Tuple[Channel, PartialTransaction]:
1702

1703
        if funding_sat > self.config.LIGHTNING_MAX_FUNDING_SAT:
1✔
1704
            raise Exception(
×
1705
                _("Requested channel capacity is over maximum.")
1706
                + f"\n{funding_sat} sat > {self.config.LIGHTNING_MAX_FUNDING_SAT} sat"
1707
            )
1708
        coro = peer.channel_establishment_flow(
1✔
1709
            funding_tx=funding_tx,
1710
            funding_sat=funding_sat,
1711
            push_msat=push_sat * 1000,
1712
            public=public,
1713
            zeroconf=zeroconf,
1714
            opening_fee=opening_fee,
1715
            temp_channel_id=crandom.get_rand_bytes(32))
1716
        chan, funding_tx = await util.wait_for2(coro, LN_P2P_NETWORK_TIMEOUT)
1✔
1717
        util.trigger_callback('channels_updated', self.wallet)
1✔
1718
        self.wallet.adb.add_transaction(funding_tx)  # save tx as local into the wallet
1✔
1719
        self.wallet.sign_transaction(funding_tx, password)
1✔
1720
        if funding_tx.is_complete() and not zeroconf:
1✔
1721
            await self.network.try_broadcasting(funding_tx, 'open_channel')
1✔
1722
        return chan, funding_tx
1✔
1723

1724
    def add_channel(self, chan: Channel):
1✔
1725
        with self.lock:
1✔
1726
            self._channels[chan.channel_id] = chan
1✔
1727
        self.lnwatcher.add_channel(chan)
1✔
1728

1729
    def add_new_channel(self, temp_chan: Channel) -> Channel:
1✔
1730
        """Add a new channel into the persisted DB.
1731
        Deletes the given temporary channel object, because it uses a simple dict.
1732
        """
1733
        assert type(temp_chan.storage) is dict
1✔
1734
        channel_id = temp_chan.channel_id.hex()
1✔
1735
        channels_db = self.db.get_dict('channels')
1✔
1736
        assert channel_id not in channels_db
1✔
1737
        channels_db[channel_id] = temp_chan.storage
1✔
1738
        jit_opening_fee = temp_chan.jit_opening_fee
1✔
1739
        peer_state = temp_chan.peer_state
1✔
1740
        del temp_chan
1✔
1741
        storage = channels_db[channel_id] # StoredDict
1✔
1742
        chan = Channel(
1✔
1743
            storage,
1744
            lnworker=self,
1745
            jit_opening_fee=jit_opening_fee,
1746
        )
1747
        assert type(chan.storage) is StoredDict, type(chan.storage)
1✔
1748
        chan.peer_state = peer_state
1✔
1749
        self.add_channel(chan)
1✔
1750
        self.wallet.set_reserved_addresses_for_chan(chan, reserved=True)
1✔
1751
        try:
1✔
1752
            self.save_channel(chan)
1✔
1753
        except Exception:
×
1754
            chan.set_state(ChannelState.REDEEMED)
×
1755
            self.remove_channel(chan.channel_id)
×
1756
            raise
×
1757
        # return new channel object
1758
        return chan
1✔
1759

1760
    def make_local_config_for_new_channel(
1✔
1761
        self,
1762
        *,
1763
        funding_sat: int,
1764
        push_msat: int,
1765
        initiator: HTLCOwner,
1766
        channel_type: ChannelType,
1767
        multisig_funding_keypair: Optional[Keypair],  # if None, will get derived from channel_seed
1768
        peer_features: LnFeatures,
1769
        channel_seed: bytes | None = None,
1770
    ) -> LocalConfig:
1771
        if channel_seed is None:
1✔
1772
            channel_seed = crandom.get_rand_bytes(32)
1✔
1773
        initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat
1✔
1774

1775
        # sending empty bytes as the upfront_shutdown_script will give us the
1776
        # flexibility to decide an address at closing time
1777
        upfront_shutdown_script = b''
1✔
1778

1779
        assert channel_type is not None
1✔
1780
        channel_type.check_combinations()  # test if raises
1✔
1781
        if channel_type & ChannelType.OPTION_ANCHORS:  # anchors
1✔
1782
            static_payment_key = self.static_payment_key
1✔
1783
            payment_basepoint = None
1✔
1784
        else:  # static_remotekey
1785
            assert channel_type & channel_type.OPTION_STATIC_REMOTEKEY
1✔
1786
            assert self.config.TEST_LN_OPEN_SRK_CHANNELS
1✔
1787
            wallet = self.wallet
1✔
1788
            assert wallet.txin_type == 'p2wpkh'
1✔
1789
            addr = wallet.get_new_sweep_address()
1✔
1790
            static_payment_key = None
1✔
1791
            payment_basepoint = bytes.fromhex(wallet.get_public_key(addr))
1✔
1792

1793
        if multisig_funding_keypair:
1✔
1794
            for chan in self.channels.values():  # check against all chans of lnworker, for sanity
1✔
1795
                if multisig_funding_keypair.pubkey == chan.config[LOCAL].multisig_key.pubkey:
×
1796
                    raise Exception(
×
1797
                        "Refusing to reuse multisig_funding_keypair for new channel. "
1798
                        "Wait one block before opening another channel with this peer."
1799
                    )
1800

1801
        dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH
1✔
1802
        reserve_sat = max(funding_sat // 100, dust_limit_sat)
1✔
1803
        # for comparison of defaults, see
1804
        # https://github.com/ACINQ/eclair/blob/afa378fbb73c265da44856b4ad0f2128a88ae6c6/eclair-core/src/main/resources/reference.conf#L66
1805
        # https://github.com/ElementsProject/lightning/blob/0056dd75572a8857cff36fcbdb1a2295a1ac9253/lightningd/options.c#L657
1806
        # https://github.com/lightningnetwork/lnd/blob/56b61078c5b2be007d318673a5f3b40c6346883a/config.go#L81
1807
        max_htlc_value_in_flight_msat = self.network.config.LIGHTNING_MAX_HTLC_VALUE_IN_FLIGHT_MSAT or funding_sat * 1000
1✔
1808
        local_config = LocalConfig.from_seed(
1✔
1809
            channel_seed=channel_seed,
1810
            channel_type=channel_type,
1811
            payment_basepoint=payment_basepoint,
1812
            static_payment_key=static_payment_key,
1813
            multisig_key=multisig_funding_keypair,
1814
            upfront_shutdown_script=upfront_shutdown_script,
1815
            to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV,
1816
            dust_limit_sat=dust_limit_sat,
1817
            max_htlc_value_in_flight_msat=max_htlc_value_in_flight_msat,
1818
            max_accepted_htlcs=30,
1819
            initial_msat=initial_msat,
1820
            reserve_sat=reserve_sat,
1821
            funding_locked_received=False,
1822
            current_commitment_signature=None,
1823
            current_htlc_signatures=b'',
1824
            htlc_minimum_msat=1,
1825
            announcement_node_sig=b'',
1826
            announcement_bitcoin_sig=b'',
1827
        )
1828
        local_config.validate_params(funding_sat=funding_sat, config=self.network.config, peer_features=peer_features)
1✔
1829
        return local_config
1✔
1830

1831
    def cb_data(self, node_id: bytes) -> bytes:
1✔
1832
        return CB_MAGIC_BYTES + node_id[0:NODE_ID_PREFIX_LEN]
1✔
1833

1834
    def decrypt_cb_data(self, encrypted_data: bytes, funding_address: str) -> bytes:
1✔
1835
        funding_scripthash = bytes.fromhex(address_to_scripthash(funding_address))
1✔
1836
        nonce = funding_scripthash[0:12]
1✔
1837
        return chacha20_decrypt(key=self.backup_key, data=encrypted_data, nonce=nonce)
1✔
1838

1839
    def encrypt_cb_data(self, data: bytes, funding_address: str) -> bytes:
1✔
1840
        funding_scripthash = bytes.fromhex(address_to_scripthash(funding_address))
1✔
1841
        nonce = funding_scripthash[0:12]
1✔
1842
        # note: we are only using chacha20 instead of chacha20+poly1305 to save onchain space
1843
        #       (not have the 16 byte MAC). Otherwise, the latter would be preferable.
1844
        return chacha20_encrypt(key=self.backup_key, data=data, nonce=nonce)
1✔
1845

1846
    def mktx_for_open_channel(
1✔
1847
            self, *,
1848
            coins: Sequence[PartialTxInput],
1849
            funding_sat: int,
1850
            node_id: bytes,
1851
            fee_policy: FeePolicy,
1852
    ) -> PartialTransaction:
1853
        from .wallet import get_locktime_for_new_transaction
1✔
1854

1855
        outputs = [PartialTxOutput.from_address_and_value(DummyAddress.CHANNEL, funding_sat)]
1✔
1856
        if self.has_recoverable_channels():
1✔
1857
            dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
1✔
1858
            outputs.append(PartialTxOutput(scriptpubkey=dummy_scriptpubkey, value=0))
1✔
1859
        tx = self.wallet.make_unsigned_transaction(
1✔
1860
            coins=coins,
1861
            outputs=outputs,
1862
            fee_policy=fee_policy,
1863
            is_anchor_channel_opening=not self.config.TEST_LN_OPEN_SRK_CHANNELS,
1864
        )
1865
        tx.set_rbf(False)
1✔
1866
        # rm randomness from locktime, as we use the locktime as entropy for deriving the funding_privkey
1867
        # (and it would be confusing to get a collision as a consequence of the randomness)
1868
        tx.locktime = get_locktime_for_new_transaction(self.network, include_random_component=False)
1✔
1869
        return tx
1✔
1870

1871
    def suggest_funding_amount(self, amount_to_pay: int, coins: Sequence[PartialTxInput]) -> Tuple[int, int] | None:
1✔
1872
        """ whether we can pay amount_sat after opening a new channel"""
1873
        num_sats_can_send = int(self.num_sats_can_send())
×
1874
        lightning_needed = amount_to_pay - num_sats_can_send
×
1875
        assert lightning_needed > 0
×
1876
        min_funding_sat = lightning_needed + (lightning_needed // 20) + 1000  # safety margin
×
1877
        min_funding_sat = max(min_funding_sat, MIN_FUNDING_SAT)  # at least MIN_FUNDING_SAT
×
1878
        if min_funding_sat > self.config.LIGHTNING_MAX_FUNDING_SAT:
×
1879
            return
×
1880
        fee_policy = FeePolicy(f'feerate:{FEERATE_FALLBACK_STATIC_FEE}')
×
1881
        try:
×
1882
            self.mktx_for_open_channel(
×
1883
                coins=coins, funding_sat=min_funding_sat, node_id=bytes(32), fee_policy=fee_policy)
1884
            funding_sat = min_funding_sat
×
1885
        except NotEnoughFunds:
×
1886
            return
×
1887
        # if available, suggest twice that amount:
1888
        if 2 * min_funding_sat <= self.config.LIGHTNING_MAX_FUNDING_SAT:
×
1889
            try:
×
1890
                self.mktx_for_open_channel(
×
1891
                    coins=coins, funding_sat=2*min_funding_sat, node_id=bytes(32), fee_policy=fee_policy)
1892
                funding_sat = 2 * min_funding_sat
×
1893
            except NotEnoughFunds:
×
1894
                pass
×
1895
        return funding_sat, min_funding_sat
×
1896

1897
    def open_channel(
1✔
1898
            self, *,
1899
            connect_str: str,
1900
            funding_tx: PartialTransaction,
1901
            funding_sat: int,
1902
            push_amt_sat: int,
1903
            public: bool = False,
1904
            password: str | None = None,
1905
    ) -> Tuple[Channel, PartialTransaction]:
1906

1907
        fut = asyncio.run_coroutine_threadsafe(self.lnpeermgr.add_peer(connect_str), get_asyncio_loop())
×
1908
        try:
×
1909
            peer = fut.result()
×
1910
        except concurrent.futures.TimeoutError:
×
1911
            raise Exception(_("add peer timed out"))
×
1912
        coro = self._open_channel_coroutine(
×
1913
            peer=peer,
1914
            funding_tx=funding_tx,
1915
            funding_sat=funding_sat,
1916
            push_sat=push_amt_sat,
1917
            public=public,
1918
            password=password)
1919
        fut = asyncio.run_coroutine_threadsafe(coro, get_asyncio_loop())
×
1920
        try:
×
1921
            chan, funding_tx = fut.result()
×
1922
        except concurrent.futures.TimeoutError:
×
1923
            raise Exception(_("open_channel timed out"))
×
1924
        return chan, funding_tx
×
1925

1926
    def get_channel_by_short_id(self, short_channel_id: bytes) -> Optional[Channel]:
1✔
1927
        assert short_channel_id and isinstance(short_channel_id, bytes), repr(short_channel_id)
1✔
1928
        # First check against *real* SCIDs.
1929
        # This e.g. protects against maliciously chosen SCID aliases, and accidental collisions.
1930
        for chan in self.channels.values():
1✔
1931
            if chan.short_channel_id == short_channel_id:
1✔
1932
                return chan
1✔
1933
        # Now we also consider aliases.
1934
        # TODO we should split this as this search currently ignores the "direction"
1935
        #      of the aliases. We should only look at either the remote OR the local alias,
1936
        #      depending on context.
1937
        for chan in self.channels.values():
1✔
1938
            if chan.get_remote_scid_alias() == short_channel_id:
1✔
1939
                return chan
×
1940
            if chan.get_local_scid_alias() == short_channel_id:
1✔
1941
                return chan
×
1942
        return None
1✔
1943

1944
    def can_pay_invoice(self, invoice: Invoice) -> bool:
1✔
1945
        assert invoice.is_lightning()
×
1946
        return (invoice.get_amount_sat() or 0) <= self.num_sats_can_send()
×
1947

1948
    @log_exceptions
1✔
1949
    async def pay_invoice(
1✔
1950
            self, invoice: Invoice, *,
1951
            amount_msat: int | None = None,  # to overwrite amt in invoice
1952
            attempts: int | None = None,  # used only in unit tests
1953
            full_path: LNPaymentPath = None,
1954
            channels: Optional[Sequence[Channel]] = None,  # my own direct channels
1955
            budget: Optional[PaymentFeeBudget] = None,  # to limit max fee
1956
    ) -> Tuple[bool, List[HtlcLog]]:
1957
        """Attempt to pay a Lightning invoice (find routes, do MPP, send HTLCs).
1958

1959
        Note: this does NOT directly send money, it sends HTLC(s), which is a conditional contract.
1960
              The intended recipient (or any node) can only claim the HTLCs by revealing the correct preimage.
1961
              When paying a hold-invoice, or during a submarine swap, it is often the case that the recipient
1962
              does not YET know the preimage, and hence they cannot take the money until later.
1963
        """
1964
        bolt11 = invoice.lightning_invoice
1✔
1965
        lnaddr = self._check_bolt11_invoice(bolt11, amount_msat=amount_msat)
1✔
1966
        min_final_cltv_delta = lnaddr.get_min_final_cltv_delta()
1✔
1967
        payment_hash = lnaddr.paymenthash
1✔
1968
        key = payment_hash.hex()
1✔
1969
        payment_secret = lnaddr.payment_secret
1✔
1970
        invoice_pubkey = lnaddr.pubkey.serialize()
1✔
1971
        invoice_features = lnaddr.get_features()
1✔
1972
        r_tags = lnaddr.get_routing_info()
1✔
1973
        amount_to_pay = lnaddr.get_amount_msat()
1✔
1974
        status = self.get_invoice_status(invoice)
1✔
1975
        if status == PR_PAID:
1✔
1976
            raise PaymentFailure(_("This invoice has been paid already"))
×
1977
        if status == PR_INFLIGHT:
1✔
1978
            raise PaymentFailure(_("A payment was already initiated for this invoice"))
1✔
1979
        if self.has_unresolved_sent_htlcs(payment_hash):
1✔
1980
            raise PaymentFailure(_("A previous attempt to pay this invoice did not clear"))
×
1981
        info = PaymentInfo(
1✔
1982
            payment_hash=payment_hash,
1983
            amount_msat=amount_to_pay,
1984
            direction=SENT,
1985
            status=PR_UNPAID,
1986
            min_final_cltv_delta=min_final_cltv_delta,
1987
            expiry_delay=LN_EXPIRY_NEVER,
1988
            invoice_features=invoice_features,
1989
        )
1990
        self.save_payment_info(info)
1✔
1991
        self.wallet.set_label(key, lnaddr.get_description())
1✔
1992
        self.set_invoice_status(key, PR_INFLIGHT)
1✔
1993
        if budget is None:
1✔
1994
            budget = PaymentFeeBudget.from_invoice_amount(invoice_amount_msat=amount_to_pay, config=self.config)
1✔
1995
        if attempts is None and self.uses_trampoline():
1✔
1996
            # we don't expect lots of failed htlcs with trampoline, so we can fail sooner
1997
            attempts = 30
1✔
1998
        success, reason = False, _("unknown")
1✔
1999
        try:
1✔
2000
            await self.pay_to_node(
1✔
2001
                node_pubkey=invoice_pubkey,
2002
                payment_hash=payment_hash,
2003
                payment_secret=payment_secret,
2004
                amount_to_pay=amount_to_pay,
2005
                min_final_cltv_delta=min_final_cltv_delta,
2006
                r_tags=r_tags,
2007
                invoice_features=invoice_features,
2008
                attempts=attempts,
2009
                full_path=full_path,
2010
                channels=channels,
2011
                budget=budget,
2012
            )
2013
            success = True
1✔
2014
        except (PaymentFailure, ChannelDBNotLoaded) as e:
1✔
2015
            self.logger.info(f'payment failure: {e!r}')
1✔
2016
            reason = str(e)
1✔
2017
        finally:
2018
            self.logger.info(f"pay_invoice ending session for RHASH={payment_hash.hex()}. {success=}")
1✔
2019
            if success:
1✔
2020
                self.set_invoice_status(key, PR_PAID)
1✔
2021
                util.trigger_callback('payment_succeeded', self.wallet, key)
1✔
2022
            elif self.has_unresolved_sent_htlcs(payment_hash):
1✔
2023
                # The invoice stays PR_INFLIGHT until the htlcs resolve.
2024
                self.logger.info("pay_invoice: htlcs are still unresolved.")
1✔
2025
            else:
2026
                self.set_invoice_status(key, PR_UNPAID)  # allows retries
1✔
2027
                util.trigger_callback('payment_failed', self.wallet, key, reason)
1✔
2028
        log = self.logs[key]
1✔
2029
        return success, log
1✔
2030

2031
    @log_exceptions
1✔
2032
    async def pay_to_node(
1✔
2033
            self, *,
2034
            node_pubkey: bytes,
2035
            payment_hash: bytes,
2036
            payment_secret: bytes,
2037
            amount_to_pay: int,  # in msat
2038
            min_final_cltv_delta: int,
2039
            r_tags,
2040
            invoice_features: int,
2041
            attempts: int | None = None,
2042
            full_path: LNPaymentPath = None,
2043
            fwd_trampoline_onion: OnionPacket = None,
2044
            budget: PaymentFeeBudget,
2045
            channels: Optional[Sequence[Channel]] = None,
2046
            fw_payment_key: str | None = None,  # for forwarding
2047
    ) -> None:
2048
        """
2049
        Can raise PaymentFailure, ChannelDBNotLoaded,
2050
        or OnionRoutingFailure (if forwarding trampoline).
2051
        """
2052

2053
        assert budget
1✔
2054
        assert budget.fee_msat >= 0, budget
1✔
2055
        assert budget.cltv >= 0, budget
1✔
2056

2057
        payment_key = payment_hash + payment_secret
1✔
2058
        assert payment_key not in self._paysessions
1✔
2059
        self._paysessions[payment_key] = paysession = PaySession(
1✔
2060
            payment_hash=payment_hash,
2061
            payment_secret=payment_secret,
2062
            initial_trampoline_fee_level=self.config.INITIAL_TRAMPOLINE_FEE_LEVEL,
2063
            invoice_features=invoice_features,
2064
            r_tags=r_tags,
2065
            min_final_cltv_delta=min_final_cltv_delta,
2066
            amount_to_pay=amount_to_pay,
2067
            invoice_pubkey=node_pubkey,
2068
            uses_trampoline=self.uses_trampoline(),
2069
        )
2070
        self.logs[payment_hash.hex()] = log = []  # TODO incl payment_secret in key (re trampoline forwarding)
1✔
2071

2072
        paysession.logger.info(
1✔
2073
            f"pay_to_node starting session for RHASH={payment_hash.hex()}. "
2074
            f"using_trampoline={self.uses_trampoline()}. "
2075
            f"invoice_features={paysession.invoice_features.get_names()}. "
2076
            f"r_tags={BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags)}. "
2077
            f"{amount_to_pay=} msat. {budget=}")
2078
        if not self.uses_trampoline():
1✔
2079
            self.logger.info(
1✔
2080
                f"gossip_db status. sync progress: {self.network.lngossip.get_sync_progress_estimate()}. "
2081
                f"num_nodes={self.channel_db.num_nodes}, "
2082
                f"num_channels={self.channel_db.num_channels}, "
2083
                f"num_policies={self.channel_db.num_policies}.")
2084

2085
        # when encountering trampoline forwarding difficulties in the legacy case, we
2086
        # sometimes need to fall back to a single trampoline forwarder, at the expense
2087
        # of privacy
2088
        try:
1✔
2089
            while True:
1✔
2090
                if (amount_to_send := paysession.get_outstanding_amount_to_send()) > 0:
1✔
2091
                    remaining_fee_budget_msat = (budget.fee_msat * amount_to_send) // amount_to_pay
1✔
2092
                    # splitting the amount of the payment between our channels requires the correct
2093
                    # available channel balance. to prevent concurrent splitting attempts from
2094
                    # using stale channel balances for the split calculation a lock needs to be
2095
                    # taken until the htlcs are added to the channel so the next splitting attempt
2096
                    # acts on a correct channel balance.
2097
                    async with self._channel_sending_capacity_lock:
1✔
2098
                        # 1. create a set of routes for remaining amount.
2099
                        # note: path-finding runs in a separate thread so that we don't block the asyncio loop
2100
                        # graph updates might occur during the computation
2101
                        routes = self.create_routes_for_payment(
1✔
2102
                            paysession=paysession,
2103
                            amount_msat=amount_to_send,
2104
                            full_path=full_path,
2105
                            fwd_trampoline_onion=fwd_trampoline_onion,
2106
                            channels=channels,
2107
                            budget=budget._replace(fee_msat=remaining_fee_budget_msat),
2108
                        )
2109
                        # 2. send htlcs
2110
                        async for sent_htlc_info, cltv_delta, trampoline_onion in routes:
1✔
2111
                            await self.pay_to_route(
1✔
2112
                                paysession=paysession,
2113
                                sent_htlc_info=sent_htlc_info,
2114
                                min_final_cltv_delta=cltv_delta,
2115
                                trampoline_onion=trampoline_onion,
2116
                                fw_payment_key=fw_payment_key,
2117
                            )
2118
                    # invoice_status is triggered in self.set_invoice_status when it actually changes.
2119
                    # It is also triggered here to update progress for a lightning payment in the GUI
2120
                    # (e.g. attempt counter)
2121
                    util.trigger_callback('invoice_status', self.wallet, payment_hash.hex(), PR_INFLIGHT)
1✔
2122
                # 3. await a queue, collect resolved htlcs
2123
                htlc_log = await paysession.wait_for_one_htlc_to_resolve()
1✔
2124
                while True:
1✔
2125
                    log.append(htlc_log)
1✔
2126
                    await self._process_htlc_log(
1✔
2127
                        paysession=paysession, htlc_log=htlc_log, is_forwarding_trampoline=bool(fwd_trampoline_onion))
2128
                    if paysession.number_htlcs_inflight < 1:
1✔
2129
                        break
1✔
2130
                    # wait a bit, more failures might come
2131
                    try:
1✔
2132
                        htlc_log = await util.wait_for2(
1✔
2133
                            paysession.wait_for_one_htlc_to_resolve(),
2134
                            timeout=paysession.TIMEOUT_WAIT_FOR_NEXT_RESOLVED_HTLC)
2135
                    except asyncio.TimeoutError:
1✔
2136
                        break
1✔
2137

2138
                # max attempts or timeout
2139
                if (attempts is not None and len(log) >= attempts) or (attempts is None and time.time() - paysession.start_time > self.PAYMENT_TIMEOUT):
1✔
2140
                    raise PaymentFailure('Giving up after %d attempts'%len(log))
1✔
2141
        except PaymentSuccess:
1✔
2142
            pass
1✔
2143
        finally:
2144
            paysession.is_active = False
1✔
2145
            if paysession.can_be_deleted():
1✔
2146
                self._paysessions.pop(payment_key)
1✔
2147
            paysession.logger.info(f"pay_to_node ending session for RHASH={payment_hash.hex()}")
1✔
2148

2149
    async def _process_htlc_log(
1✔
2150
        self,
2151
        *,
2152
        paysession: PaySession,
2153
        htlc_log: HtlcLog,
2154
        is_forwarding_trampoline: bool,
2155
    ) -> None:
2156
        """Handle a single just-resolved HTLC, as part of a payment-session.
2157

2158
        Can raise PaymentFailure, PaymentSuccess,
2159
        or OnionRoutingFailure (if forwarding trampoline).
2160
        """
2161
        if htlc_log.success:
1✔
2162
            raise PaymentSuccess()
1✔
2163
        # htlc failed
2164
        # if we get a tmp channel failure, it might work to split the amount and try more routes
2165
        # if we get a channel update, we might retry the same route and amount
2166
        route = htlc_log.route
1✔
2167
        sender_idx = htlc_log.sender_idx
1✔
2168
        failure_msg = htlc_log.failure_msg
1✔
2169
        if sender_idx is None:
1✔
2170
            raise PaymentFailure(failure_msg.code_name())
1✔
2171
        erring_node_id = route[sender_idx].node_id
1✔
2172
        code, data = failure_msg.code, failure_msg.data
1✔
2173
        self.logger.info(f"UPDATE_FAIL_HTLC. code={repr(code)}. "
1✔
2174
                         f"decoded_data={failure_msg.decode_data()}. data={data.hex()!r}")
2175
        self.logger.info(f"error reported by {erring_node_id.hex()}")
1✔
2176
        if code == OnionFailureCode.MPP_TIMEOUT:
1✔
2177
            raise PaymentFailure(failure_msg.code_name())
1✔
2178
        # errors returned by the next trampoline.
2179
        if is_forwarding_trampoline and code in [
1✔
2180
                OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT,
2181
                OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON]:
2182
            raise failure_msg
×
2183
        # trampoline
2184
        if self.uses_trampoline():
1✔
2185
            paysession.handle_failed_trampoline_htlc(
1✔
2186
                node_id=erring_node_id,
2187
                htlc_log=htlc_log,
2188
                failure_msg=failure_msg)
2189
        else:
2190
            self.handle_error_code_from_failed_htlc(
1✔
2191
                route=route, sender_idx=sender_idx, failure_msg=failure_msg, amount_msat=htlc_log.amount_msat)
2192

2193
    async def pay_to_route(
1✔
2194
            self, *,
2195
            paysession: PaySession,
2196
            sent_htlc_info: SentHtlcInfo,
2197
            min_final_cltv_delta: int,
2198
            trampoline_onion: Optional[OnionPacket] = None,
2199
            fw_payment_key: str | None = None,
2200
    ) -> None:
2201
        """Sends a single HTLC."""
2202
        shi = sent_htlc_info
1✔
2203
        del sent_htlc_info  # just renamed
1✔
2204
        short_channel_id = shi.route[0].short_channel_id
1✔
2205
        chan = self.get_channel_by_short_id(short_channel_id)
1✔
2206
        assert chan, ShortChannelID(short_channel_id)
1✔
2207
        peer = self.lnpeermgr.get_peer_by_pubkey(shi.route[0].node_id)
1✔
2208
        if not peer:
1✔
2209
            raise PaymentFailure('Dropped peer')
×
2210
        await peer.initialized
1✔
2211
        htlc = peer.pay(
1✔
2212
            route=shi.route,
2213
            chan=chan,
2214
            amount_msat=shi.amount_msat,
2215
            total_msat=shi.bucket_msat,
2216
            payment_hash=paysession.payment_hash,
2217
            min_final_cltv_delta=min_final_cltv_delta,
2218
            payment_secret=shi.payment_secret_bucket,
2219
            trampoline_onion=trampoline_onion)
2220

2221
        key = (paysession.payment_hash, short_channel_id, htlc.htlc_id)
1✔
2222
        self.sent_htlcs_info[key] = shi
1✔
2223
        paysession.add_new_htlc(shi)
1✔
2224
        if fw_payment_key:
1✔
2225
            htlc_key = serialize_htlc_key(short_channel_id, htlc.htlc_id)
1✔
2226
            self.logger.info(f'adding active forwarding {fw_payment_key}')
1✔
2227
            self.active_forwardings[fw_payment_key].append(htlc_key)
1✔
2228
        if self.network.path_finder:
1✔
2229
            # add inflight htlcs to liquidity hints; removed again in htlc_fulfilled/htlc_failed
2230
            self.network.path_finder.update_num_inflight_htlcs(shi.route, add_htlcs=True)
1✔
2231
        util.trigger_callback('htlc_added', chan, htlc, SENT)
1✔
2232

2233
    def handle_error_code_from_failed_htlc(
1✔
2234
            self,
2235
            *,
2236
            route: LNPaymentRoute,
2237
            sender_idx: int,
2238
            failure_msg: OnionRoutingFailure,
2239
            amount_msat: int,
2240
    ) -> None:
2241

2242
        assert self.channel_db  # cannot be in trampoline mode
1✔
2243
        assert self.network.path_finder
1✔
2244

2245
        code, data = failure_msg.code, failure_msg.data
1✔
2246
        # TODO can we use lnmsg.OnionWireSerializer here?
2247
        # TODO update onion_wire.csv
2248
        # handle some specific error codes
2249
        failure_codes = {
1✔
2250
            OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 0,
2251
            OnionFailureCode.AMOUNT_BELOW_MINIMUM: 8,
2252
            OnionFailureCode.FEE_INSUFFICIENT: 8,
2253
            OnionFailureCode.INCORRECT_CLTV_EXPIRY: 4,
2254
            OnionFailureCode.EXPIRY_TOO_SOON: 0,
2255
            OnionFailureCode.CHANNEL_DISABLED: 2,
2256
        }
2257
        try:
1✔
2258
            failing_channel = route[sender_idx + 1].short_channel_id
1✔
2259
        except IndexError:
1✔
2260
            raise PaymentFailure(f'payment destination reported error: {failure_msg.code_name()}') from None
1✔
2261

2262
        # TODO: handle unknown next peer?
2263
        # handle failure codes that may include a channel update
2264
        if code in failure_codes:
1✔
2265
            offset = failure_codes[code]
1✔
2266
            channel_update_len = int.from_bytes(data[offset:offset+2], byteorder="big")
1✔
2267
            channel_update_as_received = data[offset+2: offset+2+channel_update_len]
1✔
2268
            if channel_update_len == 0:
1✔
2269
                # the channel_update became optional
2270
                # https://github.com/lightning/bolts/blob/93b7ee031b50acd59967a105f1326176a37628f9/04-onion-routing.md?plain=1#L1384-L1389
2271
                # without an update we cannot correct our local policy for the channel, so we avoid (blacklist) it,
2272
                # except for liquidity failures, where the liquidity hint suffices to retry
2273
                blacklist = code != OnionFailureCode.TEMPORARY_CHANNEL_FAILURE
1✔
2274
            elif (payload := self._decode_channel_update_msg(channel_update_as_received)) is None:
1✔
2275
                self.logger.info(f'could not decode channel_update for failed htlc: '
×
2276
                                 f'{channel_update_as_received.hex()}')
2277
                blacklist = True
×
2278
            elif payload.get('short_channel_id') != failing_channel:
1✔
2279
                self.logger.info(f'short_channel_id in channel_update does not match our route')
×
2280
                blacklist = True
×
2281
            else:
2282
                # apply the channel update or get blacklisted
2283
                blacklist, handled = self._handle_chanupd_from_failed_htlc(
1✔
2284
                    payload, route=route, sender_idx=sender_idx, failure_msg=failure_msg)
2285
                assert blacklist or handled, "some action has to be taken on a failure with channel update"
1✔
2286
            # we interpret a temporary channel failure as a liquidity issue
2287
            # in the channel and update our liquidity hints accordingly
2288
            if code == OnionFailureCode.TEMPORARY_CHANNEL_FAILURE:
1✔
2289
                self.network.path_finder.update_liquidity_hints(
1✔
2290
                    route,
2291
                    amount_msat,
2292
                    failing_channel=ShortChannelID(failing_channel))
2293
        # for errors that never include a channel update
2294
        else:
2295
            blacklist = True
1✔
2296
        if blacklist:
1✔
2297
            self.network.path_finder.add_edge_to_blacklist(short_channel_id=failing_channel)
1✔
2298

2299
    def _handle_chanupd_from_failed_htlc(
1✔
2300
        self, payload, *,
2301
        route: LNPaymentRoute,
2302
        sender_idx: int,
2303
        failure_msg: OnionRoutingFailure,
2304
    ) -> Tuple[bool, bool]:
2305
        blacklist = False
1✔
2306
        handled = False
1✔
2307
        try:
1✔
2308
            r = self.channel_db.add_channel_update(payload, verify=True)
1✔
2309
        except InvalidGossipMsg:
×
2310
            return True, False  # blacklist
×
2311
        short_channel_id = ShortChannelID(payload['short_channel_id'])
1✔
2312
        if r == UpdateStatus.GOOD:
1✔
2313
            self.logger.info(f"applied channel update to {short_channel_id}")
×
2314
            # TODO: add test for this
2315
            # FIXME: this does not work for our own unannounced channels.
2316
            for chan in self.channels.values():
×
2317
                if chan.short_channel_id == short_channel_id:
×
2318
                    chan.set_remote_update(payload)
×
2319
            handled = True
×
2320
        elif r == UpdateStatus.ORPHANED:
1✔
2321
            # maybe it is a private channel (and data in invoice was outdated)
2322
            self.logger.info(f"Could not find {short_channel_id}. maybe update is for private channel?")
1✔
2323
            start_node_id = route[sender_idx].node_id
1✔
2324
            cache_ttl = None
1✔
2325
            if failure_msg.code == OnionFailureCode.CHANNEL_DISABLED:
1✔
2326
                # eclair sends CHANNEL_DISABLED if its peer is offline. E.g. we might be trying to pay
2327
                # a mobile phone with the app closed. So we cache this with a short TTL.
2328
                cache_ttl = self.channel_db.PRIVATE_CHAN_UPD_CACHE_TTL_SHORT
×
2329
            handled = self.channel_db.add_channel_update_for_private_channel(payload, start_node_id, cache_ttl=cache_ttl)
1✔
2330
            blacklist = not handled
1✔
2331
        elif r == UpdateStatus.EXPIRED:
1✔
2332
            blacklist = True
×
2333
        elif r == UpdateStatus.DEPRECATED:
1✔
2334
            self.logger.info(f'channel update is not more recent.')
×
2335
            blacklist = True
×
2336
        elif r == UpdateStatus.UNCHANGED:
1✔
2337
            if failure_msg.code == OnionFailureCode.TEMPORARY_CHANNEL_FAILURE:
1✔
2338
                # the sent htlc might have exceeded the channel's liquidity, no need to blacklist,
2339
                # we record liquidity hints and can attempt again with a smaller htlc
2340
                handled = True
1✔
2341
            else:
2342
                blacklist = True
1✔
2343
        else:
2344
            raise Exception(f"unexpected chan upd UpdateStatus: {r}")
×
2345
        return blacklist, handled
1✔
2346

2347
    @classmethod
1✔
2348
    def _decode_channel_update_msg(cls, chan_upd_msg: bytes) -> Optional[Dict[str, Any]]:
1✔
2349
        channel_update_as_received = chan_upd_msg
1✔
2350
        channel_update_typed = (258).to_bytes(length=2, byteorder="big") + channel_update_as_received
1✔
2351
        # note: some nodes put channel updates in error msgs with the leading msg_type already there.
2352
        #       we try decoding both ways here.
2353
        try:
1✔
2354
            message_type, payload = decode_msg(channel_update_typed)
1✔
2355
            if payload['chain_hash'] != constants.net.rev_genesis_bytes(): raise Exception()
1✔
2356
            payload['raw'] = channel_update_typed
1✔
2357
            return payload
1✔
2358
        except Exception:  # FIXME: too broad
1✔
2359
            try:
1✔
2360
                message_type, payload = decode_msg(channel_update_as_received)
1✔
2361
                if payload['chain_hash'] != constants.net.rev_genesis_bytes(): raise Exception()
1✔
2362
                payload['raw'] = channel_update_as_received
1✔
2363
                return payload
1✔
2364
            except Exception:
1✔
2365
                return None
1✔
2366

2367
    def _check_bolt11_invoice(self, bolt11_invoice: str, *, amount_msat: int | None = None, max_min_final_cltv_delta=NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE) -> BOLT11Addr:
1✔
2368
        """Parses and validates a bolt11 invoice str into a BOLT11Addr.
2369
        Includes pre-payment checks external to the parser.
2370
        """
2371
        addr = decode_bolt11_invoice(bolt11_invoice)
1✔
2372
        if addr.is_expired():
1✔
2373
            raise InvoiceError(_("This invoice has expired"))
×
2374
        # check amount
2375
        if amount_msat:  # replace amt in invoice. main usecase is paying zero amt invoices
1✔
2376
            existing_amt_msat = addr.get_amount_msat()
×
2377
            if existing_amt_msat and amount_msat < existing_amt_msat:
×
2378
                raise Exception("cannot pay lower amt than what is originally in LN invoice")
×
2379
            addr.amount = Decimal(amount_msat) / COIN / 1000
×
2380
        if addr.amount is None:
1✔
2381
            raise InvoiceError(_("Missing amount"))
×
2382
        # check cltv
2383
        if addr.get_min_final_cltv_delta() > max_min_final_cltv_delta:
1✔
2384
            raise InvoiceError("{}\n{}".format(
1✔
2385
                _("Invoice wants us to risk locking funds for unreasonably long."),
2386
                f"min_final_cltv_delta: {addr.get_min_final_cltv_delta()}"))
2387
        # check features
2388
        addr.validate_and_compare_features(self.features)
1✔
2389
        return addr
1✔
2390

2391
    def is_trampoline_peer(self, node_id: bytes) -> bool:
1✔
2392
        # until trampoline is advertised in lnfeatures, check against hardcoded list
2393
        if is_hardcoded_trampoline(node_id):
1✔
2394
            return True
1✔
2395
        peer = self.lnpeermgr.get_peer_by_pubkey(node_id)
1✔
2396
        if not peer:
1✔
2397
            return False
1✔
2398
        return (peer.their_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR)
1✔
2399
                or peer.their_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM))
2400

2401
    def suggest_peer(self) -> Optional[bytes]:
1✔
2402
        if not self.uses_trampoline():
×
2403
            return self.lnrater.suggest_peer()
×
2404
        else:
2405
            return random.choice(list(hardcoded_trampoline_nodes().values())).pubkey
×
2406

2407
    def suggest_payment_splits(
1✔
2408
        self,
2409
        *,
2410
        amount_msat: int,
2411
        final_total_msat: int,
2412
        my_active_channels: Sequence[Channel],
2413
        invoice_features: LnFeatures,
2414
        r_tags: Sequence[Sequence[Sequence[Any]]],
2415
        receiver_pubkey: bytes,
2416
    ) -> List['SplitConfigRating']:
2417
        channels_with_funds = {
1✔
2418
            (chan.channel_id, chan.node_id): ( int(chan.available_to_spend(HTLCOwner.LOCAL)), chan.htlc_slots_left(HTLCOwner.LOCAL))
2419
            for chan in my_active_channels
2420
        }
2421
        # if we have a direct channel it's preferable to send a single part directly through this
2422
        # channel, so this bool will disable excluding single part payments
2423
        have_direct_channel = any(chan.node_id == receiver_pubkey for chan in my_active_channels)
1✔
2424
        self.logger.info(f"channels_with_funds: {channels_with_funds}, {have_direct_channel=}")
1✔
2425
        exclude_single_part_payments = False
1✔
2426
        if self.uses_trampoline():
1✔
2427
            # in the case of a legacy payment, we don't allow splitting via different
2428
            # trampoline nodes, because of https://github.com/ACINQ/eclair/issues/2127
2429
            is_legacy, _ = is_legacy_relay(invoice_features, r_tags)
1✔
2430
            exclude_multinode_payments = is_legacy
1✔
2431
            # we don't split within a channel when sending to a trampoline node,
2432
            # the trampoline node will split for us
2433
            exclude_single_channel_splits = not self.config.TEST_FORCE_MPP
1✔
2434
        else:
2435
            exclude_multinode_payments = False
1✔
2436
            exclude_single_channel_splits = False
1✔
2437
            if invoice_features.supports(LnFeatures.BASIC_MPP_OPT) and not self.config.TEST_FORCE_DISABLE_MPP:
1✔
2438
                # if amt is still large compared to total_msat, split it:
2439
                if (amount_msat / final_total_msat > self.MPP_SPLIT_PART_FRACTION
1✔
2440
                        and amount_msat > self.MPP_SPLIT_PART_MINAMT_MSAT
2441
                        and not have_direct_channel):
2442
                    exclude_single_part_payments = True
×
2443

2444
        split_configurations = suggest_splits(
1✔
2445
            amount_msat,
2446
            channels_with_funds,
2447
            exclude_single_part_payments=exclude_single_part_payments,
2448
            exclude_multinode_payments=exclude_multinode_payments,
2449
            exclude_single_channel_splits=exclude_single_channel_splits
2450
        )
2451

2452
        self.logger.info(f'suggest_split {amount_msat} returned {len(split_configurations)} configurations')
1✔
2453
        return split_configurations
1✔
2454

2455
    async def create_routes_for_payment(
1✔
2456
            self, *,
2457
            paysession: PaySession,
2458
            amount_msat: int,        # part of payment amount we want routes for now
2459
            fwd_trampoline_onion: OnionPacket = None,
2460
            full_path: LNPaymentPath = None,
2461
            channels: Optional[Sequence[Channel]] = None,
2462
            budget: PaymentFeeBudget,
2463
    ) -> AsyncGenerator[Tuple[SentHtlcInfo, int, Optional[OnionPacket]], None]:
2464

2465
        """Creates multiple routes for splitting a payment over the available
2466
        private channels.
2467

2468
        We first try to conduct the payment over a single channel. If that fails
2469
        and mpp is supported by the receiver, we will split the payment."""
2470
        trampoline_features = LnFeatures.VAR_ONION_OPT
1✔
2471
        local_height = self.wallet.adb.get_local_height()
1✔
2472
        fee_related_error = None  # type: Optional[FeeBudgetExceeded]
1✔
2473
        if channels:
1✔
2474
            my_active_channels = channels
1✔
2475
        else:
2476
            my_active_channels = [
1✔
2477
                chan for chan in self.channels.values() if
2478
                chan.is_active() and not chan.is_frozen_for_sending()]
2479
        # try random order
2480
        random.shuffle(my_active_channels)
1✔
2481
        split_configurations = self.suggest_payment_splits(
1✔
2482
            amount_msat=amount_msat,
2483
            final_total_msat=paysession.amount_to_pay,
2484
            my_active_channels=my_active_channels,
2485
            invoice_features=paysession.invoice_features,
2486
            r_tags=paysession.r_tags,
2487
            receiver_pubkey=paysession.invoice_pubkey,
2488
        )
2489
        for sc in split_configurations:
1✔
2490
            is_mpp = sc.config.number_parts() > 1
1✔
2491
            if is_mpp and not paysession.invoice_features.supports(LnFeatures.BASIC_MPP_OPT):
1✔
2492
                continue
1✔
2493
            if not is_mpp and self.config.TEST_FORCE_MPP:
1✔
2494
                continue
1✔
2495
            if is_mpp and self.config.TEST_FORCE_DISABLE_MPP:
1✔
2496
                continue
×
2497
            self.logger.info(f"trying split configuration: {sc.config.values()} rating: {sc.rating}")
1✔
2498
            routes = []
1✔
2499
            try:
1✔
2500
                is_direct_path = all(node_id == paysession.invoice_pubkey for (chan_id, node_id) in sc.config.keys())
1✔
2501
                if self.uses_trampoline() and not is_direct_path:
1✔
2502
                    if fwd_trampoline_onion:
1✔
2503
                        raise NoPathFound()
1✔
2504
                    per_trampoline_channel_amounts = defaultdict(list)
1✔
2505
                    # categorize by trampoline nodes for trampoline mpp construction
2506
                    for (chan_id, _), part_amounts_msat in sc.config.items():
1✔
2507
                        chan = self._channels[chan_id]
1✔
2508
                        for part_amount_msat in part_amounts_msat:
1✔
2509
                            per_trampoline_channel_amounts[chan.node_id].append((chan_id, part_amount_msat))
1✔
2510
                    # for each trampoline forwarder, construct mpp trampoline
2511
                    for trampoline_node_id, trampoline_parts in per_trampoline_channel_amounts.items():
1✔
2512
                        per_trampoline_amount = sum([x[1] for x in trampoline_parts])
1✔
2513
                        trampoline_route, trampoline_onion, per_trampoline_amount_with_fees, per_trampoline_cltv_delta = create_trampoline_route_and_onion(
1✔
2514
                            amount_msat=per_trampoline_amount,
2515
                            total_msat=paysession.amount_to_pay,
2516
                            min_final_cltv_delta=paysession.min_final_cltv_delta,
2517
                            my_pubkey=self.node_keypair.pubkey,
2518
                            invoice_pubkey=paysession.invoice_pubkey,
2519
                            invoice_features=paysession.invoice_features,
2520
                            node_id=trampoline_node_id,
2521
                            r_tags=paysession.r_tags,
2522
                            payment_hash=paysession.payment_hash,
2523
                            payment_secret=paysession.payment_secret,
2524
                            local_height=local_height,
2525
                            trampoline_fee_level=paysession.trampoline_fee_level,
2526
                            next_trampolines=paysession.next_trampolines.get(trampoline_node_id, {}),
2527
                            failed_routes=paysession.failed_trampoline_routes,
2528
                            budget=budget._replace(fee_msat=budget.fee_msat // len(per_trampoline_channel_amounts)),
2529
                        )
2530
                        # node_features is only used to determine is_tlv
2531
                        per_trampoline_secret = crandom.get_rand_bytes(32)
1✔
2532
                        per_trampoline_fees = per_trampoline_amount_with_fees - per_trampoline_amount
1✔
2533
                        self.logger.info(f'created route with trampoline fee level={paysession.trampoline_fee_level}')
1✔
2534
                        self.logger.info(f'trampoline hops: {[hop.end_node.hex() for hop in trampoline_route]}')
1✔
2535
                        self.logger.info(f'per trampoline fees: {per_trampoline_fees}')
1✔
2536
                        for chan_id, part_amount_msat in trampoline_parts:
1✔
2537
                            chan = self._channels[chan_id]
1✔
2538
                            margin = chan.available_to_spend(LOCAL) - part_amount_msat
1✔
2539
                            delta_fee = min(per_trampoline_fees, margin)
1✔
2540
                            # TODO: distribute trampoline fee over several channels?
2541
                            part_amount_msat_with_fees = part_amount_msat + delta_fee
1✔
2542
                            per_trampoline_fees -= delta_fee
1✔
2543
                            route = [
1✔
2544
                                RouteEdge(
2545
                                    start_node=self.node_keypair.pubkey,
2546
                                    end_node=trampoline_node_id,
2547
                                    short_channel_id=chan.short_channel_id,
2548
                                    fee_base_msat=0,
2549
                                    fee_proportional_millionths=0,
2550
                                    cltv_delta=0,
2551
                                    node_features=trampoline_features)
2552
                            ]
2553
                            self.logger.info(f'adding route {part_amount_msat} {delta_fee} {margin}')
1✔
2554
                            shi = SentHtlcInfo(
1✔
2555
                                route=route,
2556
                                payment_secret_orig=paysession.payment_secret,
2557
                                payment_secret_bucket=per_trampoline_secret,
2558
                                amount_msat=part_amount_msat_with_fees,
2559
                                bucket_msat=per_trampoline_amount_with_fees,
2560
                                amount_receiver_msat=part_amount_msat,
2561
                                trampoline_fee_level=paysession.trampoline_fee_level,
2562
                                trampoline_route=trampoline_route,
2563
                            )
2564
                            routes.append((shi, per_trampoline_cltv_delta, trampoline_onion))
1✔
2565
                        if per_trampoline_fees != 0:
1✔
2566
                            e = 'not enough margin to pay trampoline fee'
×
2567
                            self.logger.info(e)
×
2568
                            raise FeeBudgetExceeded(e)
×
2569
                else:
2570
                    # We atomically loop through a split configuration. If there was
2571
                    # a failure to find a path for a single part, we try the next configuration
2572
                    for (chan_id, _), part_amounts_msat in sc.config.items():
1✔
2573
                        for part_amount_msat in part_amounts_msat:
1✔
2574
                            channel = self._channels[chan_id]
1✔
2575
                            if is_direct_path:
1✔
2576
                                route = self.create_direct_route(
1✔
2577
                                    amount_msat=part_amount_msat,
2578
                                    channel=channel,
2579
                                )
2580
                            else:
2581
                                assert not self.uses_trampoline()
1✔
2582
                                route = await run_in_thread(partial(
1✔
2583
                                    self.create_route_for_single_htlc,
2584
                                    amount_msat=part_amount_msat,
2585
                                    invoice_pubkey=paysession.invoice_pubkey,
2586
                                    r_tags=paysession.r_tags,
2587
                                    invoice_features=paysession.invoice_features,
2588
                                    my_sending_channels=[channel] if is_mpp else my_active_channels,
2589
                                    full_path=full_path,
2590
                                ))
2591
                            if not is_route_within_budget(
1✔
2592
                                    route, budget=budget._replace(fee_msat=budget.fee_msat // sc.config.number_parts()),
2593
                                    amount_msat_for_dest=part_amount_msat,
2594
                                    cltv_delta_for_dest=paysession.min_final_cltv_delta):
2595
                                self.logger.info(f"rejecting route (exceeds budget): {route=}. {budget=}")
1✔
2596
                                raise FeeBudgetExceeded()
1✔
2597
                            shi = SentHtlcInfo(
1✔
2598
                                route=route,
2599
                                payment_secret_orig=paysession.payment_secret,
2600
                                payment_secret_bucket=paysession.payment_secret,
2601
                                amount_msat=part_amount_msat,
2602
                                bucket_msat=paysession.amount_to_pay,
2603
                                amount_receiver_msat=part_amount_msat,
2604
                                trampoline_fee_level=None,
2605
                                trampoline_route=None,
2606
                            )
2607
                            routes.append((shi, paysession.min_final_cltv_delta, fwd_trampoline_onion))
1✔
2608
            except NoPathFound:
1✔
2609
                continue
1✔
2610
            except FeeBudgetExceeded as e:
1✔
2611
                fee_related_error = e
1✔
2612
                continue
1✔
2613
            for route in routes:
1✔
2614
                yield route
1✔
2615
            return
1✔
2616
        if fee_related_error is not None:
1✔
2617
            raise fee_related_error
1✔
2618
        raise NoPathFound()
1✔
2619

2620
    def create_direct_route(
1✔
2621
            self, *,
2622
            amount_msat: int,  # that final receiver gets
2623
            channel: Channel,
2624
    ) -> LNPaymentRoute:
2625
        self.logger.info(f'create_direct_route {channel.node_id.hex()}')
1✔
2626
        my_sending_channels = {channel.short_channel_id: channel}
1✔
2627
        channel_policy = get_mychannel_policy(
1✔
2628
            short_channel_id=channel.short_channel_id,
2629
            node_id=self.node_keypair.pubkey,
2630
            my_channels=my_sending_channels)
2631
        fee_base_msat = channel_policy.fee_base_msat
1✔
2632
        fee_proportional_millionths = channel_policy.fee_proportional_millionths
1✔
2633
        cltv_delta = channel_policy.cltv_delta
1✔
2634
        route_edge = RouteEdge(
1✔
2635
            start_node=self.node_keypair.pubkey,
2636
            end_node=channel.node_id,
2637
            short_channel_id=channel.short_channel_id,
2638
            fee_base_msat=fee_base_msat,
2639
            fee_proportional_millionths=fee_proportional_millionths,
2640
            cltv_delta=cltv_delta,
2641
            node_features=0)
2642
        route = [route_edge]
1✔
2643
        return route
1✔
2644

2645
    @profiler
1✔
2646
    def create_route_for_single_htlc(
1✔
2647
            self, *,
2648
            amount_msat: int,  # that final receiver gets
2649
            invoice_pubkey: bytes,
2650
            r_tags,
2651
            invoice_features: int,
2652
            my_sending_channels: List[Channel],
2653
            full_path: Optional[LNPaymentPath],
2654
    ) -> LNPaymentRoute:
2655

2656
        my_sending_aliases = set(chan.get_local_scid_alias() for chan in my_sending_channels)
1✔
2657
        my_sending_channels = {chan.short_channel_id: chan for chan in my_sending_channels
1✔
2658
            if chan.short_channel_id is not None}
2659
        # Collect all private edges from route hints.
2660
        # Note: if some route hints are multiple edges long, and these paths cross each other,
2661
        #       we allow our path finding to cross the paths; i.e. the route hints are not isolated.
2662
        private_route_edges = {}  # type: Dict[ShortChannelID, RouteEdge]
1✔
2663
        for private_path in r_tags:
1✔
2664
            # we need to shift the node pubkey by one towards the destination:
2665
            private_path_nodes = [edge[0] for edge in private_path][1:] + [invoice_pubkey]
1✔
2666
            private_path_rest = [edge[1:] for edge in private_path]
1✔
2667
            start_node = private_path[0][0]
1✔
2668
            # remove aliases from direct routes
2669
            if len(private_path) == 1 and private_path[0][1] in my_sending_aliases:
1✔
2670
                self.logger.info(f'create_route: skipping alias {ShortChannelID(private_path[0][1])}')
×
2671
                continue
×
2672
            for end_node, edge_rest in zip(private_path_nodes, private_path_rest):
1✔
2673
                short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_delta = edge_rest
1✔
2674
                short_channel_id = ShortChannelID(short_channel_id)
1✔
2675
                our_chan = self.get_channel_by_short_id(short_channel_id)
1✔
2676
                if our_chan is not None and start_node == self.node_keypair.pubkey:
1✔
2677
                    # check if the channel is one of our channels and frozen for sending
2678
                    if our_chan.is_frozen_for_sending():
1✔
2679
                        continue
×
2680
                # if we have a routing policy for this edge in the db, that takes precedence,
2681
                # as it is likely from a previous failure
2682
                channel_policy = self.channel_db.get_policy_for_node(
1✔
2683
                    short_channel_id=short_channel_id,
2684
                    node_id=start_node,
2685
                    my_channels=my_sending_channels)
2686
                if channel_policy:
1✔
2687
                    fee_base_msat = channel_policy.fee_base_msat
1✔
2688
                    fee_proportional_millionths = channel_policy.fee_proportional_millionths
1✔
2689
                    cltv_delta = channel_policy.cltv_delta
1✔
2690
                node_info = self.channel_db.get_node_info_for_node_id(node_id=end_node)
1✔
2691
                route_edge = RouteEdge(
1✔
2692
                        start_node=start_node,
2693
                        end_node=end_node,
2694
                        short_channel_id=short_channel_id,
2695
                        fee_base_msat=fee_base_msat,
2696
                        fee_proportional_millionths=fee_proportional_millionths,
2697
                        cltv_delta=cltv_delta,
2698
                        node_features=node_info.features if node_info else 0)
2699
                private_route_edges[route_edge.short_channel_id] = route_edge
1✔
2700
                start_node = end_node
1✔
2701
        # now find a route, end to end: between us and the recipient
2702
        try:
1✔
2703
            route = self.network.path_finder.find_route(
1✔
2704
                nodeA=self.node_keypair.pubkey,
2705
                nodeB=invoice_pubkey,
2706
                invoice_amount_msat=amount_msat,
2707
                path=full_path,
2708
                my_sending_channels=my_sending_channels,
2709
                private_route_edges=private_route_edges)
2710
        except NoChannelPolicy as e:
1✔
2711
            raise NoPathFound() from e
×
2712
        if not route:
1✔
2713
            raise NoPathFound()
1✔
2714
        assert len(route) > 0
1✔
2715
        if route[-1].end_node != invoice_pubkey:
1✔
2716
            raise LNPathInconsistent("last node_id != invoice pubkey")
1✔
2717
        # add features from invoice
2718
        route[-1].node_features |= invoice_features
1✔
2719
        return route
1✔
2720

2721
    def _prepare_invoice_features(self, base_features: LnFeatures, *, amount_msat: Optional[int]) -> LnFeatures:
1✔
2722
        if not all((not c.is_open() or c.is_frozen_for_receiving()) or self.is_trampoline_peer(c.node_id) \
1✔
2723
                        for c in self.channels.values()):
2724
            base_features &= ~ LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM
1✔
2725
        needs_jit: bool = self.receive_requires_jit_channel(amount_msat)
1✔
2726
        if needs_jit:
1✔
2727
            # jit only works with single htlcs, mpp will cause LSP to open channels for each htlc
2728
            base_features &= ~ LnFeatures.BASIC_MPP_OPT & ~ LnFeatures.BASIC_MPP_REQ
×
2729
        return base_features
1✔
2730

2731
    def clear_invoices_cache(self):
1✔
2732
        self._bolt11_cache.clear()
1✔
2733

2734
    def get_bolt11_invoice(
1✔
2735
            self, *,
2736
            payment_info: PaymentInfo,
2737
            message: str,
2738
            fallback_address: Optional[str],
2739
            channels: Optional[Sequence[Channel]] = None,
2740
    ) -> Tuple[BOLT11Addr, str]:
2741
        amount_msat = payment_info.amount_msat
1✔
2742
        pair = self._bolt11_cache.get(payment_info.payment_hash)
1✔
2743
        if pair:
1✔
2744
            lnaddr, invoice = pair
×
2745
            assert lnaddr.get_amount_msat() == amount_msat
×
2746
            return pair
×
2747

2748
        assert amount_msat is None or amount_msat > 0
1✔
2749
        timestamp = int(time.time())
1✔
2750
        routing_hints = self.calc_routing_hints_for_invoice(
1✔
2751
            amount_msat,
2752
            channels=channels,
2753
            # if the invoice_features signal trampoline support all included r_tags should support trampoline forwarding
2754
            # TODO: make invoice_features dynamic depending on available trampoline channels
2755
            only_trampoline=payment_info.invoice_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM),
2756
        )
2757
        formatted_r_hints = BOLT11Addr.format_bolt11_routing_info_as_human_readable(routing_hints, has_explicit_r_tagtype=True)
1✔
2758
        self.logger.info(f"creating bolt11 invoice with routing_hints: {formatted_r_hints}, sat: {(amount_msat or 0) // 1000}")
1✔
2759
        payment_secret = self.get_payment_secret(payment_info.payment_hash)
1✔
2760
        amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None
1✔
2761
        min_final_cltv_delta = payment_info.min_final_cltv_delta + MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE
1✔
2762
        lnaddr = BOLT11Addr(
1✔
2763
            paymenthash=payment_info.payment_hash,
2764
            amount=amount_btc,
2765
            tags=[
2766
                ('d', message),
2767
                ('c', min_final_cltv_delta),
2768
                ('x', payment_info.expiry_delay),
2769
                ('9', payment_info.invoice_features),
2770
                ('f', fallback_address),
2771
            ] + routing_hints,
2772
            date=timestamp,
2773
            payment_secret=payment_secret)
2774
        invoice = encode_bolt11_invoice(lnaddr, self.node_keypair.privkey)
1✔
2775
        pair = lnaddr, invoice
1✔
2776
        self._bolt11_cache[payment_info.payment_hash] = pair
1✔
2777
        return pair
1✔
2778

2779
    def get_payment_secret(self, payment_hash):
1✔
2780
        return sha256(sha256(self.payment_secret_key) + payment_hash)
1✔
2781

2782
    def _get_payment_key(self, payment_hash: bytes) -> bytes:
1✔
2783
        """Return payment bucket key.
2784
        We bucket htlcs based on payment_hash+payment_secret. payment_secret is included
2785
        as it changes over a trampoline path (in the outer onion), and these paths can overlap.
2786
        """
2787
        payment_secret = self.get_payment_secret(payment_hash)
1✔
2788
        return payment_hash + payment_secret
1✔
2789

2790
    def create_payment_info(
1✔
2791
        self, *,
2792
        amount_msat: Optional[int],
2793
        min_final_cltv_delta: Optional[int] = None,
2794
        exp_delay: int = LN_EXPIRY_NEVER,
2795
        write_to_disk=True
2796
    ) -> bytes:
2797
        if amount_msat == 0:
1✔
2798
            raise ValueError("amount_msat must not be 0. Use None instead.")
1✔
2799
        payment_preimage = crandom.get_rand_bytes(32)
1✔
2800
        payment_hash = sha256(payment_preimage)
1✔
2801
        min_final_cltv_delta = min_final_cltv_delta or MIN_FINAL_CLTV_DELTA_ACCEPTED
1✔
2802
        invoice_features = self._prepare_invoice_features(self.features.for_bolt11_invoice(), amount_msat=amount_msat)
1✔
2803
        info = PaymentInfo(
1✔
2804
            payment_hash=payment_hash,
2805
            amount_msat=amount_msat,
2806
            direction=RECEIVED,
2807
            status=PR_UNPAID,
2808
            min_final_cltv_delta=min_final_cltv_delta,
2809
            expiry_delay=exp_delay or LN_EXPIRY_NEVER,
2810
            invoice_features=invoice_features,
2811
        )
2812
        self.save_preimage(payment_hash, payment_preimage, write_to_disk=False)
1✔
2813
        self.save_payment_info(info, write_to_disk=False)
1✔
2814
        if write_to_disk:
1✔
2815
            self.wallet.save_db()
1✔
2816
        return payment_hash
1✔
2817

2818
    def bundle_payments(self, hash_list: Sequence[bytes]) -> None:
1✔
2819
        """Bundle together a list of payment_hashes, for atomicity, so that either
2820
        - all gets fulfilled, or
2821
        - none of them gets fulfilled.
2822
        (we are the recipient of this payment)
2823
        note: payment bundles are kept only in-memory. if the process restarts the bundle is dissolved and the
2824
              payments with known preimage will get settled immediately independent of the other parts status.
2825
              For swaps specifically this is fine as only swapservers receive a (bundled) trusted prepayment. Swapservers
2826
              are long-running daemon and the risk of them restarting mid-swap and claiming a prepayment for an
2827
              otherwise failing swap is negligible.
2828
        """
2829
        payment_keys = [self._get_payment_key(x) for x in hash_list]
1✔
2830
        with self.lock:
1✔
2831
            # We maintain two maps.
2832
            #   map1: payment_key -> bundle_key=canon_pkey (canonically smallest among pkeys)
2833
            #   map2: bundle_key -> list of pkeys in bundle
2834
            # assumption: bundles are immutable, so no adding extra pkeys after-the-fact
2835
            canon_pkey = min(payment_keys)
1✔
2836
            for pkey in payment_keys:
1✔
2837
                assert pkey not in self._payment_bundles_pkey_to_canon
1✔
2838
            for pkey in payment_keys:
1✔
2839
                self._payment_bundles_pkey_to_canon[pkey] = canon_pkey
1✔
2840
            self._payment_bundles_canon_to_pkeylist[canon_pkey] = tuple(payment_keys)
1✔
2841

2842
    def has_payment_bundle(self, payment_hash: bytes) -> bool:
1✔
2843
        return bool(self.get_payment_bundle(self._get_payment_key(payment_hash)))
×
2844

2845
    def get_payment_bundle(self, payment_key: Union[bytes, str]) -> Sequence[bytes]:
1✔
2846
        with self.lock:
1✔
2847
            if isinstance(payment_key, str):
1✔
2848
                try:
1✔
2849
                    payment_key = bytes.fromhex(payment_key)
1✔
2850
                except ValueError:
×
2851
                    # might be a forwarding payment_key which is not hex and will never have a bundle
2852
                    return []
×
2853
            canon_pkey =  self._payment_bundles_pkey_to_canon.get(payment_key)
1✔
2854
            if canon_pkey is None:
1✔
2855
                return []
1✔
2856
            return self._payment_bundles_canon_to_pkeylist[canon_pkey]
1✔
2857

2858
    def is_payment_bundle_complete(self, any_payment_key: str) -> bool:
1✔
2859
        """
2860
        complete means a htlc set is available for each payment key of the payment bundle and
2861
        all htlc sets have a resolution >= COMPLETE (we got the whole payment bundle amount)
2862
        """
2863
        # get all payment keys covered by this bundle
2864
        bundle_payment_keys = self.get_payment_bundle(any_payment_key)
1✔
2865
        if not bundle_payment_keys:  # there is no payment bundle
1✔
2866
            return True
1✔
2867
        for payment_key in bundle_payment_keys:
1✔
2868
            mpp_set = self.received_mpp_htlcs.get(payment_key.hex())
1✔
2869
            if mpp_set is None:
1✔
2870
                # payment bundle is missing htlc set for payment request
2871
                # it might have already been failed and deleted
2872
                return False
1✔
2873
            elif mpp_set.resolution not in (RecvMPPResolution.COMPLETE, RecvMPPResolution.SETTLING):
1✔
2874
                return False
1✔
2875
        return True
1✔
2876

2877
    def delete_payment_bundle(
1✔
2878
        self, *,
2879
        payment_hash: Optional[bytes] = None,
2880
        payment_key: Optional[bytes] = None,
2881
    ) -> None:
2882
        assert (payment_hash is not None) ^ (payment_key is not None), \
1✔
2883
                    "must provide exactly one of (payment_hash, payment_key)"
2884
        if not payment_key:
1✔
2885
            payment_key = self._get_payment_key(payment_hash)
1✔
2886
        with self.lock:
1✔
2887
            canon_pkey = self._payment_bundles_pkey_to_canon.get(payment_key)
1✔
2888
            if canon_pkey is None:  # is it ok for bundle to be missing??
1✔
2889
                return
1✔
2890
            pkey_list = self._payment_bundles_canon_to_pkeylist[canon_pkey]
1✔
2891
            for pkey in pkey_list:
1✔
2892
                del self._payment_bundles_pkey_to_canon[pkey]
1✔
2893
            del self._payment_bundles_canon_to_pkeylist[canon_pkey]
1✔
2894

2895
    def save_preimage(
1✔
2896
        self,
2897
        payment_hash: bytes,
2898
        preimage: bytes,
2899
        *,
2900
        write_to_disk: bool = True,
2901
        mark_as_public: bool = False,  # see is_preimage_public
2902
    ):
2903
        assert isinstance(payment_hash, bytes), f"expected bytes, but got {type(payment_hash)}"
1✔
2904
        assert isinstance(preimage, bytes), f"expected bytes, but got {type(preimage)}"
1✔
2905
        if sha256(preimage) != payment_hash:
1✔
2906
            raise Exception("tried to save incorrect preimage for payment_hash")
×
2907
        old_tuple = _, old_is_public = self._preimages.get(payment_hash.hex(), (None, False))
1✔
2908
        mark_as_public |= old_is_public  # disallow True->False transition
1✔
2909
        # sanity checks and conversions done.
2910
        new_tuple = preimage.hex(), mark_as_public
1✔
2911
        if old_tuple == new_tuple:  # no change
1✔
2912
            return
1✔
2913
        self.logger.debug(f"saving preimage for {payment_hash.hex()} (public={mark_as_public})")
1✔
2914
        self._preimages[payment_hash.hex()] = new_tuple
1✔
2915
        if write_to_disk:
1✔
2916
            self.wallet.save_db()
1✔
2917

2918
    def get_preimage(self, payment_hash: bytes) -> Optional[bytes]:
1✔
2919
        assert isinstance(payment_hash, bytes), f"expected bytes, but got {type(payment_hash)}"
1✔
2920
        preimage_hex, _ = self._preimages.get(payment_hash.hex(), (None, None))
1✔
2921
        if preimage_hex is None:
1✔
2922
            return None
1✔
2923
        preimage_bytes = bytes.fromhex(preimage_hex)
1✔
2924
        if sha256(preimage_bytes) != payment_hash:
1✔
2925
            raise Exception("found incorrect preimage for payment_hash")
×
2926
        return preimage_bytes
1✔
2927

2928
    def get_preimage_hex(self, payment_hash: str) -> Optional[str]:
1✔
2929
        preimage_bytes = self.get_preimage(bytes.fromhex(payment_hash)) or b""
1✔
2930
        return preimage_bytes.hex() or None
1✔
2931

2932
    def is_preimage_public(self, payment_hash: bytes) -> bool:
1✔
2933
        """If another LN node knows a preimage besides us, we consider it public.
2934
        If a preimage is public, it is safe to reveal it in an arbitrary context.
2935

2936
        For example, if there is a pending incoming partial MPP for an invoice we created,
2937
        we must not reveal the preimage, otherwise we will get paid less than invoice amount.
2938
        What if there is a force-close around that time? When is it safe to reveal the preimage on-chain?
2939
        e.g. if we already revealed the preimage either offchain or onchain, it is fine to reveal it again.
2940
        """
2941
        assert isinstance(payment_hash, bytes), f"expected bytes, but got {type(payment_hash)}"
1✔
2942
        preimage_hex, is_public = self._preimages.get(payment_hash.hex(), (None, None))
1✔
2943
        return bool(is_public)
1✔
2944

2945
    def get_payment_info(self, payment_hash: bytes, *, direction: lnutil.Direction) -> Optional[PaymentInfo]:
1✔
2946
        """returns None if payment_hash is a payment we are forwarding"""
2947
        key = PaymentInfo.calc_db_key(payment_hash_hex=payment_hash.hex(), direction=direction)
1✔
2948
        with self.lock:
1✔
2949
            if key in self.payment_info:
1✔
2950
                stored_tuple = self.payment_info[key]
1✔
2951
                amount_msat, status, min_final_cltv_delta, expiry_delay, creation_ts, invoice_features = stored_tuple
1✔
2952
                return PaymentInfo(
1✔
2953
                    payment_hash=payment_hash,
2954
                    amount_msat=amount_msat,
2955
                    direction=direction,
2956
                    status=status,
2957
                    min_final_cltv_delta=min_final_cltv_delta,
2958
                    expiry_delay=expiry_delay,
2959
                    creation_ts=creation_ts,
2960
                    invoice_features=LnFeatures(invoice_features),
2961
                )
2962
            return None
1✔
2963

2964
    def add_payment_info_for_hold_invoice(
1✔
2965
        self,
2966
        payment_hash: bytes, *,
2967
        lightning_amount_sat: Optional[int],
2968
        min_final_cltv_delta: int,
2969
        exp_delay: int,
2970
    ):
2971
        amount_msat = lightning_amount_sat * 1000 if lightning_amount_sat else None
1✔
2972
        info = PaymentInfo(
1✔
2973
            payment_hash=payment_hash,
2974
            amount_msat=amount_msat,
2975
            direction=RECEIVED,
2976
            status=PR_UNPAID,
2977
            min_final_cltv_delta=min_final_cltv_delta,
2978
            expiry_delay=exp_delay,
2979
            invoice_features=self._prepare_invoice_features(self.features.for_bolt11_invoice(), amount_msat=amount_msat),
2980
        )
2981
        self.save_payment_info(info, write_to_disk=False)
1✔
2982

2983
    def register_hold_invoice(self, payment_hash: bytes, cb: Callable[[bytes], Awaitable[None]]):
1✔
2984
        assert self.get_preimage(payment_hash) is None, "hold invoice cb won't get called if preimage is already set"
1✔
2985
        self.hold_invoice_callbacks[payment_hash] = cb
1✔
2986

2987
    def unregister_hold_invoice(self, payment_hash: bytes):
1✔
2988
        self.hold_invoice_callbacks.pop(payment_hash, None)
1✔
2989
        payment_key = self._get_payment_key(payment_hash).hex()
1✔
2990
        if payment_key in self.received_mpp_htlcs:
1✔
2991
            if self.get_preimage(payment_hash) is None:
1✔
2992
                # the pending mpp set can be failed as we don't have the preimage to settle it
2993
                self.set_mpp_resolution(payment_key, RecvMPPResolution.FAILED)
1✔
2994

2995
    def save_payment_info(self, info: PaymentInfo, *, write_to_disk: bool = True) -> None:
1✔
2996
        assert info.status in SAVED_PR_STATUS
1✔
2997
        with self.lock:
1✔
2998
            if old_info := self.get_payment_info(payment_hash=info.payment_hash, direction=info.direction):
1✔
2999
                if info == old_info:
1✔
3000
                    return  # already saved
1✔
3001
                if info.direction == SENT and old_info.status in (PR_UNPAID, PR_FAILED):
1✔
3002
                    # allow saving of newer PaymentInfo if it is a sending attempt and the previous
3003
                    # payment failed or was not yet attempted
3004
                    old_info = dataclasses.replace(
1✔
3005
                        old_info,
3006
                        creation_ts=info.creation_ts,
3007
                        status=info.status,
3008
                        amount_msat=info.amount_msat,  # might retrying to pay 0 amount invoice
3009
                    )
3010
                if info != dataclasses.replace(old_info, status=info.status):
1✔
3011
                    # differs more than in status. let's fail
3012
                    raise Exception(f"payment_hash already in use: {info=} != {old_info=}")
×
3013
            v = info.amount_msat, info.status, info.min_final_cltv_delta, info.expiry_delay, info.creation_ts, int(info.invoice_features)
1✔
3014
            self.payment_info[info.db_key] = v
1✔
3015
        if write_to_disk:
1✔
3016
            self.wallet.save_db()
1✔
3017

3018
    def update_or_create_mpp_with_received_htlc(
1✔
3019
        self,
3020
        *,
3021
        payment_key: str,
3022
        channel_id: bytes,
3023
        htlc: UpdateAddHtlc,
3024
        unprocessed_onion_packet: str,
3025
    ):
3026
        # Payment key creation:
3027
        #   * for regular forwarded htlcs -> "scid.hex() + ':%d' % htlc_id" [htlc key]
3028
        #   * for trampoline forwarding -> "payment hash + payment secret from outer onion"
3029
        #   * for final non-trampoline htlcs (we are receiver) -> "payment hash + payment secret from onion"
3030
        #   * for final trampoline htlcs (we are receiver) -> 2. step grouping:
3031
        #           1. grouping of htlcs by "payments hash + outer onion payment secret", a 'multi-trampoline mpp part'.
3032
        #           2. once the set of step 1. is COMPLETE (amount_fwd outer onion >= total_amt outer onion)
3033
        #              the htlcs get moved to the parent mpp set (created once first part is complete) grouped by:
3034
        #              "payment_hash + inner onion payment secret (the one in the invoice)"
3035
        #              After moving the htlcs the first set gets deleted.
3036
        #
3037
        # Add the validated htlc to the htlc set associated with the payment key.
3038
        # If no set exists, a new set in WAITING state is created.
3039
        mpp_status = self.received_mpp_htlcs.get(payment_key)
1✔
3040
        if mpp_status is None:
1✔
3041
            self.logger.debug(f"creating new mpp set for {payment_key=}")
1✔
3042
            mpp_status = ReceivedMPPStatus(
1✔
3043
                resolution=RecvMPPResolution.WAITING,
3044
                htlcs=frozenset(),
3045
            )
3046

3047
        if mpp_status.resolution > RecvMPPResolution.WAITING:
1✔
3048
            # we are getting a htlc for a set that is not in WAITING state, it cannot be safely added
3049
            self.logger.info(f"htlc set cannot accept htlc, failing htlc: {channel_id=} {htlc.htlc_id=}")
×
3050
            if mpp_status.resolution == RecvMPPResolution.EXPIRED:
×
3051
                raise OnionRoutingFailure(code=OnionFailureCode.MPP_TIMEOUT, data=b'')
×
3052
            raise OnionRoutingFailure(
×
3053
                code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
3054
                data=htlc.amount_msat.to_bytes(8, byteorder="big"),
3055
            )
3056

3057
        new_htlc = ReceivedMPPHtlc(
1✔
3058
            channel_id=channel_id,
3059
            htlc=htlc,
3060
            unprocessed_onion=unprocessed_onion_packet,
3061
        )
3062
        assert new_htlc not in mpp_status.htlcs, "each htlc should make it here only once?"
1✔
3063
        assert isinstance(unprocessed_onion_packet, str)
1✔
3064
        new_htlcs = set(mpp_status.htlcs)
1✔
3065
        new_htlcs.add(new_htlc)
1✔
3066
        self.received_mpp_htlcs[payment_key] = mpp_status._replace(htlcs=frozenset(new_htlcs))
1✔
3067

3068
    def set_mpp_resolution(self, payment_key: str, new_resolution: RecvMPPResolution) -> ReceivedMPPStatus:
1✔
3069
        mpp_status = self.received_mpp_htlcs[payment_key]
1✔
3070
        if mpp_status.resolution == new_resolution:
1✔
3071
            return mpp_status
1✔
3072
        if not (mpp_status.resolution, new_resolution) in lnutil.allowed_mpp_set_transitions:
1✔
3073
            raise ValueError(f'forbidden mpp set transition: {mpp_status.resolution} -> {new_resolution}')
×
3074
        self.logger.info(f'set_mpp_resolution {new_resolution.name} {len(mpp_status.htlcs)=}: {payment_key=}')
1✔
3075
        self.received_mpp_htlcs[payment_key] = mpp_status._replace(resolution=new_resolution)
1✔
3076
        self.wallet.save_db()
1✔
3077
        return self.received_mpp_htlcs[payment_key]
1✔
3078

3079
    def set_htlc_set_error(
1✔
3080
        self,
3081
        payment_key: str,
3082
        error: Union[bytes, OnionFailureCode, OnionRoutingFailure],
3083
    ) -> Optional[Tuple[Optional[bytes], Optional[OnionFailureCode | int], Optional[bytes]]]:
3084
        """
3085
        handles different types of errors and sets the htlc set to failed, then returns a more
3086
        structured tuple of error types which can then be used to fail the htlc set
3087
        """
3088
        htlc_set = self.received_mpp_htlcs[payment_key]
1✔
3089
        assert htlc_set.resolution != RecvMPPResolution.SETTLING
1✔
3090
        raw_error, error_code, error_data = None, None, None
1✔
3091
        if isinstance(error, bytes):
1✔
3092
            raw_error = error
1✔
3093
        elif isinstance(error, OnionFailureCode):
1✔
3094
            error_code = error
1✔
3095
        elif isinstance(error, OnionRoutingFailure):
1✔
3096
            error_code, error_data = OnionFailureCode.from_int(error.code), error.data
1✔
3097
        else:
3098
            raise ValueError(f"invalid error type: {repr(error)}")
×
3099

3100
        if error_code == OnionFailureCode.MPP_TIMEOUT:
1✔
3101
            self.set_mpp_resolution(payment_key=payment_key, new_resolution=RecvMPPResolution.EXPIRED)
1✔
3102
        else:
3103
            self.set_mpp_resolution(payment_key=payment_key, new_resolution=RecvMPPResolution.FAILED)
1✔
3104

3105
        return raw_error, error_code, error_data
1✔
3106

3107
    def get_mpp_resolution(self, payment_hash: bytes) -> Optional[RecvMPPResolution]:
1✔
3108
        payment_key = self._get_payment_key(payment_hash)
1✔
3109
        status = self.received_mpp_htlcs.get(payment_key.hex())
1✔
3110
        return status.resolution if status else None
1✔
3111

3112
    def is_complete_mpp(self, payment_hash: bytes) -> bool:
1✔
3113
        resolution = self.get_mpp_resolution(payment_hash)
1✔
3114
        if resolution is not None:
1✔
3115
            return resolution in (RecvMPPResolution.COMPLETE, RecvMPPResolution.SETTLING)
1✔
3116
        return False
1✔
3117

3118
    def get_payment_mpp_amount_msat(self, payment_hash: bytes) -> Optional[int]:
1✔
3119
        """Returns the received mpp amount for given payment hash."""
3120
        payment_key = self._get_payment_key(payment_hash)
1✔
3121
        total_msat = self.get_mpp_amounts(payment_key)
1✔
3122
        if not total_msat:
1✔
3123
            return None
1✔
3124
        return total_msat
1✔
3125

3126
    def get_mpp_amounts(self, payment_key: bytes) -> Optional[int]:
1✔
3127
        """Returns total received amount or None."""
3128
        mpp_status = self.received_mpp_htlcs.get(payment_key.hex())
1✔
3129
        if not mpp_status:
1✔
3130
            return None
1✔
3131
        total = sum([mpp_htlc.htlc.amount_msat for mpp_htlc in mpp_status.htlcs])
1✔
3132
        return total
1✔
3133

3134
    def maybe_cleanup_mpp(
1✔
3135
            self,
3136
            chan: Channel,
3137
    ) -> None:
3138
        """
3139
        Remove all remaining mpp htlcs of the given channel after closing.
3140
        Usually they get removed in htlc_switch after all htlcs of the set are resolved,
3141
        however if there is a force close with pending htlcs they need to be removed after the channel
3142
        is closed.
3143
        """
3144
        # only cleanup when channel is REDEEMED as mpp set is still required for lnsweep
3145
        assert chan._state == ChannelState.REDEEMED
1✔
3146
        for payment_key_hex, mpp_status in list(self.received_mpp_htlcs.items()):
1✔
3147
            htlcs_to_remove = [htlc for htlc in mpp_status.htlcs if htlc.channel_id == chan.channel_id]
×
3148
            new_htlcs = set(mpp_status.htlcs)
×
3149
            for stale_mpp_htlc in htlcs_to_remove:
×
3150
                assert mpp_status.resolution != RecvMPPResolution.WAITING
×
3151
                self.logger.info(f'maybe_cleanup_mpp: removing htlc of MPP {payment_key_hex}')
×
3152
                new_htlcs.remove(stale_mpp_htlc)
×
3153
            if htlcs_to_remove:
×
3154
                mpp_status = mpp_status._replace(htlcs=frozenset(new_htlcs))
×
3155
                self.received_mpp_htlcs[payment_key_hex] = mpp_status  # save changes to db
×
3156
            if len(mpp_status.htlcs) == 0:
×
3157
                self.logger.info(f'maybe_cleanup_mpp: removing mpp {payment_key_hex}')
×
3158
                del self.received_mpp_htlcs[payment_key_hex]
×
3159
                self.maybe_cleanup_forwarding(payment_key_hex)
×
3160

3161
    def maybe_cleanup_forwarding(self, payment_key_hex: str) -> None:
1✔
3162
        self.active_forwardings.pop(payment_key_hex, None)
1✔
3163
        self.forwarding_failures.pop(payment_key_hex, None)
1✔
3164

3165
    def get_payment_status(self, payment_hash: bytes, *, direction: lnutil.Direction) -> int:
1✔
3166
        info = self.get_payment_info(payment_hash, direction=direction)
1✔
3167
        return info.status if info else PR_UNPAID
1✔
3168

3169
    def get_invoice_status(self, invoice: BaseInvoice) -> int:
1✔
3170
        invoice_id = invoice.rhash
1✔
3171
        assert isinstance(invoice, (Request, Invoice)), type(invoice)
1✔
3172
        direction = RECEIVED if isinstance(invoice, Request) else SENT
1✔
3173
        status = self.get_payment_status(bfh(invoice_id), direction=direction)
1✔
3174
        if status == PR_UNPAID and invoice_id in self.inflight_payments:
1✔
3175
            return PR_INFLIGHT
1✔
3176
        # status may be PR_FAILED
3177
        if status == PR_UNPAID and invoice_id in self.logs:
1✔
3178
            status = PR_FAILED
1✔
3179
        return status
1✔
3180

3181
    def set_invoice_status(self, key: str, status: int) -> None:
1✔
3182
        if status == PR_INFLIGHT:
1✔
3183
            self.inflight_payments.add(key)
1✔
3184
        elif key in self.inflight_payments:
1✔
3185
            self.inflight_payments.remove(key)
1✔
3186
        if status in SAVED_PR_STATUS:
1✔
3187
            self.set_payment_status(bfh(key), status, direction=SENT)
1✔
3188
        util.trigger_callback('invoice_status', self.wallet, key, status)
1✔
3189
        self.logger.info(f"set_invoice_status {key}: {status}")
1✔
3190
        # liquidity changed
3191
        self.clear_invoices_cache()
1✔
3192

3193
    def set_request_status(self, payment_hash: bytes, status: int) -> None:
1✔
3194
        if self.get_payment_status(payment_hash, direction=RECEIVED) == status:
1✔
3195
            return
1✔
3196
        self.set_payment_status(payment_hash, status, direction=RECEIVED)
1✔
3197
        request_id = payment_hash.hex()
1✔
3198
        req = self.wallet.get_request(request_id)
1✔
3199
        if req is None:
1✔
3200
            return
1✔
3201
        util.trigger_callback('request_status', self.wallet, request_id, status)
1✔
3202

3203
    def set_payment_status(self, payment_hash: bytes, status: int, *, direction: lnutil.Direction) -> None:
1✔
3204
        info = self.get_payment_info(payment_hash, direction=direction)
1✔
3205
        if info is None:
1✔
3206
            # if we are forwarding
3207
            return
1✔
3208
        info = dataclasses.replace(info, status=status)
1✔
3209
        self.save_payment_info(info)
1✔
3210

3211
    def is_forwarded_htlc(self, htlc_key) -> Optional[str]:
1✔
3212
        """Returns whether this was a forwarded HTLC."""
3213
        for payment_key, htlcs in self.active_forwardings.items():
1✔
3214
            if htlc_key in htlcs:
1✔
3215
                return payment_key
1✔
3216
        return None
1✔
3217

3218
    def notify_upstream_peer(self, htlc_key: str) -> None:
1✔
3219
        """Called when an HTLC we offered on chan gets irrevocably fulfilled or failed.
3220
        If we find this was a forwarded HTLC, the upstream peer is notified.
3221
        """
3222
        upstream_key = self.downstream_to_upstream_htlc.pop(htlc_key, None)
1✔
3223
        if not upstream_key:
1✔
3224
            return
1✔
3225
        upstream_chan_scid, _ = deserialize_htlc_key(upstream_key)
1✔
3226
        upstream_chan = self.get_channel_by_short_id(upstream_chan_scid)
1✔
3227
        upstream_peer = self.lnpeermgr.get_peer_by_pubkey(upstream_chan.node_id) if upstream_chan else None
1✔
3228
        if upstream_peer:
1✔
3229
            upstream_peer.downstream_htlc_resolved_event.set()
1✔
3230
            upstream_peer.downstream_htlc_resolved_event.clear()
1✔
3231

3232
    def _set_sent_payment_succeeded(self, payment_hash: bytes) -> None:
1✔
3233
        key = payment_hash.hex()
1✔
3234
        info = self.get_payment_info(payment_hash, direction=SENT)
1✔
3235
        if info is not None and info.status != PR_PAID:
1✔
3236
            self.set_invoice_status(key, PR_PAID)
1✔
3237
            util.trigger_callback('payment_succeeded', self.wallet, key)
1✔
3238

3239
    def _set_sent_payment_failed(self, payment_hash: bytes) -> None:
1✔
3240
        key = payment_hash.hex()
1✔
3241
        if self.has_unresolved_sent_htlcs(payment_hash):
1✔
3242
            return
1✔
3243
        if self.get_preimage(payment_hash) and self.wallet.get_request(key) is None:
1✔
3244
            # if we know the preimage don't consider the payment failed (unless we pay ourselves).
3245
            # maybe another htlc of the same mpp got fulfilled, or we saw a htlc-success tx in the mempool
3246
            # before claiming a revoked htlc with a justice tx which ultimately would make LNWatcher try to fail the htlc here
3247
            return
×
3248
        info = self.get_payment_info(payment_hash, direction=SENT)
1✔
3249
        if info is not None and (info.status == PR_UNPAID and key in self.inflight_payments):
1✔
3250
            # invoice status is PR_INFLIGHT, set it to PR_UNPAID
3251
            self.set_invoice_status(key, PR_UNPAID)
1✔
3252
            util.trigger_callback('payment_failed', self.wallet, key, '')
1✔
3253

3254
    def htlc_fulfilled(self, chan: Channel, payment_hash: bytes, htlc_id: int):
1✔
3255
        """Called when an HTLC *WE proposed* becomes irrevocably fulfilled."""
3256
        # note: this may be called several times for the same htlc
3257

3258
        util.trigger_callback('htlc_fulfilled', payment_hash, chan, htlc_id)
1✔
3259
        htlc_key = serialize_htlc_key(chan.get_scid_or_local_alias(), htlc_id)
1✔
3260
        fw_key = self.is_forwarded_htlc(htlc_key)
1✔
3261
        if fw_key:
1✔
3262
            fw_htlcs = self.active_forwardings[fw_key]
1✔
3263
            fw_htlcs.remove(htlc_key)
1✔
3264

3265
        shi = self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id))
1✔
3266
        if shi and htlc_id in chan.onion_keys:
1✔
3267
            chan.pop_onion_key(htlc_id)
1✔
3268
            if self.network.path_finder:
1✔
3269
                self.network.path_finder.update_liquidity_hints(shi.route, shi.amount_receiver_msat)
1✔
3270
                self.network.path_finder.update_num_inflight_htlcs(shi.route, add_htlcs=False)
1✔
3271
            payment_key = payment_hash + shi.payment_secret_orig
1✔
3272
            paysession = self._paysessions[payment_key]
1✔
3273
            q = paysession.sent_htlcs_q
1✔
3274
            htlc_log = HtlcLog(
1✔
3275
                success=True,
3276
                route=shi.route,
3277
                amount_msat=shi.amount_receiver_msat,
3278
                trampoline_fee_level=shi.trampoline_fee_level)
3279
            q.put_nowait(htlc_log)
1✔
3280
            if paysession.can_be_deleted():
1✔
3281
                self._paysessions.pop(payment_key)
1✔
3282
                paysession_active = False
1✔
3283
            else:
3284
                paysession_active = True
1✔
3285
            if not fw_key and not paysession.is_active:
1✔
3286
                self._set_sent_payment_succeeded(payment_hash)
1✔
3287
        else:
3288
            if fw_key:
1✔
3289
                paysession_active = False
1✔
3290
            else:
3291
                self._set_sent_payment_succeeded(payment_hash)
1✔
3292

3293
        if fw_key:
1✔
3294
            fw_htlcs = self.active_forwardings[fw_key]
1✔
3295
            if len(fw_htlcs) == 0 and not paysession_active:
1✔
3296
                self.notify_upstream_peer(htlc_key)
1✔
3297

3298
    def htlc_failed(
1✔
3299
            self,
3300
            chan: Channel,
3301
            payment_hash: bytes,
3302
            htlc_id: int,
3303
            error_bytes: Optional[bytes],
3304
            failure_message: Optional['OnionRoutingFailure'],
3305
    ):
3306
        """Called when an HTLC *WE proposed* becomes irrevocably failed."""
3307
        # note: this may be called several times for the same htlc
3308

3309
        util.trigger_callback('htlc_failed', payment_hash, chan, htlc_id)
1✔
3310
        htlc_key = serialize_htlc_key(chan.get_scid_or_local_alias(), htlc_id)
1✔
3311
        fw_key = self.is_forwarded_htlc(htlc_key)
1✔
3312
        if fw_key:
1✔
3313
            fw_htlcs = self.active_forwardings[fw_key]
1✔
3314
            fw_htlcs.remove(htlc_key)
1✔
3315

3316
        shi = self.sent_htlcs_info.get((payment_hash, chan.short_channel_id, htlc_id))
1✔
3317
        if shi and htlc_id in chan.onion_keys:
1✔
3318
            onion_key = chan.pop_onion_key(htlc_id)
1✔
3319
            if self.network.path_finder:
1✔
3320
                self.network.path_finder.update_num_inflight_htlcs(shi.route, add_htlcs=False)
1✔
3321
            payment_okey = payment_hash + shi.payment_secret_orig
1✔
3322
            paysession = self._paysessions[payment_okey]
1✔
3323
            q = paysession.sent_htlcs_q
1✔
3324
            # detect if it is part of a bucket
3325
            # if yes, wait until the bucket completely failed
3326
            route = shi.route
1✔
3327
            if error_bytes:
1✔
3328
                # TODO "decode_onion_error" might raise, catch and maybe blacklist/penalise someone?
3329
                try:
1✔
3330
                    failure_message, sender_idx = decode_onion_error(
1✔
3331
                        error_bytes,
3332
                        [x.node_id for x in route],
3333
                        onion_key)
3334
                except Exception as e:
1✔
3335
                    self.logger.warning(f"failed to decode onion error for htlc {htlc_id}", exc_info=True)
1✔
3336
                    sender_idx = None
1✔
3337
                    failure_message = OnionRoutingFailure(OnionFailureCode.INVALID_ONION_PAYLOAD, str(e).encode())
1✔
3338
            else:
3339
                # probably got "update_fail_malformed_htlc". well... who to penalise now?
3340
                assert failure_message is not None
1✔
3341
                sender_idx = None
1✔
3342
            self.logger.info(f"htlc_failed {failure_message}")
1✔
3343
            amount_receiver_msat = paysession.on_htlc_fail_get_fail_amt_to_propagate(shi)
1✔
3344
            if amount_receiver_msat is None:
1✔
3345
                return
1✔
3346
            if shi.trampoline_route:
1✔
3347
                route = shi.trampoline_route
1✔
3348
            htlc_log = HtlcLog(
1✔
3349
                success=False,
3350
                route=route,
3351
                amount_msat=amount_receiver_msat,
3352
                error_bytes=error_bytes,
3353
                failure_msg=failure_message,
3354
                sender_idx=sender_idx,
3355
                trampoline_fee_level=shi.trampoline_fee_level)
3356
            q.put_nowait(htlc_log)
1✔
3357
            if paysession.can_be_deleted():
1✔
3358
                self._paysessions.pop(payment_okey)
1✔
3359
                paysession_active = False
1✔
3360
            else:
3361
                paysession_active = True
1✔
3362
            if not fw_key and not paysession.is_active:
1✔
3363
                self._set_sent_payment_failed(payment_hash)
1✔
3364
        else:
3365
            if fw_key:
1✔
3366
                paysession_active = False
1✔
3367
            else:
3368
                self.logger.info(f"received unknown htlc_failed, probably from previous session (phash={payment_hash.hex()})")
1✔
3369
                self._set_sent_payment_failed(payment_hash)
1✔
3370

3371
        if fw_key:
1✔
3372
            fw_htlcs = self.active_forwardings[fw_key]
1✔
3373
            can_forward_failure = (len(fw_htlcs) == 0) and not paysession_active
1✔
3374
            if can_forward_failure:
1✔
3375
                self.logger.info(f'htlc_failed: save_forwarding_failure (phash={payment_hash.hex()})')
1✔
3376
                self.save_forwarding_failure(fw_key, error_bytes=error_bytes, failure_message=failure_message)
1✔
3377
                self.notify_upstream_peer(htlc_key)
1✔
3378
            else:
3379
                self.logger.info(f'htlc_failed: waiting for other htlcs to fail (phash={payment_hash.hex()})')
1✔
3380

3381
    def calc_routing_hints_for_invoice(self, amount_msat: Optional[int], *, channels=None, only_trampoline: bool = False):
1✔
3382
        """calculate routing hints (BOLT-11 'r' field)"""
3383
        routing_hints = []
1✔
3384
        if self.receive_requires_jit_channel(amount_msat):
1✔
3385
            self.logger.debug(f"will request just-in-time channel")
×
3386
            node_id, rest = extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)
×
3387
            alias_or_scid = self.get_static_jit_scid_alias()
×
3388
            routing_hints.append(('r', [(node_id, alias_or_scid, 0, 0, 144)]))
×
3389
            # no need for more because we cannot receive enough through the others and mpp is disabled for jit
3390
            channels = []
×
3391
        else:
3392
            if channels is None:
1✔
3393
                channels = list(self.get_channels_for_receiving(amount_msat=amount_msat, include_disconnected=True))
1✔
3394
                random.shuffle(channels)  # let's not leak channel order
1✔
3395
            scid_to_my_channels = {
1✔
3396
                chan.short_channel_id: chan for chan in channels
3397
                if chan.short_channel_id is not None
3398
            }
3399
        for chan in channels:
1✔
3400
            if only_trampoline and not self.is_trampoline_peer(chan.node_id):
1✔
3401
                continue
1✔
3402
            alias_or_scid = chan.get_remote_scid_alias() or chan.short_channel_id
1✔
3403
            assert isinstance(alias_or_scid, bytes), alias_or_scid
1✔
3404
            channel_info = get_mychannel_info(chan.short_channel_id, scid_to_my_channels)
1✔
3405
            # note: as a fallback, if we don't have a channel update for the
3406
            # incoming direction of our private channel, we fill the invoice with garbage.
3407
            # the sender should still be able to pay us, but will incur an extra round trip
3408
            # (they will get the channel update from the onion error)
3409
            # at least, that's the theory. https://github.com/lightningnetwork/lnd/issues/2066
3410
            fee_base_msat = fee_proportional_millionths = 0
1✔
3411
            cltv_delta = 1  # lnd won't even try with zero
1✔
3412
            missing_info = True
1✔
3413
            if channel_info:
1✔
3414
                policy = get_mychannel_policy(channel_info.short_channel_id, chan.node_id, scid_to_my_channels)
1✔
3415
                if policy:
1✔
3416
                    fee_base_msat = policy.fee_base_msat
1✔
3417
                    fee_proportional_millionths = policy.fee_proportional_millionths
1✔
3418
                    cltv_delta = policy.cltv_delta
1✔
3419
                    missing_info = False
1✔
3420
            if missing_info:
1✔
3421
                self.logger.info(
1✔
3422
                    f"Warning. Missing channel update for our channel {chan.short_channel_id}; "
3423
                    f"filling invoice with incorrect data.")
3424
            routing_hints.append(('r', [(
1✔
3425
                chan.node_id,
3426
                alias_or_scid,
3427
                fee_base_msat,
3428
                fee_proportional_millionths,
3429
                cltv_delta)]))
3430
        return routing_hints
1✔
3431

3432
    def delete_payment_info(self, payment_hash_hex: str, *, direction: lnutil.Direction):
1✔
3433
        # This method is called when an invoice or request is deleted by the user.
3434
        # The GUI only lets the user delete invoices or requests that have not been paid.
3435
        # Once an invoice/request has been paid, it is part of the history,
3436
        # and get_lightning_history assumes that payment_info is there.
3437
        assert self.get_payment_status(bytes.fromhex(payment_hash_hex), direction=direction) != PR_PAID
1✔
3438
        with self.lock:
1✔
3439
            key = PaymentInfo.calc_db_key(payment_hash_hex=payment_hash_hex, direction=direction)
1✔
3440
            self.payment_info.pop(key, None)
1✔
3441

3442
    def get_balance(self, *, frozen=False) -> Decimal:
1✔
3443
        with self.lock:
×
3444
            return Decimal(sum(
×
3445
                chan.balance(LOCAL) if not chan.is_closed() and (chan.is_frozen_for_sending() if frozen else True) else 0
3446
                for chan in self.channels.values())) / 1000
3447

3448
    def get_channels_for_sending(self):
1✔
3449
        for c in self.channels.values():
×
3450
            if c.is_active() and not c.is_frozen_for_sending():
×
3451
                if self.channel_db or self.is_trampoline_peer(c.node_id):
×
3452
                    yield c
×
3453

3454
    def estimate_fee_reserve_for_total_amount(self, amount_sat: int | Decimal) -> int:
1✔
3455
        """
3456
        Estimate how much of the given amount needs to be reserved for
3457
        ln payment fees to reliably pay the remaining amount.
3458
        """
3459
        amount_msat = ceil(amount_sat * 1000)  # round up to the next sat
1✔
3460
        fee_msat = PaymentFeeBudget.reverse_from_total_amount(
1✔
3461
            total_amount_msat=amount_msat,
3462
            config=self.config,
3463
        )
3464
        return ceil(Decimal(fee_msat) / 1000)
1✔
3465

3466
    def num_sats_can_send(self, deltas=None) -> Decimal:
1✔
3467
        """
3468
        without trampoline, sum of all channel capacity
3469
        with trampoline, MPP must use a single trampoline
3470
        """
3471
        if deltas is None:
×
3472
            deltas = {}
×
3473

3474
        def send_capacity(chan):
×
3475
            if chan in deltas:
×
3476
                delta_msat = deltas[chan] * 1000
×
3477
                if delta_msat > chan.available_to_spend(REMOTE):
×
3478
                    delta_msat = 0
×
3479
            else:
3480
                delta_msat = 0
×
3481
            return chan.available_to_spend(LOCAL) + delta_msat
×
3482
        can_send_dict = defaultdict(int)
×
3483
        with self.lock:
×
3484
            for c in self.get_channels_for_sending():
×
3485
                if not self.uses_trampoline():
×
3486
                    can_send_dict[0] += send_capacity(c)
×
3487
                else:
3488
                    can_send_dict[c.node_id] += send_capacity(c)
×
3489
        can_send = max(can_send_dict.values()) if can_send_dict else 0
×
3490
        can_send_sat = Decimal(can_send)/1000
×
3491
        can_send_sat -= self.estimate_fee_reserve_for_total_amount(can_send_sat)
×
3492
        return max(can_send_sat, 0)
×
3493

3494
    def get_channels_for_receiving(
1✔
3495
        self, *, amount_msat: Optional[int] = None, include_disconnected: bool = False,
3496
    ) -> Sequence[Channel]:
3497
        if not amount_msat:  # assume we want to recv a large amt, e.g. finding max.
1✔
3498
            amount_msat = float('inf')
×
3499
        with self.lock:
1✔
3500
            channels = list(self.channels.values())
1✔
3501
            channels = [chan for chan in channels
1✔
3502
                        if chan.is_open() and not chan.is_frozen_for_receiving()]
3503

3504
            if not include_disconnected:
1✔
3505
                channels = [chan for chan in channels if chan.is_active()]
×
3506

3507
            # Filter out nodes that have low receive capacity compared to invoice amt.
3508
            # Even with MPP, below a certain threshold, including these channels probably
3509
            # hurts more than help, as they lead to many failed attempts for the sender.
3510
            channels = sorted(channels, key=lambda chan: -chan.available_to_spend(REMOTE))
1✔
3511
            selected_channels = []
1✔
3512
            running_sum = 0
1✔
3513
            cutoff_factor = 0.2  # heuristic
1✔
3514
            for chan in channels:
1✔
3515
                recv_capacity = chan.available_to_spend(REMOTE)
1✔
3516
                chan_can_handle_payment_as_single_part = recv_capacity >= amount_msat
1✔
3517
                chan_small_compared_to_running_sum = recv_capacity < cutoff_factor * running_sum
1✔
3518
                if not chan_can_handle_payment_as_single_part and chan_small_compared_to_running_sum:
1✔
3519
                    break
1✔
3520
                running_sum += recv_capacity
1✔
3521
                selected_channels.append(chan)
1✔
3522
            channels = selected_channels
1✔
3523
            del selected_channels
1✔
3524
            # cap max channels to include to keep QR code reasonably scannable
3525
            channels = channels[:10]
1✔
3526
            return channels
1✔
3527

3528
    def num_sats_can_receive(self, deltas=None) -> Decimal:
1✔
3529
        """
3530
        We no longer assume the sender to send MPP on different channels,
3531
        because channel liquidities are hard to guess
3532
        """
3533
        if deltas is None:
×
3534
            deltas = {}
×
3535

3536
        def recv_capacity(chan):
×
3537
            if chan in deltas:
×
3538
                delta_msat = deltas[chan] * 1000
×
3539
                if delta_msat > chan.available_to_spend(LOCAL):
×
3540
                    delta_msat = 0
×
3541
            else:
3542
                delta_msat = 0
×
3543
            return chan.available_to_spend(REMOTE) + delta_msat
×
3544
        with self.lock:
×
3545
            recv_channels = self.get_channels_for_receiving()
×
3546
            recv_chan_msats = [recv_capacity(chan) for chan in recv_channels]
×
3547
        if not recv_chan_msats:
×
3548
            return Decimal(0)
×
3549
        can_receive_msat = max(recv_chan_msats)
×
3550
        return Decimal(can_receive_msat) / 1000
×
3551

3552
    def receive_requires_jit_channel(self, amount_msat: Optional[int]) -> bool:
1✔
3553
        """Returns true if we cannot receive the amount and have set up a trusted LSP node.
3554
        Cannot work reliably with 0 amount invoices as we don't know if we are able to receive it.
3555
        """
3556
        # zeroconf provider is configured and connected
3557
        if (self.can_get_zeroconf_channel()
1✔
3558
                # we cannot receive the amount specified
3559
                and ((amount_msat and self.num_sats_can_receive() < (amount_msat // 1000))
3560
                        # or we cannot receive anything, and it's a 0 amount invoice
3561
                        or (not amount_msat and self.num_sats_can_receive() < 1))):
3562
            return True
1✔
3563
        return False
1✔
3564

3565
    def can_get_zeroconf_channel(self) -> bool:
1✔
3566
        if not self.config.OPEN_ZEROCONF_CHANNELS:
1✔
3567
            return False
1✔
3568
        if self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS or self.config.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS:
1✔
3569
            return False
×
3570
        node_id = self.trusted_zeroconf_node_id
1✔
3571
        if not node_id:
1✔
3572
            return False
1✔
3573
        # only return True if we are connected to the zeroconf provider
3574
        return self.lnpeermgr.get_peer_by_pubkey(node_id) is not None
1✔
3575

3576
    @property
1✔
3577
    def trusted_zeroconf_node_id(self) -> Optional[bytes]:
1✔
3578
        if not self.config.ZEROCONF_TRUSTED_NODE:
1✔
3579
            return None
1✔
3580
        try:
1✔
3581
            return extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0]
1✔
3582
        except ConnStringFormatError:
1✔
3583
            self.logger.warning(f"invalid zeroconf node connection string configured")
1✔
3584
        return None
1✔
3585

3586
    def _suggest_channels_for_rebalance(self, direction, amount_sat) -> Sequence[Tuple[Channel, int]]:
1✔
3587
        """
3588
        Suggest a channel and amount to send/receive with that channel, so that we will be able to receive/send amount_sat
3589
        This is used when suggesting a swap or rebalance in order to receive a payment
3590
        """
3591
        with self.lock:
×
3592
            func = self.num_sats_can_send if direction == SENT else self.num_sats_can_receive
×
3593
            suggestions = []
×
3594
            channels = self.get_channels_for_sending() if direction == SENT else self.get_channels_for_receiving()
×
3595
            for chan in channels:
×
3596
                available_sat = chan.available_to_spend(LOCAL if direction == SENT else REMOTE) // 1000
×
3597
                delta = amount_sat - available_sat
×
3598
                delta += self.estimate_fee_reserve_for_total_amount(amount_sat)
×
3599
                # add safety margin
3600
                delta += delta // 100 + 1
×
3601
                if func(deltas={chan:delta}) >= amount_sat:
×
3602
                    suggestions.append((chan, int(delta)))
×
3603
                elif direction == RECEIVED and func(deltas={chan:2*delta}) >= amount_sat:
×
3604
                    # MPP heuristics has a 0.5 slope
3605
                    suggestions.append((chan, int(2*delta)))
×
3606
        if not suggestions:
×
3607
            raise NotEnoughFunds
×
3608
        return suggestions
×
3609

3610
    def _suggest_rebalance(self, direction, amount_sat):
1✔
3611
        """
3612
        Suggest a rebalance in order to be able to send or receive amount_sat.
3613
        Returns (from_channel, to_channel, amount to shuffle)
3614
        """
3615
        try:
×
3616
            suggestions = self._suggest_channels_for_rebalance(direction, amount_sat)
×
3617
        except NotEnoughFunds:
×
3618
            return False
×
3619
        for chan2, delta in suggestions:
×
3620
            # margin for fee caused by rebalancing
3621
            delta += self.estimate_fee_reserve_for_total_amount(amount_sat)
×
3622
            # find other channel or trampoline that can send delta
3623
            for chan1 in self.channels.values():
×
3624
                if chan1.is_frozen_for_sending() or not chan1.is_active():
×
3625
                    continue
×
3626
                if chan1 == chan2:
×
3627
                    continue
×
3628
                if self.uses_trampoline() and chan1.node_id == chan2.node_id:
×
3629
                    continue
×
3630
                if direction == SENT:
×
3631
                    if chan1.can_pay(delta*1000):
×
3632
                        return chan1, chan2, delta
×
3633
                else:
3634
                    if chan1.can_receive(delta*1000):
×
3635
                        return chan2, chan1, delta
×
3636
            else:
3637
                continue
×
3638
        else:
3639
            return False
×
3640

3641
    def num_sats_can_rebalance(self, chan1: Channel, chan2: Channel) -> int:
1✔
3642
        # TODO: we should be able to spend 'max', with variable fee
3643
        if chan1.is_frozen_for_sending() or chan2.is_frozen_for_receiving():
1✔
3644
            return 0
1✔
3645
        n1 = chan1.available_to_spend(LOCAL)
1✔
3646
        n1 -= self.estimate_fee_reserve_for_total_amount(n1)
1✔
3647
        n2 = chan2.available_to_spend(REMOTE)
1✔
3648
        amount_sat = min(n1, n2) // 1000
1✔
3649
        return amount_sat
1✔
3650

3651
    def suggest_rebalance_to_send(self, amount_sat):
1✔
3652
        return self._suggest_rebalance(SENT, amount_sat)
×
3653

3654
    def suggest_rebalance_to_receive(self, amount_sat):
1✔
3655
        return self._suggest_rebalance(RECEIVED, amount_sat)
×
3656

3657
    def suggest_swap_to_send(self, amount_sat, coins):
1✔
3658
        # fixme: if swap_amount_sat is lower than the minimum swap amount, we need to propose a higher value
3659
        assert amount_sat > self.num_sats_can_send()
×
3660
        try:
×
3661
            suggestions = self._suggest_channels_for_rebalance(SENT, amount_sat)
×
3662
        except NotEnoughFunds:
×
3663
            return None
×
3664
        for chan, swap_recv_amount in suggestions:
×
3665
            # check that we can send onchain
3666
            swap_server_mining_fee = 10000 # guessing, because we have not called get_pairs yet
×
3667
            swap_funding_sat = swap_recv_amount + swap_server_mining_fee
×
3668
            swap_output = PartialTxOutput.from_address_and_value(DummyAddress.SWAP, int(swap_funding_sat))
×
3669
            try:
×
3670
                # check if we have enough onchain funds
3671
                self.wallet.make_unsigned_transaction(
×
3672
                    coins=coins,
3673
                    outputs=[swap_output],
3674
                    fee_policy=FeePolicy(self.config.FEE_POLICY_SWAPS),
3675
                )
3676
            except NotEnoughFunds:
×
3677
                continue
×
3678
            return chan, swap_recv_amount
×
3679
        return None
×
3680

3681
    def suggest_swap_to_receive(self, amount_sat: int):
1✔
3682
        assert amount_sat > self.num_sats_can_receive(), f"{amount_sat=} | {self.num_sats_can_receive()=}"
×
3683
        try:
×
3684
            suggestions = self._suggest_channels_for_rebalance(RECEIVED, amount_sat)
×
3685
        except NotEnoughFunds:
×
3686
            return
×
3687
        for chan, swap_recv_amount in suggestions:
×
3688
            return chan, swap_recv_amount
×
3689

3690
    async def rebalance_channels(self, chan1: Channel, chan2: Channel, *, amount_msat: int):
1✔
3691
        if chan1 == chan2:
1✔
3692
            raise Exception('Rebalance requires two different channels')
×
3693
        if self.uses_trampoline() and chan1.node_id == chan2.node_id:
1✔
3694
            raise Exception('Rebalance requires channels from different trampolines')
×
3695
        if chan1.is_frozen_for_sending() or chan2.is_frozen_for_receiving():  # the gui should not allow this
1✔
3696
            raise Exception('Cannot rebalance through frozen channels')
×
3697
        payment_hash = self.create_payment_info(
1✔
3698
            amount_msat=amount_msat,
3699
            exp_delay=3600,
3700
        )
3701
        info = self.get_payment_info(payment_hash, direction=RECEIVED)
1✔
3702
        lnaddr, invoice = self.get_bolt11_invoice(
1✔
3703
            payment_info=info,
3704
            message='rebalance',
3705
            fallback_address=None,
3706
            channels=[chan2],
3707
        )
3708
        invoice_obj = Invoice.from_bech32(invoice)
1✔
3709
        return await self.pay_invoice(invoice_obj, channels=[chan1])
1✔
3710

3711
    def can_receive_invoice(self, invoice: BaseInvoice) -> bool:
1✔
3712
        assert invoice.is_lightning()
×
3713
        return (invoice.get_amount_sat() or 0) <= self.num_sats_can_receive()
×
3714

3715
    async def close_channel(self, chan_id):
1✔
3716
        chan = self._channels[chan_id]
×
3717
        peer = self.lnpeermgr.get_peer_by_pubkey(chan.node_id)
×
3718
        if peer is None:
×
3719
            raise KeyError
×
3720
        return await peer.close_channel(chan_id)
×
3721

3722
    def _force_close_channel(self, chan_id: bytes) -> Transaction:
1✔
3723
        chan = self._channels[chan_id]
1✔
3724
        tx = chan.force_close_tx()
1✔
3725
        # We set the channel state to make sure we won't sign new commitment txs.
3726
        # We expect the caller to try to broadcast this tx, after which it is
3727
        # not safe to keep using the channel even if the broadcast errors (server could be lying).
3728
        # Until the tx is seen in the mempool, there will be automatic rebroadcasts.
3729
        chan.set_state(ChannelState.FORCE_CLOSING)
1✔
3730
        # Add local tx to wallet to also allow manual rebroadcasts.
3731
        try:
1✔
3732
            self.wallet.adb.add_transaction(tx)
1✔
3733
        except UnrelatedTransactionException:
1✔
3734
            pass  # this can happen if (~all the balance goes to REMOTE)
1✔
3735
        return tx
1✔
3736

3737
    async def force_close_channel(self, chan_id: bytes) -> str:
1✔
3738
        """Force-close the channel. Network-related exceptions are propagated to the caller.
3739
        (automatic rebroadcasts will be scheduled)
3740
        """
3741
        # note: as we are async, it can take a few event loop iterations between the caller
3742
        #       "calling us" and us getting to run, and we only set the channel state now:
3743
        tx = self._force_close_channel(chan_id)
1✔
3744
        await self.network.broadcast_transaction(tx)
1✔
3745
        return tx.txid()
1✔
3746

3747
    def schedule_force_closing(self, chan_id: bytes) -> 'asyncio.Task[bool]':
1✔
3748
        """Schedules a task to force-close the channel and returns it.
3749
        Network-related exceptions are suppressed.
3750
        (automatic rebroadcasts will be scheduled)
3751
        Note: this method is intentionally not async so that callers have a guarantee
3752
              that the channel state is set immediately.
3753
        """
3754
        tx = self._force_close_channel(chan_id)
1✔
3755
        return asyncio.create_task(self.network.try_broadcasting(tx, 'force-close'))
1✔
3756

3757
    def remove_channel(self, chan_id):
1✔
3758
        chan = self._channels[chan_id]
1✔
3759
        assert chan.can_be_deleted()
1✔
3760
        with self.lock:
1✔
3761
            self._channels.pop(chan_id)
1✔
3762
            self.db.get('channels').pop(chan_id.hex())
1✔
3763
        self.wallet.set_reserved_addresses_for_chan(chan, reserved=False)
1✔
3764

3765
        util.trigger_callback('channels_updated', self.wallet)
1✔
3766
        util.trigger_callback('wallet_updated', self.wallet)
1✔
3767

3768
    async def reestablish_peers_and_channels(self):
1✔
3769
        while True:
1✔
3770
            await asyncio.sleep(1)
1✔
3771
            if self.lnpeermgr.stopping_soon:
1✔
UNCOV
3772
                return
×
3773
            await self.lnpeermgr.reestablish_peer_for_zero_conf_trusted_node()
1✔
3774
            for chan in self.channels.values():
1✔
3775
                # reestablish
3776
                # note: we delegate filtering out uninteresting chans to this:
3777
                if not chan.should_try_to_reestablish_peer():
1✔
3778
                    continue
1✔
3779
                peer = self.lnpeermgr.get_peer_by_pubkey(chan.node_id)
1✔
3780
                if peer:
1✔
3781
                    # FIXME maybe this should be the responsibility of the peer itself, done in peer.main_loop:
3782
                    await peer.taskgroup.spawn(peer.reestablish_channel(chan))
×
3783
                else:
3784
                    await self.lnpeermgr.reestablish_peer_for_given_channel(chan)
1✔
3785

3786
    def current_target_feerate_per_kw(self, *, has_anchors: bool) -> Optional[int]:
1✔
3787
        target: int = FEE_LN_MINIMUM_ETA_TARGET if has_anchors else FEE_LN_ETA_TARGET
1✔
3788
        feerate_per_kvbyte = self.network.fee_estimates.eta_target_to_fee(target)
1✔
3789
        if feerate_per_kvbyte is None:
1✔
3790
            return None
×
3791
        if has_anchors:
1✔
3792
            # set a floor of 5 sat/vb to have some safety margin in case the mempool
3793
            # grows quickly
3794
            feerate_per_kvbyte = max(feerate_per_kvbyte, 5000)
1✔
3795
        return max(FEERATE_PER_KW_MIN_RELAY_LIGHTNING, feerate_per_kvbyte // 4)
1✔
3796

3797
    def current_low_feerate_per_kw_srk_channel(self) -> Optional[int]:
1✔
3798
        """Gets low feerate for static remote key channels."""
3799
        if constants.net is constants.BitcoinRegtest:
1✔
3800
            feerate_per_kvbyte = 0
1✔
3801
        else:
3802
            feerate_per_kvbyte = self.network.fee_estimates.eta_target_to_fee(FEE_LN_LOW_ETA_TARGET)
1✔
3803
            if feerate_per_kvbyte is None:
1✔
3804
                return None
×
3805
        low_feerate_per_kw = max(FEERATE_PER_KW_MIN_RELAY_LIGHTNING, feerate_per_kvbyte // 4)
1✔
3806
        # make sure this is never higher than the target feerate:
3807
        current_target_feerate = self.current_target_feerate_per_kw(has_anchors=False)
1✔
3808
        if not current_target_feerate:
1✔
3809
            return None
×
3810
        low_feerate_per_kw = min(low_feerate_per_kw, current_target_feerate)
1✔
3811
        return low_feerate_per_kw
1✔
3812

3813
    def create_channel_backup(self, channel_id: bytes):
1✔
3814
        chan = self._channels[channel_id]
1✔
3815
        # do not backup old-style channels
3816
        assert chan.is_static_remotekey_enabled()
1✔
3817
        peer_addresses = list(chan.get_peer_addresses())
1✔
3818
        peer_addr = peer_addresses[0] if peer_addresses else None
1✔
3819
        if chan.has_anchors():
1✔
3820
            local_payment_basepoint = chan.config[LOCAL].payment_basepoint.privkey
1✔
3821
        else:
3822
            local_payment_basepoint = chan.config[LOCAL].payment_basepoint.pubkey
1✔
3823
        return ImportedChannelBackupStorage(
1✔
3824
            node_id=chan.node_id,
3825
            privkey=self.node_keypair.privkey,
3826
            funding_txid=chan.funding_outpoint.txid,
3827
            funding_index=chan.funding_outpoint.output_index,
3828
            funding_address=chan.get_funding_address(),
3829
            host=peer_addr.host if peer_addr else '',
3830
            port=peer_addr.port if peer_addr else 0,
3831
            is_initiator=chan.constraints.is_initiator,
3832
            channel_seed=chan.config[LOCAL].channel_seed,
3833
            channel_type=int(chan.storage['channel_type']),
3834
            local_delay=chan.config[LOCAL].to_self_delay,
3835
            remote_delay=chan.config[REMOTE].to_self_delay,
3836
            remote_revocation_pubkey=chan.config[REMOTE].revocation_basepoint.pubkey,
3837
            remote_payment_pubkey=chan.config[REMOTE].payment_basepoint.pubkey,
3838
            local_payment_basepoint=local_payment_basepoint,
3839
            multisig_funding_privkey=chan.config[LOCAL].multisig_key.privkey,
3840
        )
3841

3842
    def export_channel_backup(self, channel_id):
1✔
3843
        """Historically, we allowed watching-only wallets and hardware wallets
3844
        to have lightning channels.  Since these wallets do not have
3845
        private keys, we use their master public key to encrypt
3846
        channel backups. This allows users to import channel backups
3847
        in these wallets.
3848

3849
        The creation of lightning channels in watching-only wallets
3850
        has been disabled for anchor channels.
3851

3852
        Note that these are static backups: they
3853
        only allow requesting a force close (and, in some scenarios,
3854
        sweeping funds after a channel has been force closed).
3855
        The LN node privkey is also contained (needed to establish a BOLT-08 transport with
3856
        the counterparty and request the force-close).
3857

3858
        This makes the xpub somewhat sensitive: having *both* the wallet xpub
3859
        and an encrypted-channel-backup allows the above actions.
3860
        TODO instead of xpub, encrypt with a secret derived from the seed along a dedicated hardened path?
3861
        """
3862
        xpub = self.wallet.get_fingerprint()
1✔
3863
        backup_bytes = self.create_channel_backup(channel_id).to_bytes()
1✔
3864
        assert backup_bytes == ImportedChannelBackupStorage.from_bytes(backup_bytes).to_bytes(), "roundtrip failed"
1✔
3865
        encrypted = pw_encode_with_version_and_mac(backup_bytes, xpub)
1✔
3866
        assert backup_bytes == pw_decode_with_version_and_mac(encrypted, xpub), "encrypt failed"
1✔
3867
        return 'channel_backup:' + encrypted
1✔
3868

3869
    async def request_force_close(self, channel_id: bytes, *, connect_str=None) -> None:
1✔
3870
        if chan := self.get_channel_by_id(channel_id):
1✔
3871
            peer = self.lnpeermgr.get_peer_by_pubkey(chan.node_id)
×
3872
            chan.should_request_force_close = True
×
3873
            if peer:
×
3874
                peer.close_and_cleanup()  # to force a reconnect
×
3875
        elif connect_str:
1✔
3876
            peer = await self.lnpeermgr.add_peer(connect_str)
×
3877
            await peer.request_force_close(channel_id)
×
3878
        elif channel_id in self.channel_backups:
1✔
3879
            await self._request_force_close_from_backup(channel_id)
1✔
3880
        else:
3881
            raise Exception(f'Unknown channel {channel_id.hex()}')
×
3882

3883
    def import_channel_backup(self, data):
1✔
3884
        xpub = self.wallet.get_fingerprint()
1✔
3885
        cb_blob = ImportedChannelBackupStorage.decrypt_encrypted_str(data, password=xpub)
1✔
3886
        cb_storage = ImportedChannelBackupStorage.from_bytes(cb_blob)
1✔
3887
        channel_id = cb_storage.channel_id()
1✔
3888
        if channel_id.hex() in self.db.get_dict("channels"):
1✔
3889
            raise Exception('Channel already in wallet')
×
3890
        if existing_backup := self._channel_backups.get(channel_id):
1✔
3891
            if existing_backup.is_imported and existing_backup.cb.backup_version > cb_storage.backup_version:
1✔
3892
                raise util.UserFacingException(_("You already have a newer version of this backup in your wallet."))
1✔
3893
        self.logger.info(f'importing channel backup: {channel_id.hex()}')
1✔
3894
        d = self.db.get_dict("imported_channel_backups")
1✔
3895
        d[channel_id.hex()] = cb_blob.hex()
1✔
3896
        with self.lock:
1✔
3897
            cb = ChannelBackup(cb_storage, lnworker=self)
1✔
3898
            self._channel_backups[channel_id] = cb
1✔
3899
        self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
1✔
3900
        self.wallet.save_db()
1✔
3901
        util.trigger_callback('channels_updated', self.wallet)
1✔
3902
        self.lnwatcher.add_channel(cb)
1✔
3903
        if not cb.can_sweep_their_ctx_to_remote():
1✔
3904
            # the user has lost their channel state and cannot locally force close. If they'd request a remote fclose
3905
            # they wouldn't be able to claim their to_remote output. However, they could collaborate with the channel
3906
            # counterparty (likely one of the hardcoded trampolines) and manually construct a transaction to spend
3907
            # the channel funding UTXO as they do have the multisig key in their backup ("manual collaborative close").
3908
            raise util.UserFacingException(
×
3909
                _("The channel backup you imported cannot be used to request a force close. Please generate a new backup. "
3910
                  "If you lost your wallet data, please open an issue on GitHub.")
3911
            )
3912

3913
    def has_conflicting_backup_with(self, remote_node_id: bytes):
1✔
3914
        """ Returns whether we have an active channel with this node on another device, using same local node id. """
3915
        channel_backup_peers = [
×
3916
            cb.node_id for cb in self.channel_backups.values()
3917
            if (not cb.is_closed() and cb.get_local_pubkey() == self.node_keypair.pubkey)]
3918
        return any(remote_node_id.startswith(cb_peer_nodeid) for cb_peer_nodeid in channel_backup_peers)
×
3919

3920
    def remove_channel_backup(self, channel_id):
1✔
3921
        chan = self.channel_backups[channel_id]
×
3922
        assert chan.can_be_deleted()
×
3923
        found = False
×
3924
        onchain_backups = self.db.get_dict("onchain_channel_backups")
×
3925
        imported_backups = self.db.get_dict("imported_channel_backups")
×
3926
        if channel_id.hex() in onchain_backups:
×
3927
            onchain_backups.pop(channel_id.hex())
×
3928
            found = True
×
3929
        if channel_id.hex() in imported_backups:
×
3930
            imported_backups.pop(channel_id.hex())
×
3931
            found = True
×
3932
        if not found:
×
3933
            raise Exception('Channel not found')
×
3934
        with self.lock:
×
3935
            self._channel_backups.pop(channel_id)
×
3936
        self.wallet.set_reserved_addresses_for_chan(chan, reserved=False)
×
3937
        self.wallet.save_db()
×
3938
        util.trigger_callback('channels_updated', self.wallet)
×
3939

3940
    @log_exceptions
1✔
3941
    async def _request_force_close_from_backup(self, channel_id: bytes):
1✔
3942
        cb = self.channel_backups.get(channel_id)
1✔
3943
        if not cb:
1✔
3944
            raise Exception(f'channel backup not found {self.channel_backups}')
×
3945
        cb = cb.cb # storage
1✔
3946
        self.logger.info(f'requesting channel force close: {channel_id.hex()}')
1✔
3947
        if isinstance(cb, ImportedChannelBackupStorage):
1✔
3948
            node_id = cb.node_id
1✔
3949
            privkey = cb.privkey
1✔
3950
            addresses = [(cb.host, cb.port, 0)]
1✔
3951
        else:
3952
            assert isinstance(cb, OnchainChannelBackupStorage)
1✔
3953
            privkey = self.node_keypair.privkey
1✔
3954
            for pubkey, peer_addr in trampolines_by_id().items():
1✔
3955
                if pubkey.startswith(cb.node_id_prefix):
1✔
3956
                    node_id = pubkey
1✔
3957
                    addresses = [(peer_addr.host, peer_addr.port, 0)]
1✔
3958
                    break
1✔
3959
            else:
3960
                # we will try with gossip (see below)
3961
                addresses = []
×
3962

3963
        async def _request_fclose(addresses):
1✔
3964
            for host, port, timestamp in addresses:
1✔
3965
                peer_addr = LNPeerAddr(host, port, node_id)
1✔
3966
                transport = LNTransport(privkey, peer_addr, e_proxy=ESocksProxy.from_network_settings(self.network))
1✔
3967
                peer = Peer(self, node_id, transport, is_channel_backup=True)
1✔
3968
                try:
1✔
3969
                    async with OldTaskGroup(wait=any) as group:
1✔
3970
                        await group.spawn(peer._message_loop())
1✔
3971
                        await group.spawn(peer.request_force_close(channel_id))
1✔
3972
                    async with util.async_timeout(1):
1✔
3973
                        peer.transport.close()
1✔
3974
                        await peer.transport.writer.wait_closed()  # flush write-buffer
1✔
3975
                    return True
1✔
3976
                except Exception as e:
×
3977
                    self.logger.info(f'failed to connect {host} {e}')
×
3978
                    continue
×
3979
            else:
3980
                return False
×
3981
        # try first without gossip db
3982
        success = await _request_fclose(addresses)
1✔
3983
        if success:
1✔
3984
            return
1✔
3985
        # try with gossip db
3986
        if self.uses_trampoline():
×
3987
            raise Exception(_('Please enable gossip'))
×
3988
        node_id = self.network.channel_db.get_node_by_prefix(cb.node_id_prefix)
×
3989
        addresses_from_gossip = self.network.channel_db.get_node_addresses(node_id)
×
3990
        if not addresses_from_gossip:
×
3991
            raise Exception('Peer not found in gossip database')
×
3992
        success = await _request_fclose(addresses_from_gossip)
×
3993
        if not success:
×
3994
            raise Exception('failed to connect')
×
3995

3996
    def maybe_add_backup_from_tx(self, tx):
1✔
3997
        funding_address = None
1✔
3998
        node_id_prefix = None
1✔
3999
        for i, o in enumerate(tx.outputs()):
1✔
4000
            script_type = get_script_type_from_output_script(o.scriptpubkey)
1✔
4001
            if script_type == 'p2wsh':
1✔
4002
                funding_index = i
1✔
4003
                funding_address = o.address
1✔
4004
                for o2 in tx.outputs():
1✔
4005
                    if o2.scriptpubkey.startswith(bytes([opcodes.OP_RETURN])):
1✔
4006
                        encrypted_data = o2.scriptpubkey[2:]
1✔
4007
                        data = self.decrypt_cb_data(encrypted_data, funding_address)
1✔
4008
                        if data.startswith(CB_MAGIC_BYTES):
1✔
4009
                            node_id_prefix = data[len(CB_MAGIC_BYTES):]
1✔
4010
        if node_id_prefix is None:
1✔
4011
            return
1✔
4012
        funding_txid = tx.txid()
1✔
4013
        cb_storage = OnchainChannelBackupStorage(
1✔
4014
            node_id_prefix=node_id_prefix,
4015
            funding_txid=funding_txid,
4016
            funding_index=funding_index,
4017
            funding_address=funding_address,
4018
            is_initiator=True)
4019
        channel_id = cb_storage.channel_id().hex()
1✔
4020
        if channel_id in self.db.get_dict("channels"):
1✔
4021
            return
1✔
4022
        self.logger.info(f"adding backup from tx")
1✔
4023
        d = self.db.get_dict("onchain_channel_backups")
1✔
4024
        d[channel_id] = cb_storage
1✔
4025
        cb = ChannelBackup(cb_storage, lnworker=self)
1✔
4026
        self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
1✔
4027
        self.wallet.save_db()
1✔
4028
        with self.lock:
1✔
4029
            self._channel_backups[bfh(channel_id)] = cb
1✔
4030
        util.trigger_callback('channels_updated', self.wallet)
1✔
4031
        self.lnwatcher.add_channel(cb)
1✔
4032

4033
    async def maybe_forward_htlc_set(
1✔
4034
        self,
4035
        payment_key: str, *,
4036
        processed_htlc_set: dict[ReceivedMPPHtlc, Tuple[ProcessedOnionPacket, Optional[ProcessedOnionPacket]]],
4037
    ) -> None:
4038
        assert self.enable_htlc_forwarding
1✔
4039
        assert payment_key not in self.active_forwardings, "cannot forward set twice"
1✔
4040
        self.active_forwardings[payment_key] = []
1✔
4041
        self.logger.debug(f"adding active_forwarding: {payment_key=}")
1✔
4042

4043
        any_mpp_htlc, (any_outer_onion, any_trampoline_onion) = next(iter(processed_htlc_set.items()))
1✔
4044
        try:
1✔
4045
            if any_trampoline_onion is None:
1✔
4046
                assert not any_outer_onion.are_we_final
1✔
4047
                assert len(processed_htlc_set) == 1, processed_htlc_set
1✔
4048
                forward_htlc = any_mpp_htlc.htlc
1✔
4049
                incoming_chan = self._channels[any_mpp_htlc.channel_id]
1✔
4050
                next_htlc = await self._maybe_forward_htlc(
1✔
4051
                    incoming_chan=incoming_chan,
4052
                    htlc=forward_htlc,
4053
                    processed_onion=any_outer_onion,
4054
                )
4055
                htlc_key = serialize_htlc_key(incoming_chan.get_scid_or_local_alias(), forward_htlc.htlc_id)
1✔
4056
                self.active_forwardings[payment_key].append(next_htlc)
1✔
4057
                self.downstream_to_upstream_htlc[next_htlc] = htlc_key
1✔
4058
            else:
4059
                assert not any_trampoline_onion.are_we_final and any_outer_onion.are_we_final
1✔
4060
                # trampoline forwarding
4061
                min_inc_cltv_abs = min(
1✔
4062
                    mpp_htlc.htlc.cltv_abs
4063
                    for mpp_htlc in processed_htlc_set.keys())  # take "min" to assume worst-case
4064
                total_msat = any_outer_onion.total_msat
1✔
4065
                sum_inc_amt_msat = sum(mpp_htlc.htlc.amount_msat for mpp_htlc in processed_htlc_set)
1✔
4066
                assert total_msat <= sum_inc_amt_msat, f"{total_msat=} should be <= {sum_inc_amt_msat=}"
1✔
4067
                await self._maybe_forward_trampoline(
1✔
4068
                    payment_hash=any_mpp_htlc.htlc.payment_hash,
4069
                    closest_inc_cltv_abs=min_inc_cltv_abs,
4070
                    total_msat=total_msat,
4071
                    any_trampoline_onion=any_trampoline_onion,
4072
                    fw_payment_key=payment_key,
4073
                )
4074
        except OnionRoutingFailure as e:
1✔
4075
            self.logger.debug(f"forwarding failed: {e=}")
1✔
4076
            if len(self.active_forwardings[payment_key]) == 0:
1✔
4077
                self.save_forwarding_failure(payment_key, failure_message=e)
1✔
4078
        # TODO what about other errors?
4079
        #      Could we "catch-all Exception" and fail back the htlcs with e.g. TEMPORARY_NODE_FAILURE?
4080
        #        - we don't want to fail the inc-HTLC for a syntax error that happens in the callback
4081
        #      If we don't call save_forwarding_failure(), the inc-HTLC gets stuck until expiry
4082
        #      and then the inc-channel will get force-closed.
4083
        #      => forwarding_callback() could have an API with two exceptions types:
4084
        #        - type1, such as OnionRoutingFailure, that signals we need to fail back the inc-HTLC
4085
        #        - type2, such as NoPathFound, that signals we want to retry forwarding
4086

4087
    async def _maybe_forward_htlc(
1✔
4088
            self, *,
4089
            incoming_chan: Channel,
4090
            htlc: UpdateAddHtlc,
4091
            processed_onion: ProcessedOnionPacket,
4092
    ) -> str:
4093

4094
        # Forward HTLC
4095
        # FIXME: there are critical safety checks MISSING here
4096
        #        - for example; atm we forward first and then persist "forwarding_info",
4097
        #          so if we segfault in-between and restart, we might forward an HTLC twice...
4098
        #          (same for trampoline forwarding)
4099
        #        - we should check for the exposure to dust HTLCs ("max_dust_htlc_exposure_msat"), see:
4100
        #          https://github.com/ACINQ/eclair/pull/1985
4101
        #          https://github.com/lightning/bolts/blob/35e79db504560b9d3494a0ed07bf1e8379c3663a/02-peer-protocol.md#bounding-exposure-to-trimmed-in-flight-htlcs-max_dust_htlc_exposure_msat
4102

4103
        def log_fail_reason(reason: str):
1✔
4104
            self.logger.debug(
1✔
4105
                f"_maybe_forward_htlc. will FAIL HTLC: inc_chan={incoming_chan.get_id_for_log()}. "
4106
                f"{reason}. inc_htlc={str(htlc)}. onion_payload={processed_onion.hop_data.payload}")
4107

4108
        forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
1✔
4109
        if not forwarding_enabled:
1✔
4110
            log_fail_reason("forwarding is disabled")
×
4111
            raise OnionRoutingFailure(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
×
4112
        chain = self.network.blockchain()
1✔
4113
        if chain.is_tip_stale():
1✔
4114
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
×
4115
        if (next_chan_scid := processed_onion.next_chan_scid) is None:
1✔
4116
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
4117
        if (next_amount_msat_htlc := processed_onion.amt_to_forward) is None:
1✔
4118
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
4119
        if (next_cltv_abs := processed_onion.outgoing_cltv_value) is None:
1✔
4120
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
4121

4122
        next_chan = self.get_channel_by_short_id(next_chan_scid)
1✔
4123

4124
        if self.features.supports(LnFeatures.OPTION_ZEROCONF_OPT):
1✔
4125
            next_peer = self.get_peer_by_static_jit_scid_alias(next_chan_scid)
×
4126
        else:
4127
            next_peer = None
1✔
4128

4129
        if not next_chan and next_peer and next_peer.accepts_zeroconf():
1✔
4130
            # check if an already existing channel can be used.
4131
            # todo: split the payment
4132
            for next_chan in next_peer.channels.values():
×
4133
                if next_chan.can_pay(next_amount_msat_htlc):
×
4134
                    break
×
4135
            else:
4136
                return await self.open_channel_just_in_time(
×
4137
                    next_peer=next_peer,
4138
                    next_amount_msat_htlc=next_amount_msat_htlc,
4139
                    next_cltv_abs=next_cltv_abs,
4140
                    payment_hash=htlc.payment_hash,
4141
                    next_onion=processed_onion.next_packet)
4142

4143
        local_height = chain.height()
1✔
4144
        if next_chan is None:
1✔
4145
            log_fail_reason(f"cannot find next_chan {next_chan_scid}")
×
4146
            raise OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=b'')
×
4147
        outgoing_chan_upd = next_chan.get_outgoing_gossip_channel_update(scid=next_chan_scid)[2:]
1✔
4148
        outgoing_chan_upd_len = len(outgoing_chan_upd).to_bytes(2, byteorder="big")
1✔
4149
        outgoing_chan_upd_message = outgoing_chan_upd_len + outgoing_chan_upd
1✔
4150
        if not next_chan.can_send_update_add_htlc():
1✔
4151
            log_fail_reason(
×
4152
                f"next_chan {next_chan.get_id_for_log()} cannot send ctx updates. "
4153
                f"chan state {next_chan.get_state()!r}, peer state: {next_chan.peer_state!r}")
4154
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message)
×
4155
        if not next_chan.can_pay(next_amount_msat_htlc):
1✔
4156
            log_fail_reason(f"transient error (likely due to insufficient funds): not next_chan.can_pay(amt)")
1✔
4157
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message)
1✔
4158
        if htlc.cltv_abs - next_cltv_abs < next_chan.forwarding_cltv_delta:
1✔
4159
            log_fail_reason(
1✔
4160
                f"INCORRECT_CLTV_EXPIRY. "
4161
                f"{htlc.cltv_abs=} - {next_cltv_abs=} < {next_chan.forwarding_cltv_delta=}")
4162
            data = htlc.cltv_abs.to_bytes(4, byteorder="big") + outgoing_chan_upd_message
1✔
4163
            raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_CLTV_EXPIRY, data=data)
1✔
4164
        if htlc.cltv_abs - lnutil.MIN_FINAL_CLTV_DELTA_ACCEPTED <= local_height \
1✔
4165
                or next_cltv_abs <= local_height:
4166
            raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_SOON, data=outgoing_chan_upd_message)
×
4167
        if max(htlc.cltv_abs, next_cltv_abs) > local_height + lnutil.NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE:
1✔
4168
            raise OnionRoutingFailure(code=OnionFailureCode.EXPIRY_TOO_FAR, data=b'')
×
4169
        forwarding_fees = fee_for_edge_msat(
1✔
4170
            forwarded_amount_msat=next_amount_msat_htlc,
4171
            fee_base_msat=next_chan.forwarding_fee_base_msat,
4172
            fee_proportional_millionths=next_chan.forwarding_fee_proportional_millionths)
4173
        if htlc.amount_msat - next_amount_msat_htlc < forwarding_fees:
1✔
4174
            data = next_amount_msat_htlc.to_bytes(8, byteorder="big") + outgoing_chan_upd_message
×
4175
            raise OnionRoutingFailure(code=OnionFailureCode.FEE_INSUFFICIENT, data=data)
×
4176
        self.logger.info(
1✔
4177
            f"maybe_forward_htlc. will forward HTLC: inc_chan={incoming_chan.short_channel_id}. inc_htlc={str(htlc)}. "
4178
            f"next_chan={next_chan.get_id_for_log()}.")
4179

4180
        next_peer = self.lnpeermgr.get_peer_by_pubkey(next_chan.node_id)
1✔
4181
        if next_peer is None:
1✔
4182
            log_fail_reason(f"next_peer offline ({next_chan.node_id.hex()})")
×
4183
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message)
×
4184
        try:
1✔
4185
            next_htlc = next_peer.send_htlc(
1✔
4186
                chan=next_chan,
4187
                payment_hash=htlc.payment_hash,
4188
                amount_msat=next_amount_msat_htlc,
4189
                cltv_abs=next_cltv_abs,
4190
                onion=processed_onion.next_packet,
4191
            )
4192
        except BaseException as e:
×
4193
            log_fail_reason(f"error sending message to next_peer={next_chan.node_id.hex()}")
×
4194
            raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_CHANNEL_FAILURE, data=outgoing_chan_upd_message)
×
4195

4196
        htlc_key = serialize_htlc_key(next_chan.get_scid_or_local_alias(), next_htlc.htlc_id)
1✔
4197
        return htlc_key
1✔
4198

4199
    @log_exceptions
1✔
4200
    async def _maybe_forward_trampoline(
1✔
4201
            self, *,
4202
            payment_hash: bytes,
4203
            closest_inc_cltv_abs: int,
4204
            total_msat: int,  # total_msat of the outer onion. this is <= sum_inc_amt_msat
4205
            any_trampoline_onion: ProcessedOnionPacket,  # any trampoline onion of the incoming htlc set, they should be similar
4206
            fw_payment_key: str,
4207
    ) -> None:
4208

4209
        forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
1✔
4210
        forwarding_trampoline_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS
1✔
4211
        if not (forwarding_enabled and forwarding_trampoline_enabled):
1✔
4212
            self.logger.info(f"trampoline forwarding is disabled. failing htlc.")
×
4213
            raise OnionRoutingFailure(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
×
4214
        payload = any_trampoline_onion.hop_data.payload
1✔
4215
        payment_data = payload.get('payment_data')
1✔
4216
        try:
1✔
4217
            payment_secret = payment_data['payment_secret'] if payment_data else crandom.get_rand_bytes(32)
1✔
4218
            outgoing_node_id = payload["outgoing_node_id"]["outgoing_node_id"]
1✔
4219
            amt_to_forward = payload["amt_to_forward"]["amt_to_forward"]
1✔
4220
            out_cltv_abs = payload["outgoing_cltv_value"]["outgoing_cltv_value"]
1✔
4221
            if "invoice_features" in payload:
1✔
4222
                self.logger.info('forward_trampoline: legacy')
1✔
4223
                next_trampoline_onion = None
1✔
4224
                invoice_features = payload["invoice_features"]["invoice_features"]
1✔
4225
                invoice_routing_info = payload["invoice_routing_info"]["invoice_routing_info"]
1✔
4226
                r_tags = decode_routing_info(invoice_routing_info)
1✔
4227
                self.logger.info(f'r_tags {BOLT11Addr.format_bolt11_routing_info_as_human_readable(r_tags)}')
1✔
4228
                # TODO legacy mpp payment, use total_msat from trampoline onion
4229
            else:
4230
                self.logger.info('forward_trampoline: end-to-end')
1✔
4231
                invoice_features = LnFeatures.BASIC_MPP_OPT
1✔
4232
                next_trampoline_onion = any_trampoline_onion.next_packet
1✔
4233
                r_tags = []
1✔
4234
        except Exception as e:
×
4235
            self.logger.exception('')
×
4236
            raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_PAYLOAD, data=b'\x00\x00\x00')
×
4237

4238
        assert total_msat >= amt_to_forward  # sanity check: money_in >= money_out
1✔
4239
        # these are the fee/cltv paid by the sender
4240
        # pay_to_node will raise if they are not sufficient
4241
        budget = PaymentFeeBudget(
1✔
4242
            fee_msat=total_msat - amt_to_forward,
4243
            cltv=closest_inc_cltv_abs - out_cltv_abs,
4244
        )
4245
        self.logger.info(f'trampoline forwarding. budget={budget}')
1✔
4246
        self.logger.info(f'trampoline forwarding. {closest_inc_cltv_abs=}, {out_cltv_abs=}')
1✔
4247
        # To convert abs vs rel cltvs, we need to guess blockheight used by original sender as "current blockheight".
4248
        # Blocks might have been mined since.
4249
        # - if we skew towards the past, we decrease our own cltv_budget accordingly (which is ok)
4250
        # - if we skew towards the future, we decrease the cltv_budget for the subsequent nodes in the path,
4251
        #   which can result in them failing the payment.
4252
        # So we skew towards the past and guess that there has been 1 new block mined since the payment began:
4253
        local_height_of_onion_creator = self.network.get_local_height() - 1
1✔
4254
        cltv_budget_for_rest_of_route = out_cltv_abs - local_height_of_onion_creator
1✔
4255

4256
        # do we have a connection to the node?
4257
        direct_channels = None
1✔
4258
        next_peer = self.lnpeermgr.get_peer_by_pubkey(outgoing_node_id)
1✔
4259
        if next_peer:
1✔
4260
            for next_chan in next_peer.channels.values():
1✔
4261
                if next_chan.can_pay(amt_to_forward):
1✔
4262
                    # todo: detect if we can do mpp
4263
                    direct_channels = [next_chan]
1✔
4264
                    break
1✔
4265
            # open JIT channel
4266
            if not direct_channels and next_peer.accepts_zeroconf() and self.features.supports(LnFeatures.OPTION_ZEROCONF_OPT):
1✔
4267
                scid_alias = self._scid_alias_of_node(next_peer.pubkey)
×
4268
                route = [RouteEdge(
×
4269
                    start_node=next_peer.pubkey,
4270
                    end_node=outgoing_node_id,
4271
                    short_channel_id=scid_alias,
4272
                    fee_base_msat=0,
4273
                    fee_proportional_millionths=0,
4274
                    cltv_delta=144,
4275
                    node_features=0
4276
                )]
4277
                next_onion, amount_msat, cltv_abs, session_key = self.create_onion_for_route(
×
4278
                    route=route,
4279
                    amount_msat=amt_to_forward,
4280
                    total_msat=amt_to_forward,
4281
                    payment_hash=payment_hash,
4282
                    min_final_cltv_delta=cltv_budget_for_rest_of_route,
4283
                    payment_secret=payment_secret,
4284
                    trampoline_onion=next_trampoline_onion,
4285
                )
4286
                await self.open_channel_just_in_time(
×
4287
                    next_peer=next_peer,
4288
                    next_amount_msat_htlc=amt_to_forward,
4289
                    next_cltv_abs=cltv_abs,
4290
                    payment_hash=payment_hash,
4291
                    next_onion=next_onion)
4292
                return
×
4293

4294
        if budget.fee_msat < (1000 if not direct_channels else 0):
1✔
4295
            raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
1✔
4296
        if budget.cltv < (576 if not direct_channels else 0):
1✔
4297
            raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_EXPIRY_TOO_SOON, data=b'')
×
4298

4299
        try:
1✔
4300
            await self.pay_to_node(
1✔
4301
                node_pubkey=outgoing_node_id,
4302
                payment_hash=payment_hash,
4303
                payment_secret=payment_secret,
4304
                amount_to_pay=amt_to_forward,
4305
                min_final_cltv_delta=cltv_budget_for_rest_of_route,
4306
                r_tags=r_tags,
4307
                invoice_features=invoice_features,
4308
                fwd_trampoline_onion=next_trampoline_onion,
4309
                budget=budget,
4310
                attempts=100,
4311
                fw_payment_key=fw_payment_key,
4312
                channels=direct_channels,
4313
            )
4314
        except OnionRoutingFailure as e:
1✔
4315
            raise
×
4316
        except FeeBudgetExceeded:
1✔
4317
            raise OnionRoutingFailure(code=OnionFailureCode.TRAMPOLINE_FEE_INSUFFICIENT, data=b'')
×
4318
        except PaymentFailure as e:
1✔
4319
            self.logger.debug(
1✔
4320
                f"maybe_forward_trampoline. PaymentFailure for {payment_hash.hex()=}, {payment_secret.hex()=}: {e!r}")
4321
            if self.uses_trampoline():
1✔
4322
                # todo: use max fee & cltv if I have more than 1 channel to the same node
4323
                trampoline_channels = set(
1✔
4324
                    [chan for chan in self.channels.values()
4325
                     if chan.is_public() and chan.is_active()
4326
                     and self.is_trampoline_peer(chan.node_id)
4327
                     and chan.can_pay(amt_to_forward)
4328
                ])
4329
                data = encode_next_trampolines(trampoline_channels)
1✔
4330
            else:
4331
                data = b''
1✔
4332
            raise OnionRoutingFailure(code=OnionFailureCode.UNKNOWN_NEXT_PEER, data=data)
1✔
4333

4334
    def maybe_refuse_to_forward_htlc_that_corresponds_to_payreq_we_created(self, payment_hash: bytes) -> bool:
1✔
4335
        """Returns True if the HTLC should be failed.
4336
        We must not forward HTLCs with a matching payment_hash to a payment request we created.
4337
        Example attack:
4338
        - Bob creates payment request with HASH1, for 1 BTC; and gives the payreq to Alice
4339
        - Alice sends htlc A->B->C, for 1 sat, with HASH1
4340
        - Bob must not release the preimage of HASH1
4341
        """
4342
        payment_info = self.get_payment_info(payment_hash, direction=RECEIVED)
1✔
4343
        # note: If we don't have the preimage for a payment request, then it must be a hold invoice.
4344
        #       Hold invoices are created by other parties (e.g. a counterparty initiating a submarine swap),
4345
        #       and it is the other party choosing the payment_hash. If we failed HTLCs with payment_hashes colliding
4346
        #       with hold invoices, then a party that can make us save a hold invoice for an arbitrary hash could
4347
        #       also make us fail arbitrary HTLCs.
4348
        return bool(payment_info and self.get_preimage(payment_hash))
1✔
4349

4350
    def create_onion_for_route(
1✔
4351
        self, *,
4352
        route: 'LNPaymentRoute',
4353
        amount_msat: int,
4354
        total_msat: int,
4355
        payment_hash: bytes,
4356
        min_final_cltv_delta: int,
4357
        payment_secret: bytes,
4358
        trampoline_onion: Optional[OnionPacket] = None,
4359
    ):
4360
        # add features learned during "init" for direct neighbour:
4361
        route[0].node_features |= self.features
1✔
4362
        local_height = self.network.get_local_height()
1✔
4363
        final_cltv_abs = local_height + min_final_cltv_delta
1✔
4364
        hops_data, amount_msat, cltv_abs = calc_hops_data_for_payment(
1✔
4365
            route,
4366
            amount_msat,
4367
            final_cltv_abs=final_cltv_abs,
4368
            total_msat=total_msat,
4369
            payment_secret=payment_secret)
4370
        self.logger.info(f"pay len(route)={len(route)}. for payment_hash={payment_hash.hex()}")
1✔
4371
        for i in range(len(route)):
1✔
4372
            self.logger.info(f"  {i}: edge={route[i].short_channel_id} hop_data={hops_data[i]!r}")
1✔
4373
        assert final_cltv_abs <= cltv_abs, (final_cltv_abs, cltv_abs)
1✔
4374
        session_key = crandom.get_rand_bytes(32)  # session_key
1✔
4375
        # if we are forwarding a trampoline payment, add trampoline onion
4376
        if trampoline_onion:
1✔
4377
            self.logger.info(f'adding trampoline onion to final payload')
1✔
4378
            trampoline_payload = dict(hops_data[-1].payload)
1✔
4379
            trampoline_payload["trampoline_onion_packet"] = {
1✔
4380
                "trampoline_onion_packet": trampoline_onion.to_bytes()
4381
            }
4382
            hops_data[-1] = dataclasses.replace(hops_data[-1], payload=trampoline_payload)
1✔
4383
            if t_hops_data := trampoline_onion._debug_hops_data:  # None if trampoline-forwarding
1✔
4384
                t_route = trampoline_onion._debug_route
1✔
4385
                assert t_route is not None
1✔
4386
                self.logger.info(f"lnpeer.pay len(t_route)={len(t_route)}")
1✔
4387
                for i in range(len(t_route)):
1✔
4388
                    self.logger.info(f"  {i}: t_node={t_route[i].end_node.hex()} hop_data={t_hops_data[i]!r}")
1✔
4389
        # create onion packet
4390
        payment_path_pubkeys = [x.node_id for x in route]
1✔
4391
        onion = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data=payment_hash) # must use another sessionkey
1✔
4392
        self.logger.info(f"starting payment. len(route)={len(hops_data)}.")
1✔
4393
        # create htlc
4394
        if cltv_abs > local_height + lnutil.NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE:
1✔
4395
            raise PaymentFailure(f"htlc expiry too far into future. (in {cltv_abs-local_height} blocks)")
×
4396
        return onion, amount_msat, cltv_abs, session_key
1✔
4397

4398
    def save_forwarding_failure(
1✔
4399
            self,
4400
            payment_key: str,
4401
            *,
4402
            error_bytes: Optional[bytes] = None,
4403
            failure_message: Optional['OnionRoutingFailure'] = None
4404
    ) -> None:
4405
        error_hex = error_bytes.hex() if error_bytes else None
1✔
4406
        failure_hex = failure_message.to_bytes().hex() if failure_message else None
1✔
4407
        self.forwarding_failures[payment_key] = (error_hex, failure_hex)
1✔
4408

4409
    def get_forwarding_failure(self, payment_key: str) -> Tuple[Optional[bytes], Optional['OnionRoutingFailure']]:
1✔
4410
        error_hex, failure_hex = self.forwarding_failures.get(payment_key, (None, None))
1✔
4411
        error_bytes = bytes.fromhex(error_hex) if error_hex else None
1✔
4412
        failure_message = OnionRoutingFailure.from_bytes(bytes.fromhex(failure_hex)) if failure_hex else None
1✔
4413
        return error_bytes, failure_message
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc