From 4d4cff784092f1c4e288889c0adf95e5cdbf34ce Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 3 Feb 2026 06:53:12 +0000 Subject: [PATCH] lnwallet.pay_to_node: log r_tags from invoice and add helper "format_bolt11_routing_info_as_human_readable" --- electrum/gui/qt/main_window.py | 4 ++-- electrum/lnaddr.py | 21 +++++++++++++++-- electrum/lnworker.py | 6 +++-- tests/test_bolt11.py | 41 ++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index fe4e7fb5f..83b3e5c99 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -73,7 +73,7 @@ from electrum.exchange_rate import FxThread from electrum.simple_config import SimpleConfig from electrum.logging import Logger from electrum.lntransport import extract_nodeid, ConnStringFormatError -from electrum.lnaddr import lndecode +from electrum.lnaddr import lndecode, LnAddr from electrum.submarine_swaps import SwapServerTransport, NostrTransport from electrum.fee_policy import FeePolicy @@ -1746,7 +1746,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): grid.addWidget(QLabel(_('Text') + ':'), 8, 0) grid.addWidget(invoice_e, 8, 1) r_tags = lnaddr.get_routing_info('r') - r_tags = '\n'.join(repr([(x[0].hex(), format_short_id(x[1]), x[2], x[3]) for x in r]) for r in r_tags) + r_tags = '\n'.join(repr(r) for r in LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)) routing_e = QTextEdit(str(r_tags)) routing_e.setReadOnly(True) grid.addWidget(QLabel(_("Routing Hints") + ':'), 9, 0) diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index a5c42c750..031676840 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -302,6 +302,24 @@ class LnAddr(object): random.shuffle(r_tags) return r_tags + @staticmethod + def format_bolt11_routing_info_as_human_readable(r_tags, *, has_explicit_r_tagtype: bool = False): + """Converts the node-id bytes->hex, and the SCID bytes->"AAAxBBBxCC", e.g. for logging.""" + from .util import format_short_id + r_tags2 = [] + for r_tag in r_tags: + if has_explicit_r_tagtype: + (tagtype, path) = r_tag + assert tagtype == "r", f"found unexpected {tagtype=}" + else: + path = r_tag + path2 = [ + (edge[0].hex(), format_short_id(edge[1]), edge[2], edge[3], edge[4]) + for edge in path] + r_tag2 = (tagtype, path2) if has_explicit_r_tagtype else path2 + r_tags2.append(r_tag2) + return r_tags2 + def get_amount_msat(self) -> Optional[int]: if self.amount is None: return None @@ -374,8 +392,7 @@ class LnAddr(object): 'unknown_tags': self.unknown_tags, } if ln_routing_info := self.get_routing_info('r'): - # show the last hop of routing hints. (our invoices only have one hop) - d['r_tags'] = [str((a.hex(),b.hex(),c,d,e)) for a,b,c,d,e in ln_routing_info[-1]] + d['r_tags'] = self.format_bolt11_routing_info_as_human_readable(ln_routing_info) return d diff --git a/electrum/lnworker.py b/electrum/lnworker.py index c8846595d..89319f7f8 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -1926,6 +1926,7 @@ class LNWallet(Logger): f"pay_to_node starting session for RHASH={payment_hash.hex()}. " f"using_trampoline={self.uses_trampoline()}. " f"invoice_features={paysession.invoice_features.get_names()}. " + f"r_tags={LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)}. " f"{amount_to_pay=} msat. {budget=}") if not self.uses_trampoline(): self.logger.info( @@ -2568,7 +2569,8 @@ class LNWallet(Logger): assert amount_msat is None or amount_msat > 0 timestamp = int(time.time()) routing_hints = self.calc_routing_hints_for_invoice(amount_msat, channels=channels) - self.logger.info(f"creating bolt11 invoice with routing_hints: {routing_hints}, sat: {(amount_msat or 0) // 1000}") + formatted_r_hints = LnAddr.format_bolt11_routing_info_as_human_readable(routing_hints, has_explicit_r_tagtype=True) + self.logger.info(f"creating bolt11 invoice with routing_hints: {formatted_r_hints}, sat: {(amount_msat or 0) // 1000}") payment_secret = self.get_payment_secret(payment_info.payment_hash) amount_btc = amount_msat/Decimal(COIN*1000) if amount_msat else None min_final_cltv_delta = payment_info.min_final_cltv_delta + MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE @@ -3914,7 +3916,7 @@ class LNWallet(Logger): invoice_features = payload["invoice_features"]["invoice_features"] invoice_routing_info = payload["invoice_routing_info"]["invoice_routing_info"] r_tags = decode_routing_info(invoice_routing_info) - self.logger.info(f'r_tags {r_tags}') + self.logger.info(f'r_tags {LnAddr.format_bolt11_routing_info_as_human_readable(r_tags)}') # TODO legacy mpp payment, use total_msat from trampoline onion else: self.logger.info('forward_trampoline: end-to-end') diff --git a/tests/test_bolt11.py b/tests/test_bolt11.py index fd1884ff3..a900545f5 100644 --- a/tests/test_bolt11.py +++ b/tests/test_bolt11.py @@ -9,6 +9,7 @@ from electrum.segwit_addr import bech32_encode, bech32_decode from electrum import segwit_addr from electrum.lnutil import UnknownEvenFeatureBits, LnFeatures, IncompatibleLightningFeatures from electrum import constants +from electrum.util import bfh, ShortID from . import ElectrumTestCase @@ -169,3 +170,43 @@ class TestBolt11(ElectrumTestCase): lnaddr.validate_and_compare_features(LnFeatures((1 << 8) + (1 << 14) + (1 << 15))) with self.assertRaises(IncompatibleLightningFeatures): lnaddr.validate_and_compare_features(LnFeatures((1 << 8) + (1 << 14) + (1 << 16))) + + def test_format_bolt11_routing_info_as_human_readable(self): + r_tags_expl = [ + ['r', [(bfh('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), bfh('0102030405060708'), 1, 20, 3), + (bfh('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), bfh('030405060708090a'), 2, 30, 4)]], + ['r', [(bfh('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9'), bfh('f4240000000002cd'), 0, 1, 40)]], + ] + self.assertEqual( + [ + ('r', [('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255', + '66051x263430x1800', 1, 20, 3), + ('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255', + '197637x395016x2314', 2, 30, 4)] + ), + ('r', [('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9', + '16000000x0x717', 0, 1, 40),] + ), + ], + LnAddr.format_bolt11_routing_info_as_human_readable(r_tags_expl, has_explicit_r_tagtype=True)) + + r_tags_impl = [ + [(bfh('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), bfh('0102030405060708'), 1, 20, 3), + (bfh('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), bfh('030405060708090a'), 2, 30, 4)], + [(bfh('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9'), bfh('f4240000000002cd'), 0, 1, 40)], + ] + self.assertEqual( + [ + [('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255', + '66051x263430x1800', 1, 20, 3), + ('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255', + '197637x395016x2314', 2, 30, 4)], + [('038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9', + '16000000x0x717', 0, 1, 40),], + ], + LnAddr.format_bolt11_routing_info_as_human_readable(r_tags_impl, has_explicit_r_tagtype=False)) + + for has_explicit_r_tagtype in (False, True): + self.assertEqual( + [], + LnAddr.format_bolt11_routing_info_as_human_readable([], has_explicit_r_tagtype=has_explicit_r_tagtype))