Browse Source

start failing htlcs

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
SomberNight 6 years ago
committed by ThomasV
parent
commit
d511ecdc00
  1. 86
      electrum/lnbase.py
  2. 10
      electrum/lnchan.py
  3. 8
      electrum/lnonion.py
  4. 8
      electrum/lnutil.py
  5. 13
      electrum/lnworker.py

86
electrum/lnbase.py

@ -10,7 +10,7 @@ import asyncio
import os import os
import time import time
from functools import partial from functools import partial
from typing import List, Tuple from typing import List, Tuple, Dict
import traceback import traceback
import sys import sys
@ -23,15 +23,15 @@ from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string
from . import constants from . import constants
from .util import PrintError, bh2u, print_error, bfh, log_exceptions, list_enabled_bits, ignore_exceptions from .util import PrintError, bh2u, print_error, bfh, log_exceptions, list_enabled_bits, ignore_exceptions
from .transaction import Transaction, TxOutput from .transaction import Transaction, TxOutput
from .lnonion import new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
from .lnaddr import lndecode process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage)
from .lnchan import Channel, RevokeAndAck, htlcsum from .lnchan import Channel, RevokeAndAck, htlcsum
from .lnutil import (Outpoint, LocalConfig, ChannelConfig, from .lnutil import (Outpoint, LocalConfig, ChannelConfig,
RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
funding_output_script, get_per_commitment_secret_from_seed, funding_output_script, get_per_commitment_secret_from_seed,
secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures, secret_to_pubkey, LNPeerAddr, PaymentFailure, LnLocalFeatures,
LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily, LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily,
get_ln_flag_pair_of_bit, privkey_to_pubkey) get_ln_flag_pair_of_bit, privkey_to_pubkey, UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_ACCEPTED)
from .lnutil import LightningPeerConnectionClosed, HandshakeFailed from .lnutil import LightningPeerConnectionClosed, HandshakeFailed
from .lnrouter import NotFoundChanAnnouncementForUpdate, RouteEdge from .lnrouter import NotFoundChanAnnouncementForUpdate, RouteEdge
from .lntransport import LNTransport from .lntransport import LNTransport
@ -231,7 +231,7 @@ class Peer(PrintError):
self.initialized.set_result(True) self.initialized.set_result(True)
@property @property
def channels(self): def channels(self) -> Dict[bytes, Channel]:
return self.lnworker.channels_for_peer(self.peer_addr.pubkey) return self.lnworker.channels_for_peer(self.peer_addr.pubkey)
def diagnostic_name(self): def diagnostic_name(self):
@ -877,8 +877,7 @@ class Peer(PrintError):
failure_msg, sender_idx = decode_onion_error(error_reason, failure_msg, sender_idx = decode_onion_error(error_reason,
[x.node_id for x in route], [x.node_id for x in route],
chan.onion_keys[htlc_id]) chan.onion_keys[htlc_id])
code = OnionFailureCode(failure_msg.code) code, data = failure_msg.code, failure_msg.data
data = failure_msg.data
self.print_error("UPDATE_FAIL_HTLC", repr(code), data) self.print_error("UPDATE_FAIL_HTLC", repr(code), data)
self.print_error(f"error reported by {bh2u(route[sender_idx].node_id)}") self.print_error(f"error reported by {bh2u(route[sender_idx].node_id)}")
# handle some specific error codes # handle some specific error codes
@ -1000,30 +999,85 @@ class Peer(PrintError):
self.print_error('on_update_add_htlc') self.print_error('on_update_add_htlc')
# check if this in our list of requests # check if this in our list of requests
payment_hash = payload["payment_hash"] payment_hash = payload["payment_hash"]
preimage, invoice = self.lnworker.get_invoice(payment_hash)
expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000)
channel_id = payload['channel_id'] channel_id = payload['channel_id']
htlc_id = int.from_bytes(payload["id"], 'big') htlc_id = int.from_bytes(payload["id"], 'big')
cltv_expiry = int.from_bytes(payload["cltv_expiry"], 'big') cltv_expiry = int.from_bytes(payload["cltv_expiry"], 'big')
amount_msat = int.from_bytes(payload["amount_msat"], 'big') amount_msat_htlc = int.from_bytes(payload["amount_msat"], 'big')
onion_packet = OnionPacket.from_bytes(payload["onion_routing_packet"])
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
chan = self.channels[channel_id] chan = self.channels[channel_id]
assert htlc_id == chan.config[REMOTE].next_htlc_id, (htlc_id, chan.config[REMOTE].next_htlc_id)
assert chan.get_state() == "OPEN" assert chan.get_state() == "OPEN"
# TODO verify sanity of their cltv expiry assert htlc_id == chan.config[REMOTE].next_htlc_id, (htlc_id, chan.config[REMOTE].next_htlc_id) # TODO fail channel instead
assert amount_msat == expected_received_msat if cltv_expiry >= 500_000_000:
htlc = {'amount_msat': amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry} pass # TODO fail the channel
# add htlc
htlc = {'amount_msat': amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry}
chan.receive_htlc(htlc) chan.receive_htlc(htlc)
assert (await self.receive_commitment(chan)) <= 1 assert (await self.receive_commitment(chan)) <= 1
self.revoke(chan) self.revoke(chan)
self.send_commitment(chan) self.send_commitment(chan)
await self.receive_revoke(chan) await self.receive_revoke(chan)
# maybe fail htlc
if not processed_onion.are_we_final:
# no forwarding for now
reason = OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
try:
preimage, invoice = self.lnworker.get_invoice(payment_hash)
except UnknownPaymentHash:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_PAYMENT_HASH, data=b'')
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000) if invoice.amount is not None else None
if expected_received_msat is not None and \
(amount_msat_htlc < expected_received_msat or amount_msat_htlc > 2 * expected_received_msat):
reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_PAYMENT_AMOUNT, data=b'')
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
local_height = self.network.get_local_height()
if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > cltv_expiry:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'')
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
cltv_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.outgoing_cltv_value, byteorder="big")
if cltv_from_onion != cltv_expiry:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY,
data=cltv_expiry.to_bytes(4, byteorder="big"))
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
amount_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.amt_to_forward, byteorder="big")
if amount_from_onion > amount_msat_htlc:
reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT,
data=amount_msat_htlc.to_bytes(8, byteorder="big"))
await self.fail_htlc(chan, htlc_id, onion_packet, reason)
return
# settle htlc
await self.settle_htlc(chan, htlc_id, preimage)
async def settle_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
chan.settle_htlc(preimage, htlc_id) chan.settle_htlc(preimage, htlc_id)
await self.update_channel(chan, "update_fulfill_htlc", channel_id=channel_id, id=htlc_id, payment_preimage=preimage) await self.update_channel(chan, "update_fulfill_htlc",
channel_id=chan.channel_id,
id=htlc_id,
payment_preimage=preimage)
self.lnworker.save_channel(chan) self.lnworker.save_channel(chan)
self.network.trigger_callback('ln_message', self.lnworker, 'Payment received') self.network.trigger_callback('ln_message', self.lnworker, 'Payment received')
async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket,
reason: OnionRoutingFailureMessage):
self.print_error(f"failing received htlc {(bh2u(chan.channel_id), htlc_id)}. reason: {reason}")
chan.fail_htlc(htlc_id)
error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey)
await self.update_channel(chan, "update_fail_htlc",
channel_id=chan.channel_id,
id=htlc_id,
len=len(error_packet),
reason=error_packet)
self.lnworker.save_channel(chan)
def on_revoke_and_ack(self, payload): def on_revoke_and_ack(self, payload):
print("got revoke_and_ack") self.print_error("got revoke_and_ack")
channel_id = payload["channel_id"] channel_id = payload["channel_id"]
self.revoke_and_ack[channel_id].put_nowait(payload) self.revoke_and_ack[channel_id].put_nowait(payload)

