Browse Source

use 'r' field in invoice when making payments (routing hints)

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 6 years ago
committed by ThomasV
parent
commit
97393d05aa
  1. 7
      electrum/lnbase.py
  2. 25
      electrum/lnrouter.py
  3. 39
      electrum/lnworker.py

7
electrum/lnbase.py

@ -1018,19 +1018,18 @@ class Peer(PrintError):
await self.receive_commitment(chan) await self.receive_commitment(chan)
self.revoke(chan) self.revoke(chan)
async def pay(self, path, chan, amount_msat, payment_hash, pubkey_in_invoice, min_final_cltv_expiry): async def pay(self, route: List[RouteEdge], chan, amount_msat, payment_hash, min_final_cltv_expiry):
assert chan.get_state() == "OPEN", chan.get_state() assert chan.get_state() == "OPEN", chan.get_state()
assert amount_msat > 0, "amount_msat is not greater zero" assert amount_msat > 0, "amount_msat is not greater zero"
height = self.network.get_local_height() height = self.network.get_local_height()
route = self.network.path_finder.create_route_from_path(path, self.lnworker.node_keypair.pubkey)
hops_data = [] hops_data = []
sum_of_deltas = sum(route_edge.channel_policy.cltv_expiry_delta for route_edge in route[1:]) sum_of_deltas = sum(route_edge.cltv_expiry_delta for route_edge in route[1:])
total_fee = 0 total_fee = 0
final_cltv_expiry_without_deltas = (height + min_final_cltv_expiry) final_cltv_expiry_without_deltas = (height + min_final_cltv_expiry)
final_cltv_expiry_with_deltas = final_cltv_expiry_without_deltas + sum_of_deltas final_cltv_expiry_with_deltas = final_cltv_expiry_without_deltas + sum_of_deltas
for idx, route_edge in enumerate(route[1:]): for idx, route_edge in enumerate(route[1:]):
hops_data += [OnionHopsDataSingle(OnionPerHop(route_edge.short_channel_id, amount_msat.to_bytes(8, "big"), final_cltv_expiry_without_deltas.to_bytes(4, "big")))] hops_data += [OnionHopsDataSingle(OnionPerHop(route_edge.short_channel_id, amount_msat.to_bytes(8, "big"), final_cltv_expiry_without_deltas.to_bytes(4, "big")))]
total_fee += route_edge.channel_policy.fee_base_msat + ( amount_msat * route_edge.channel_policy.fee_proportional_millionths // 1000000 ) total_fee += route_edge.fee_base_msat + ( amount_msat * route_edge.fee_proportional_millionths // 1000000 )
associated_data = payment_hash associated_data = payment_hash
secret_key = os.urandom(32) secret_key = os.urandom(32)
hops_data += [OnionHopsDataSingle(OnionPerHop(b"\x00"*8, amount_msat.to_bytes(8, "big"), (final_cltv_expiry_without_deltas).to_bytes(4, "big")))] hops_data += [OnionHopsDataSingle(OnionPerHop(b"\x00"*8, amount_msat.to_bytes(8, "big"), (final_cltv_expiry_without_deltas).to_bytes(4, "big")))]

25
electrum/lnrouter.py

@ -28,7 +28,7 @@ import os
import json import json
import threading import threading
from collections import namedtuple, defaultdict from collections import namedtuple, defaultdict
from typing import Sequence, Union, Tuple, Optional from typing import Sequence, List, Tuple, Optional, Dict, NamedTuple
import binascii import binascii
import base64 import base64
import asyncio import asyncio
@ -478,14 +478,13 @@ class ChannelDB(JsonDB):
direction)) direction))
class RouteEdge: class RouteEdge(NamedTuple("RouteEdge", [('node_id', bytes),
('short_channel_id', bytes),
def __init__(self, node_id: bytes, short_channel_id: bytes, ('fee_base_msat', int),
channel_policy: ChannelInfoDirectedPolicy): ('fee_proportional_millionths', int),
# "if you travel through short_channel_id, you will reach node_id" ('cltv_expiry_delta', int)])):
self.node_id = node_id """if you travel through short_channel_id, you will reach node_id"""
self.short_channel_id = short_channel_id pass
self.channel_policy = channel_policy
class LNPathFinder(PrintError): class LNPathFinder(PrintError):
@ -578,7 +577,7 @@ class LNPathFinder(PrintError):
path.reverse() path.reverse()
return path return path
def create_route_from_path(self, path, from_node_id: bytes) -> Sequence[RouteEdge]: def create_route_from_path(self, path, from_node_id: bytes) -> List[RouteEdge]:
assert type(from_node_id) is bytes assert type(from_node_id) is bytes
if path is None: if path is None:
raise Exception('cannot create route from None path') raise Exception('cannot create route from None path')
@ -591,6 +590,10 @@ class LNPathFinder(PrintError):
channel_policy = channel_info.get_policy_for_node(prev_node_id) channel_policy = channel_info.get_policy_for_node(prev_node_id)
if channel_policy is None: if channel_policy is None:
raise Exception('cannot find channel policy for short_channel_id: {}'.format(bh2u(short_channel_id))) raise Exception('cannot find channel policy for short_channel_id: {}'.format(bh2u(short_channel_id)))
route.append(RouteEdge(node_id, short_channel_id, channel_policy)) route.append(RouteEdge(node_id,
short_channel_id,
channel_policy.fee_base_msat,
channel_policy.fee_proportional_millionths,
channel_policy.cltv_expiry_delta))
prev_node_id = node_id prev_node_id = node_id
return route return route

39
electrum/lnworker.py

@ -27,6 +27,7 @@ from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
from .lnutil import LOCAL, REMOTE from .lnutil import LOCAL, REMOTE
from .lnaddr import lndecode from .lnaddr import lndecode
from .i18n import _ from .i18n import _
from .lnrouter import RouteEdge
NUM_PEERS_TARGET = 4 NUM_PEERS_TARGET = 4
@ -237,16 +238,12 @@ class LNWorker(PrintError):
def pay(self, invoice, amount_sat=None): def pay(self, invoice, amount_sat=None):
addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP) addr = lndecode(invoice, expected_hrp=constants.net.SEGWIT_HRP)
payment_hash = addr.paymenthash payment_hash = addr.paymenthash
invoice_pubkey = addr.pubkey.serialize()
amount_sat = (addr.amount * COIN) if addr.amount else amount_sat amount_sat = (addr.amount * COIN) if addr.amount else amount_sat
if amount_sat is None: if amount_sat is None:
raise InvoiceError(_("Missing amount")) raise InvoiceError(_("Missing amount"))
amount_msat = int(amount_sat * 1000) amount_msat = int(amount_sat * 1000)
# TODO use 'r' field from invoice route = self._create_route_from_invoice(decoded_invoice=addr, amount_msat=amount_msat)
path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, invoice_pubkey, amount_msat) node_id, short_channel_id = route[0].node_id, route[0].short_channel_id
if path is None:
raise PaymentFailure(_("No path found"))
node_id, short_channel_id = path[0]
peer = self.peers[node_id] peer = self.peers[node_id]
with self.lock: with self.lock:
channels = list(self.channels.values()) channels = list(self.channels.values())
@ -255,9 +252,37 @@ class LNWorker(PrintError):
break break
else: else:
raise Exception("ChannelDB returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id))) raise Exception("ChannelDB returned path with short_channel_id {} that is not in channel list".format(bh2u(short_channel_id)))
coro = peer.pay(path, chan, amount_msat, payment_hash, invoice_pubkey, addr.min_final_cltv_expiry) coro = peer.pay(route, chan, amount_msat, payment_hash, addr.min_final_cltv_expiry)
return addr, peer, asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) return addr, peer, asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
def _create_route_from_invoice(self, decoded_invoice, amount_msat) -> List[RouteEdge]:
invoice_pubkey = decoded_invoice.pubkey.serialize()
# use 'r' field from invoice
route = None # type: List[RouteEdge]
for tag_type, data in decoded_invoice.tags:
if tag_type != 'r': continue
private_route = data
if len(private_route) == 0: continue
border_node_pubkey = private_route[0][0]
path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, border_node_pubkey, amount_msat)
if path is None: continue
route = self.network.path_finder.create_route_from_path(path, self.node_keypair.pubkey)
# we need to shift the node pubkey by one towards the destination:
private_route_nodes = [edge[0] for edge in private_route][1:] + [invoice_pubkey]
private_route_rest = [edge[1:] for edge in private_route]
for node_pubkey, edge_rest in zip(private_route_nodes, private_route_rest):
short_channel_id, fee_base_msat, fee_proportional_millionths, cltv_expiry_delta = edge_rest
route.append(RouteEdge(node_pubkey, short_channel_id, fee_base_msat, fee_proportional_millionths,
cltv_expiry_delta))
break
# if could not find route using any hint; try without hint now
if route is None:
path = self.network.path_finder.find_path_for_payment(self.node_keypair.pubkey, invoice_pubkey, amount_msat)
if path is None:
raise PaymentFailure(_("No path found"))
route = self.network.path_finder.create_route_from_path(path, self.node_keypair.pubkey)
return route
def add_invoice(self, amount_sat, message): def add_invoice(self, amount_sat, message):
payment_preimage = os.urandom(32) payment_preimage = os.urandom(32)
RHASH = sha256(payment_preimage) RHASH = sha256(payment_preimage)

Loading…
Cancel
Save