mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-01 01:35:20 +00:00
start failing htlcs
This commit is contained in:
parent
ded11b4d9e
commit
d511ecdc00
5 changed files with 96 additions and 29 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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…
Add table
Reference in a new issue