10
electrum/lnchan.py

@ -533,13 +533,17 @@ class Channel(PrintError):
self.log[LOCAL]['settles'].append(htlc_id) self.log[LOCAL]['settles'].append(htlc_id)
# not saving preimage because it's already saved in LNWorker.invoices # not saving preimage because it's already saved in LNWorker.invoices
def receive_htlc_settle(self, preimage, htlc_index): def receive_htlc_settle(self, preimage, htlc_id):
self.print_error("receive_htlc_settle") self.print_error("receive_htlc_settle")
htlc = self.log[LOCAL]['adds'][htlc_index] htlc = self.log[LOCAL]['adds'][htlc_id]
assert htlc.payment_hash == sha256(preimage) assert htlc.payment_hash == sha256(preimage)
self.log[REMOTE]['settles'].append(htlc_index) self.log[REMOTE]['settles'].append(htlc_id)
# we don't save the preimage because we don't need to forward it anyway # we don't save the preimage because we don't need to forward it anyway
def fail_htlc(self, htlc_id):
self.print_error("fail_htlc")
self.log[REMOTE]['adds'].pop(htlc_id)
def receive_fail_htlc(self, htlc_id): def receive_fail_htlc(self, htlc_id):
self.print_error("receive_fail_htlc") self.print_error("receive_fail_htlc")
self.log[LOCAL]['adds'].pop(htlc_id) self.log[LOCAL]['adds'].pop(htlc_id)

