1
0

lnworker: fix type error re pending_payments, and impl malformed htlcs

In old code, in lnpeer.htlc_switch(), "error" in lnworker.pending_payments
had incorrect type.

TODO: we need tests for payment failures...
This commit is contained in:
SomberNight
2020-03-17 19:23:04 +01:00
parent 9a70b79eea
commit 2cc76fbbbd
5 changed files with 92 additions and 60 deletions

View File

@@ -53,7 +53,8 @@ from .lnutil import (Outpoint, LNPeerAddr,
UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE,
NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner,
UpdateAddHtlc, Direction, LnLocalFeatures,
ShortChannelID, PaymentAttemptLog, PaymentAttemptFailureDetails)
ShortChannelID, PaymentAttemptLog, PaymentAttemptFailureDetails,
BarePaymentAttemptLog)
from .lnutil import ln_dummy_address, ln_compare_features
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
from .lnonion import OnionFailureCode, process_onion_packet, OnionPacket
@@ -436,8 +437,7 @@ class LNWallet(LNWorker):
for channel_id, c in channels.items():
self.channels[bfh(channel_id)] = Channel(c, sweep_address=self.sweep_address, lnworker=self)
# timestamps of opening and closing transactions
self.pending_payments = defaultdict(asyncio.Future)
self.pending_payments = defaultdict(asyncio.Future) # type: Dict[bytes, asyncio.Future[BarePaymentAttemptLog]]
@ignore_exceptions
@log_exceptions
@@ -954,31 +954,36 @@ class LNWallet(LNWorker):
await peer.initialized
htlc = peer.pay(route, chan, int(lnaddr.amount * COIN * 1000), lnaddr.paymenthash, lnaddr.get_min_final_cltv_expiry())
self.network.trigger_callback('htlc_added', htlc, lnaddr, SENT)
success, preimage, reason = await self.await_payment(lnaddr.paymenthash)
if success:
payment_attempt = await self.await_payment(lnaddr.paymenthash)
if payment_attempt.success:
failure_log = None
else:
# TODO this blacklisting is fragile, consider (who to ban/penalize?):
# - we might not be able to decode "reason" (coming from update_fail_htlc).
# - handle update_fail_malformed_htlc case, where there is (kinda) no "reason"
failure_msg, sender_idx = chan.decode_onion_error(reason, route, htlc.htlc_id)
blacklist = self.handle_error_code_from_failed_htlc(failure_msg, sender_idx, route, peer)
if blacklist:
# blacklist channel after reporter node
# TODO this should depend on the error (even more granularity)
# also, we need finer blacklisting (directed edges; nodes)
try:
short_chan_id = route[sender_idx + 1].short_channel_id
except IndexError:
self.logger.info("payment destination reported error")
else:
self.network.path_finder.add_to_blacklist(short_chan_id)
if payment_attempt.error_bytes:
# TODO "decode_onion_error" might raise, catch and maybe blacklist/penalise someone?
failure_msg, sender_idx = chan.decode_onion_error(payment_attempt.error_bytes, route, htlc.htlc_id)
is_blacklisted = self.handle_error_code_from_failed_htlc(failure_msg, sender_idx, route, peer)
if is_blacklisted:
# blacklist channel after reporter node
# TODO this should depend on the error (even more granularity)
# also, we need finer blacklisting (directed edges; nodes)
try:
short_chan_id = route[sender_idx + 1].short_channel_id
except IndexError:
self.logger.info("payment destination reported error")
else:
self.network.path_finder.add_to_blacklist(short_chan_id)
else:
# probably got "update_fail_malformed_htlc". well... who to penalise now?
assert payment_attempt.error_reason is not None
sender_idx = None
failure_msg = payment_attempt.error_reason
is_blacklisted = False
failure_log = PaymentAttemptFailureDetails(sender_idx=sender_idx,
failure_msg=failure_msg,
is_blacklisted=blacklist)
is_blacklisted=is_blacklisted)
return PaymentAttemptLog(route=route,
success=success,
preimage=preimage,
success=payment_attempt.success,
preimage=payment_attempt.preimage,
failure_details=failure_log)
def handle_error_code_from_failed_htlc(self, failure_msg, sender_idx, route, peer):
@@ -1205,10 +1210,10 @@ class LNWallet(LNWorker):
if status in SAVED_PR_STATUS:
self.set_payment_status(bfh(key), status)
async def await_payment(self, payment_hash):
success, preimage, reason = await self.pending_payments[payment_hash]
async def await_payment(self, payment_hash: bytes) -> BarePaymentAttemptLog:
payment_attempt = await self.pending_payments[payment_hash]
self.pending_payments.pop(payment_hash)
return success, preimage, reason
return payment_attempt
def set_payment_status(self, payment_hash: bytes, status):
try:
@@ -1219,12 +1224,12 @@ class LNWallet(LNWorker):
info = info._replace(status=status)
self.save_payment_info(info)
def payment_failed(self, chan, payment_hash: bytes, reason: bytes):
def payment_failed(self, chan, payment_hash: bytes, payment_attempt: BarePaymentAttemptLog):
self.set_payment_status(payment_hash, PR_UNPAID)
key = payment_hash.hex()
f = self.pending_payments.get(payment_hash)
if f and not f.cancelled():
f.set_result((False, None, reason))
f.set_result(payment_attempt)
else:
chan.logger.info('received unexpected payment_failed, probably from previous session')
self.network.trigger_callback('invoice_status', key)
@@ -1237,7 +1242,10 @@ class LNWallet(LNWorker):
key = payment_hash.hex()
f = self.pending_payments.get(payment_hash)
if f and not f.cancelled():
f.set_result((True, preimage, None))
payment_attempt = BarePaymentAttemptLog(success=True,
preimage=preimage,
error_bytes=None)
f.set_result(payment_attempt)
else:
chan.logger.info('received unexpected payment_sent, probably from previous session')
self.network.trigger_callback('invoice_status', key)