lnwallet.pay_to_node: log r_tags from invoice
What changed, and why it matters
This commit is a small code-quality and logging improvement. It adds a helper that converts raw Lightning invoice routing hints into a more human-readable form (hex node IDs and readable channel IDs) and uses that helper in log messages and the invoice details window. There is no security fix or behavior change.
No security action required. This is a routine refactoring/logging improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces LnAddr.format_bolt11_routing_info_as_human_readable() in electrum/lnaddr.py, which converts BOLT11 routing hint tuples from raw bytes (node_id, short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta) to a human-readable form (hex node_id, ‘blockxtxxoutput’ SCID, and the numeric fields). It then replaces ad-hoc repr/str formatting in the Qt invoice dialog, in lnaddr.to_debug_json(), and in several logging calls in lnworker.py (pay_to_node, invoice creation, and forward_trampoline). A unit test is added in tests/test_bolt11.py. No cryptographic, network, or permission logic is changed.
Changed components
electrum/lnaddr.pyelectrum/lnworker.pyelectrum/gui/qt/main_window.pytests/test_bolt11.pyInspect captured patch +66 / −6
diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
index fe4e7fb..83b3e5c 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 a5c42c7..0316768 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 c884659..89319f7 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 fd1884f..a900545 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))
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.