8
electrum/lnonion.py

@ -24,9 +24,7 @@
# SOFTWARE. # SOFTWARE.
import hashlib import hashlib
import hmac from typing import Sequence, List, Tuple, NamedTuple
from collections import namedtuple
from typing import Sequence, List, Tuple
from enum import IntEnum, IntFlag from enum import IntEnum, IntFlag
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
@ -231,7 +229,9 @@ def generate_cipher_stream(stream_key: bytes, num_bytes: int) -> bytes:
return encryptor.update(bytes(num_bytes)) return encryptor.update(bytes(num_bytes))
ProcessedOnionPacket = namedtuple("ProcessedOnionPacket", ["are_we_final", "hop_data", "next_packet"]) ProcessedOnionPacket = NamedTuple("ProcessedOnionPacket", [("are_we_final", bool),
("hop_data", OnionHopsDataSingle),
("next_packet", OnionPacket)])
# TODO replay protection # TODO replay protection

8
electrum/lnutil.py

@ -62,7 +62,13 @@ class LightningPeerConnectionClosed(LightningError): pass
class UnableToDeriveSecret(LightningError): pass class UnableToDeriveSecret(LightningError): pass
class HandshakeFailed(LightningError): pass class HandshakeFailed(LightningError): pass
class PaymentFailure(LightningError): pass class PaymentFailure(LightningError): pass
class ConnStringFormatError(LightningError): pass class ConnStringFormatError(LightningError): pass
class UnknownPaymentHash(LightningError): pass
# TODO make configurable?
MIN_FINAL_CLTV_EXPIRY_ACCEPTED = 144
MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE = MIN_FINAL_CLTV_EXPIRY_ACCEPTED + 1
class RevocationStore: class RevocationStore:

13
electrum/lnworker.py

@ -24,8 +24,8 @@ from .lnchan import Channel
from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr, from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
get_compressed_pubkey_from_bech32, extract_nodeid, get_compressed_pubkey_from_bech32, extract_nodeid,
PaymentFailure, split_host_port, ConnStringFormatError, PaymentFailure, split_host_port, ConnStringFormatError,
generate_keypair, LnKeyFamily) generate_keypair, LnKeyFamily, LOCAL, REMOTE,
from .lnutil import LOCAL, REMOTE UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)
from .lnaddr import lndecode from .lnaddr import lndecode
from .i18n import _ from .i18n import _
from .lnrouter import RouteEdge from .lnrouter import RouteEdge
@ -318,20 +318,23 @@ class LNWorker(PrintError):
if not routing_hints: if not routing_hints:
self.print_error("Warning. No routing hints added to invoice. " self.print_error("Warning. No routing hints added to invoice. "
"Other clients will likely not be able to send to us.") "Other clients will likely not be able to send to us.")
pay_req = lnencode(LnAddr(RHASH, amount_btc, tags=[('d', message)]+routing_hints), pay_req = lnencode(LnAddr(RHASH, amount_btc,
tags=[('d', message),
('c', MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE)]
+ routing_hints),
self.node_keypair.privkey) self.node_keypair.privkey)
self.invoices[bh2u(payment_preimage)] = pay_req self.invoices[bh2u(payment_preimage)] = pay_req
self.wallet.storage.put('lightning_invoices', self.invoices) self.wallet.storage.put('lightning_invoices', self.invoices)
self.wallet.storage.write() self.wallet.storage.write()
return pay_req return pay_req
def get_invoice(self, payment_hash): def get_invoice(self, payment_hash: bytes) -> Tuple[bytes, LnAddr]:
for k in self.invoices.keys(): for k in self.invoices.keys():
preimage = bfh(k) preimage = bfh(k)
if sha256(preimage) == payment_hash: if sha256(preimage) == payment_hash:
return preimage, lndecode(self.invoices[k], expected_hrp=constants.net.SEGWIT_HRP) return preimage, lndecode(self.invoices[k], expected_hrp=constants.net.SEGWIT_HRP)
else: else:
raise Exception('unknown payment hash') raise UnknownPaymentHash()
def _calc_routing_hints_for_invoice(self, amount_sat): def _calc_routing_hints_for_invoice(self, amount_sat):
"""calculate routing hints (BOLT-11 'r' field)""" """calculate routing hints (BOLT-11 'r' field)"""

Loading…
Cancel
Save