Browse Source

make our channels private, and put routing hints in invoices we create

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 6 years ago
committed by ThomasV
parent
commit
029ec5a5ab
  1. 46
      electrum/lnbase.py
  2. 1
      electrum/lnhtlc.py
  3. 11
      electrum/lnrouter.py
  4. 1
      electrum/lnwatcher.py
  5. 52
      electrum/lnworker.py

46
electrum/lnbase.py

@ -12,6 +12,7 @@ import time
import hashlib
import hmac
from functools import partial
from typing import List
import cryptography.hazmat.primitives.ciphers.aead as AEAD
import aiorpcx
@ -31,6 +32,7 @@ from .lnutil import (Outpoint, ChannelConfig, LocalState,
funding_output_script, get_ecdh, get_per_commitment_secret_from_seed,
secret_to_pubkey, LNPeerAddr, PaymentFailure,
LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily)
from .lnrouter import NotFoundChanAnnouncementForUpdate, RouteEdge
def channel_id_from_funding_tx(funding_txid, funding_index):
@ -443,7 +445,16 @@ class Peer(PrintError):
pass
def on_channel_update(self, payload):
self.channel_db.on_channel_update(payload)
try:
self.channel_db.on_channel_update(payload)
except NotFoundChanAnnouncementForUpdate:
# If it's for a direct channel with this peer, save it in chan.
# Note that this is prone to a race.. we might not have a short_channel_id
# associated with the channel in some cases
short_channel_id = payload['short_channel_id']
for chan in self.channels.values():
if chan.short_channel_id_predicted == short_channel_id:
chan.pending_channel_update_message = payload
def on_channel_announcement(self, payload):
self.channel_db.on_channel_announcement(payload)
@ -550,7 +561,7 @@ class Peer(PrintError):
first_per_commitment_point=per_commitment_point_first,
to_self_delay=local_config.to_self_delay,
max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
channel_flags=0x01, # publicly announcing channel
channel_flags=0x00, # not willing to announce channel
channel_reserve_satoshis=546
)
self.send_message(msg)
@ -833,6 +844,9 @@ class Peer(PrintError):
Runs on the Network thread.
"""
if not chan.local_state.was_announced and funding_tx_depth >= 6:
# don't announce our channels
# FIXME should this be a field in chan.local_state maybe?
return
chan.local_state=chan.local_state._replace(was_announced=True)
coro = self.handle_announcements(chan)
self.lnworker.save_channel(chan)
@ -887,25 +901,39 @@ class Peer(PrintError):
chan.set_state("OPEN")
self.network.trigger_callback('channel', chan)
# add channel to database
node_ids = [self.pubkey, self.lnworker.node_keypair.pubkey]
pubkey_ours = self.lnworker.node_keypair.pubkey
pubkey_theirs = self.pubkey
node_ids = [pubkey_theirs, pubkey_ours]
bitcoin_keys = [chan.local_config.multisig_key.pubkey, chan.remote_config.multisig_key.pubkey]
sorted_node_ids = list(sorted(node_ids))
if sorted_node_ids != node_ids:
node_ids = sorted_node_ids
bitcoin_keys.reverse()
now = int(time.time()).to_bytes(4, byteorder="big")
# note: we inject a channel announcement, and a channel update (for outgoing direction)
# This is atm needed for
# - finding routes
# - the ChanAnn is needed so that we can anchor to it a future ChanUpd
# that the remote sends, even if the channel was not announced
# (from BOLT-07: "MAY create a channel_update to communicate the channel
# parameters to the final node, even though the channel has not yet been announced")
self.channel_db.on_channel_announcement({"short_channel_id": chan.short_channel_id, "node_id_1": node_ids[0], "node_id_2": node_ids[1],
'chain_hash': constants.net.rev_genesis_bytes(), 'len': b'\x00\x00', 'features': b'',
'bitcoin_key_1': bitcoin_keys[0], 'bitcoin_key_2': bitcoin_keys[1]},
trusted=True)
self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x01', 'cltv_expiry_delta': b'\x90',
'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01',
'chain_hash': constants.net.rev_genesis_bytes(), 'timestamp': now},
trusted=True)
self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': b'\x00', 'cltv_expiry_delta': b'\x90',
# only inject outgoing direction:
flags = b'\x00' if node_ids[0] == pubkey_ours else b'\x01'
now = int(time.time()).to_bytes(4, byteorder="big")
self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'flags': flags, 'cltv_expiry_delta': b'\x90',
'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01',
'chain_hash': constants.net.rev_genesis_bytes(), 'timestamp': now},
trusted=True)
# peer may have sent us a channel update for the incoming direction previously
# note: if we were offline when the 3rd conf happened, lnd will never send us this channel_update
# see https://github.com/lightningnetwork/lnd/issues/1347
#self.send_message(gen_msg("query_short_channel_ids", chain_hash=constants.net.rev_genesis_bytes(),
# len=9, encoded_short_ids=b'\x00'+chan.short_channel_id))
if hasattr(chan, 'pending_channel_update_message'):
self.on_channel_update(chan.pending_channel_update_message)
self.print_error("CHANNEL OPENING COMPLETED")

1
electrum/lnhtlc.py

@ -143,6 +143,7 @@ class HTLCStateMachine(PrintError):
self.funding_outpoint = Outpoint(**decodeAll(state["funding_outpoint"])) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
self.node_id = maybeDecode("node_id", state["node_id"]) if type(state["node_id"]) is not bytes else state["node_id"]
self.short_channel_id = maybeDecode("short_channel_id", state["short_channel_id"]) if type(state["short_channel_id"]) is not bytes else state["short_channel_id"]
self.short_channel_id_predicted = self.short_channel_id
self.onion_keys = {int(k): bfh(v) for k,v in state['onion_keys'].items()} if 'onion_keys' in state else {}
# FIXME this is a tx serialised in the custom electrum partial tx format.

11
electrum/lnrouter.py

@ -45,6 +45,9 @@ from .lnutil import LN_GLOBAL_FEATURE_BITS, LNPeerAddr
class UnknownEvenFeatureBits(Exception): pass
class NotFoundChanAnnouncementForUpdate(Exception): pass
class ChannelInfo(PrintError):
def __init__(self, channel_announcement_payload):
@ -126,7 +129,7 @@ class ChannelInfo(PrintError):
else:
self.policy_node2 = new_policy
def get_policy_for_node(self, node_id):
def get_policy_for_node(self, node_id: bytes) -> 'ChannelInfoDirectedPolicy':
if node_id == self.node_id_1:
return self.policy_node1
elif node_id == self.node_id_2:
@ -271,7 +274,7 @@ class ChannelDB(JsonDB):
JsonDB.__init__(self, path)
self.lock = threading.RLock()
self._id_to_channel_info = {}
self._id_to_channel_info = {} # type: Dict[bytes, ChannelInfo]
self._channels_for_node = defaultdict(set) # node -> set(short_channel_id)
self.nodes = {} # node_id -> NodeInfo
self._recent_peers = []
@ -340,7 +343,7 @@ class ChannelDB(JsonDB):
# number of channels
return len(self._id_to_channel_info)
def get_channel_info(self, channel_id) -> Optional[ChannelInfo]:
def get_channel_info(self, channel_id: bytes) -> Optional[ChannelInfo]:
return self._id_to_channel_info.get(channel_id, None)
def get_channels_for_node(self, node_id):
@ -401,7 +404,7 @@ class ChannelDB(JsonDB):
channel_info = self._id_to_channel_info.get(short_channel_id, None)
if channel_info is None:
self.print_error("could not find", short_channel_id)
return
raise NotFoundChanAnnouncementForUpdate()
channel_info.on_channel_update(msg_payload, trusted=trusted)
def on_node_announcement(self, msg_payload):

1
electrum/lnwatcher.py

@ -24,6 +24,7 @@ class LNWatcher(PrintError):
path = os.path.join(network.config.path, "watcher_db")
storage = WalletStorage(path)
self.addr_sync = AddressSynchronizer(storage)
self.addr_sync.diagnostic_name = lambda: 'LnWatcherAS'
self.addr_sync.start_network(network)
self.lock = threading.RLock()
self.watched_addresses = set()

52
electrum/lnworker.py

@ -48,8 +48,8 @@ class LNWorker(PrintError):
self.ln_keystore = self._read_ln_keystore()
self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0)
self.config = network.config
self.peers = {} # pubkey -> Peer
self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))}
self.peers = {} # type: Dict[bytes, Peer] # pubkey -> Peer
self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))} # type: Dict[bytes, HTLCStateMachine]
for c in self.channels.values():
c.lnwatcher = network.lnwatcher
c.sweep_address = self.sweep_address
@ -126,21 +126,19 @@ class LNWorker(PrintError):
def save_short_chan_id(self, chan):
"""
Checks if the Funding TX has been mined. If it has save the short channel ID to disk and return the new OpenChannel.
If the Funding TX has not been mined, return None
Checks if Funding TX has been mined. If it has, save the short channel ID in chan;
if it's also deep enough, also save to disk.
Returns tuple (mined_deep_enough, num_confirmations).
"""
assert chan.get_state() in ["OPEN", "OPENING"]
peer = self.peers[chan.node_id]
addr_sync = self.network.lnwatcher.addr_sync
conf = addr_sync.get_tx_height(chan.funding_outpoint.txid).conf
if conf >= chan.constraints.funding_txn_minimum_depth:
if conf > 0:
block_height, tx_pos = addr_sync.get_txpos(chan.funding_outpoint.txid)
if tx_pos == -1:
self.print_error('funding tx is not yet SPV verified.. but there are '
'already enough confirmations (currently {})'.format(conf))
return False, conf
chan.short_channel_id = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index)
assert tx_pos >= 0
chan.short_channel_id_predicted = calc_short_channel_id(block_height, tx_pos, chan.funding_outpoint.output_index)
if conf >= chan.constraints.funding_txn_minimum_depth > 0:
chan.short_channel_id = chan.short_channel_id_predicted
self.save_channel(chan)
return True, conf
return False, conf
@ -244,6 +242,7 @@ class LNWorker(PrintError):
if amount_sat is None:
raise InvoiceError(_("Missing amount"))
amount_msat = int(amount_sat * 1000)
# TODO use 'r' field from invoice
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"))
@ -263,12 +262,39 @@ class LNWorker(PrintError):
payment_preimage = os.urandom(32)
RHASH = sha256(payment_preimage)
amount_btc = amount_sat/Decimal(COIN) if amount_sat else None
pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]), self.node_keypair.privkey)
routing_hints = self._calc_routing_hints_for_invoice(amount_sat)
pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]+routing_hints),
self.node_keypair.privkey)
self.invoices[bh2u(payment_preimage)] = pay_req
self.wallet.storage.put('lightning_invoices', self.invoices)
self.wallet.storage.write()
return pay_req
def _calc_routing_hints_for_invoice(self, amount_sat):
"""calculate routing hints (BOLT-11 'r' field)"""
routing_hints = []
with self.lock:
channels = list(self.channels.values())
# note: currently we add *all* our channels; but this might be a privacy leak?
for chan in channels:
# check channel is open
if chan.get_state() != "OPEN": continue
# check channel has sufficient balance
# FIXME because of on-chain fees of ctx, this check is insufficient
if amount_sat and chan.balance(REMOTE) // 1000 < amount_sat: continue
chan_id = chan.short_channel_id
assert type(chan_id) is bytes, chan_id
channel_info = self.channel_db.get_channel_info(chan_id)
if not channel_info: continue
policy = channel_info.get_policy_for_node(chan.node_id)
if not policy: continue
routing_hints.append(('r', [(chan.node_id,
chan_id,
policy.fee_base_msat,
policy.fee_proportional_millionths,
policy.cltv_expiry_delta)]))
return routing_hints
def delete_invoice(self, payreq_key):
try:
del self.invoices[payreq_key]

Loading…
Cancel
Save