lnworker: nicer logs/error msgs for payment failure
try to decode onion error and log it in human-readable form
This commit is contained in:
@@ -7,7 +7,11 @@ from collections import OrderedDict
|
|||||||
from .lnutil import OnionFailureCodeMetaFlag
|
from .lnutil import OnionFailureCodeMetaFlag
|
||||||
|
|
||||||
|
|
||||||
class MalformedMsg(Exception): pass
|
class FailedToParseMsg(Exception): pass
|
||||||
|
|
||||||
|
class MalformedMsg(FailedToParseMsg): pass
|
||||||
|
class UnknownMsgType(FailedToParseMsg): pass
|
||||||
|
|
||||||
class UnknownMsgFieldType(MalformedMsg): pass
|
class UnknownMsgFieldType(MalformedMsg): pass
|
||||||
class UnexpectedEndOfStream(MalformedMsg): pass
|
class UnexpectedEndOfStream(MalformedMsg): pass
|
||||||
class FieldEncodingNotMinimal(MalformedMsg): pass
|
class FieldEncodingNotMinimal(MalformedMsg): pass
|
||||||
@@ -465,13 +469,17 @@ class LNSerializer:
|
|||||||
Decode Lightning message by reading the first
|
Decode Lightning message by reading the first
|
||||||
two bytes to determine message type.
|
two bytes to determine message type.
|
||||||
|
|
||||||
Returns message type string and parsed message contents dict
|
Returns message type string and parsed message contents dict,
|
||||||
|
or raises FailedToParseMsg.
|
||||||
"""
|
"""
|
||||||
#print(f"decode_msg >>> {data.hex()}")
|
#print(f"decode_msg >>> {data.hex()}")
|
||||||
assert len(data) >= 2
|
assert len(data) >= 2
|
||||||
msg_type_bytes = data[:2]
|
msg_type_bytes = data[:2]
|
||||||
msg_type_int = int.from_bytes(msg_type_bytes, byteorder="big", signed=False)
|
msg_type_int = int.from_bytes(msg_type_bytes, byteorder="big", signed=False)
|
||||||
scheme = self.msg_scheme_from_type[msg_type_bytes]
|
try:
|
||||||
|
scheme = self.msg_scheme_from_type[msg_type_bytes]
|
||||||
|
except KeyError:
|
||||||
|
raise UnknownMsgType(f"msg_type={msg_type_int}") # TODO even/odd type?
|
||||||
assert scheme[0][2] == msg_type_int
|
assert scheme[0][2] == msg_type_int
|
||||||
msg_type_name = scheme[0][1]
|
msg_type_name = scheme[0][1]
|
||||||
parsed = {}
|
parsed = {}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING
|
from typing import Sequence, List, Tuple, NamedTuple, TYPE_CHECKING, Dict, Any, Optional
|
||||||
from enum import IntEnum, IntFlag
|
from enum import IntEnum, IntFlag
|
||||||
|
|
||||||
from . import ecc
|
from . import ecc
|
||||||
@@ -33,7 +33,7 @@ from .crypto import sha256, hmac_oneshot, chacha20_encrypt
|
|||||||
from .util import bh2u, profiler, xor_bytes, bfh
|
from .util import bh2u, profiler, xor_bytes, bfh
|
||||||
from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH,
|
from .lnutil import (get_ecdh, PaymentFailure, NUM_MAX_HOPS_IN_PAYMENT_PATH,
|
||||||
NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag)
|
NUM_MAX_EDGES_IN_PAYMENT_PATH, ShortChannelID, OnionFailureCodeMetaFlag)
|
||||||
from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int
|
from .lnmsg import OnionWireSerializer, read_bigsize_int, write_bigsize_int, UnknownMsgType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lnrouter import LNPaymentRoute
|
from .lnrouter import LNPaymentRoute
|
||||||
@@ -431,7 +431,7 @@ class OnionRoutingFailure(Exception):
|
|||||||
try:
|
try:
|
||||||
failure_code = OnionFailureCode(failure_code)
|
failure_code = OnionFailureCode(failure_code)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass # uknown failure code
|
pass # unknown failure code
|
||||||
failure_data = failure_msg[2:]
|
failure_data = failure_msg[2:]
|
||||||
return OnionRoutingFailure(failure_code, failure_data)
|
return OnionRoutingFailure(failure_code, failure_data)
|
||||||
|
|
||||||
@@ -440,6 +440,13 @@ class OnionRoutingFailure(Exception):
|
|||||||
return str(self.code.name)
|
return str(self.code.name)
|
||||||
return f"Unknown error ({self.code!r})"
|
return f"Unknown error ({self.code!r})"
|
||||||
|
|
||||||
|
def decode_data(self) -> Optional[Dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
message_type, payload = OnionWireSerializer.decode_msg(self.to_bytes())
|
||||||
|
except UnknownMsgType:
|
||||||
|
payload = None
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def construct_onion_error(
|
def construct_onion_error(
|
||||||
reason: OnionRoutingFailure,
|
reason: OnionRoutingFailure,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from .transaction import BCDataStream
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lnchannel import Channel, AbstractChannel
|
from .lnchannel import Channel, AbstractChannel
|
||||||
from .lnrouter import LNPaymentRoute
|
from .lnrouter import LNPaymentRoute
|
||||||
from .lnonion import OnionRoutingFailureMessage
|
from .lnonion import OnionRoutingFailure
|
||||||
|
|
||||||
|
|
||||||
# defined in BOLT-03:
|
# defined in BOLT-03:
|
||||||
@@ -263,7 +263,7 @@ class HtlcLog(NamedTuple):
|
|||||||
route: Optional['LNPaymentRoute'] = None
|
route: Optional['LNPaymentRoute'] = None
|
||||||
preimage: Optional[bytes] = None
|
preimage: Optional[bytes] = None
|
||||||
error_bytes: Optional[bytes] = None
|
error_bytes: Optional[bytes] = None
|
||||||
failure_msg: Optional['OnionRoutingFailureMessage'] = None
|
failure_msg: Optional['OnionRoutingFailure'] = None
|
||||||
sender_idx: Optional[int] = None
|
sender_idx: Optional[int] = None
|
||||||
|
|
||||||
def formatted_tuple(self):
|
def formatted_tuple(self):
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ from .lnutil import (Outpoint, LNPeerAddr,
|
|||||||
from .lnutil import ln_dummy_address, ln_compare_features, IncompatibleLightningFeatures
|
from .lnutil import ln_dummy_address, ln_compare_features, IncompatibleLightningFeatures
|
||||||
from .lnrouter import TrampolineEdge
|
from .lnrouter import TrampolineEdge
|
||||||
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
|
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
|
||||||
from .lnonion import OnionFailureCode, process_onion_packet, OnionPacket, OnionRoutingFailure
|
from .lnonion import OnionFailureCode, OnionRoutingFailure
|
||||||
from .lnmsg import decode_msg
|
from .lnmsg import decode_msg
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .lnrouter import (RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_sane_to_use,
|
from .lnrouter import (RouteEdge, LNPaymentRoute, LNPaymentPath, is_route_sane_to_use,
|
||||||
@@ -1115,7 +1115,7 @@ class LNWallet(LNWorker):
|
|||||||
full_path=full_path)
|
full_path=full_path)
|
||||||
success = True
|
success = True
|
||||||
except PaymentFailure as e:
|
except PaymentFailure as e:
|
||||||
self.logger.exception('')
|
self.logger.info(f'payment failure: {e!r}')
|
||||||
success = False
|
success = False
|
||||||
reason = str(e)
|
reason = str(e)
|
||||||
if success:
|
if success:
|
||||||
@@ -1207,7 +1207,8 @@ class LNWallet(LNWorker):
|
|||||||
sender_idx = htlc_log.sender_idx
|
sender_idx = htlc_log.sender_idx
|
||||||
failure_msg = htlc_log.failure_msg
|
failure_msg = htlc_log.failure_msg
|
||||||
code, data = failure_msg.code, failure_msg.data
|
code, data = failure_msg.code, failure_msg.data
|
||||||
self.logger.info(f"UPDATE_FAIL_HTLC {repr(code)} {data}")
|
self.logger.info(f"UPDATE_FAIL_HTLC. code={repr(code)}. "
|
||||||
|
f"decoded_data={failure_msg.decode_data()}. data={data.hex()}")
|
||||||
self.logger.info(f"error reported by {bh2u(route[sender_idx].node_id)}")
|
self.logger.info(f"error reported by {bh2u(route[sender_idx].node_id)}")
|
||||||
if code == OnionFailureCode.MPP_TIMEOUT:
|
if code == OnionFailureCode.MPP_TIMEOUT:
|
||||||
raise PaymentFailure(failure_msg.code_name())
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
@@ -1222,7 +1223,8 @@ class LNWallet(LNWorker):
|
|||||||
else:
|
else:
|
||||||
raise PaymentFailure(failure_msg.code_name())
|
raise PaymentFailure(failure_msg.code_name())
|
||||||
else:
|
else:
|
||||||
self.handle_error_code_from_failed_htlc(route, sender_idx, failure_msg, code, data)
|
self.handle_error_code_from_failed_htlc(
|
||||||
|
route=route, sender_idx=sender_idx, failure_msg=failure_msg)
|
||||||
|
|
||||||
async def pay_to_route(
|
async def pay_to_route(
|
||||||
self, *,
|
self, *,
|
||||||
@@ -1263,8 +1265,13 @@ class LNWallet(LNWorker):
|
|||||||
self.sent_buckets[payment_secret] = amount_sent, amount_failed
|
self.sent_buckets[payment_secret] = amount_sent, amount_failed
|
||||||
util.trigger_callback('htlc_added', chan, htlc, SENT)
|
util.trigger_callback('htlc_added', chan, htlc, SENT)
|
||||||
|
|
||||||
|
def handle_error_code_from_failed_htlc(
|
||||||
def handle_error_code_from_failed_htlc(self, route, sender_idx, failure_msg, code, data):
|
self,
|
||||||
|
*,
|
||||||
|
route: LNPaymentRoute,
|
||||||
|
sender_idx: int,
|
||||||
|
failure_msg: OnionRoutingFailure) -> None:
|
||||||
|
code, data = failure_msg.code, failure_msg.data
|
||||||
# handle some specific error codes
|
# handle some specific error codes
|
||||||
failure_codes = {
|
failure_codes = {
|
||||||
OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 0,
|
OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 0,
|
||||||
@@ -1299,7 +1306,7 @@ class LNWallet(LNWorker):
|
|||||||
try:
|
try:
|
||||||
short_chan_id = route[sender_idx + 1].short_channel_id
|
short_chan_id = route[sender_idx + 1].short_channel_id
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise PaymentFailure('payment destination reported error')
|
raise PaymentFailure(f'payment destination reported error: {failure_msg.code_name()}') from None
|
||||||
# TODO: for MPP we need to save the amount for which
|
# TODO: for MPP we need to save the amount for which
|
||||||
# we saw temporary channel failure
|
# we saw temporary channel failure
|
||||||
self.logger.info(f'blacklisting channel {short_chan_id}')
|
self.logger.info(f'blacklisting channel {short_chan_id}')
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import io
|
|||||||
from electrum.lnmsg import (read_bigsize_int, write_bigsize_int, FieldEncodingNotMinimal,
|
from electrum.lnmsg import (read_bigsize_int, write_bigsize_int, FieldEncodingNotMinimal,
|
||||||
UnexpectedEndOfStream, LNSerializer, UnknownMandatoryTLVRecordType,
|
UnexpectedEndOfStream, LNSerializer, UnknownMandatoryTLVRecordType,
|
||||||
MalformedMsg, MsgTrailingGarbage, MsgInvalidFieldOrder, encode_msg,
|
MalformedMsg, MsgTrailingGarbage, MsgInvalidFieldOrder, encode_msg,
|
||||||
decode_msg, UnexpectedFieldSizeForEncoder)
|
decode_msg, UnexpectedFieldSizeForEncoder, OnionWireSerializer,
|
||||||
|
UnknownMsgType)
|
||||||
|
from electrum.lnonion import OnionRoutingFailure
|
||||||
from electrum.util import bfh
|
from electrum.util import bfh
|
||||||
from electrum.lnutil import ShortChannelID, LnFeatures
|
from electrum.lnutil import ShortChannelID, LnFeatures
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
@@ -383,3 +385,16 @@ class TestLNMsg(TestCaseForTestnet):
|
|||||||
{'chains': b'CI\x7f\xd7\xf8&\x95q\x08\xf4\xa3\x0f\xd9\xce\xc3\xae\xbay\x97 \x84\xe9\x0e\xad\x01\xea3\t\x00\x00\x00\x00'}
|
{'chains': b'CI\x7f\xd7\xf8&\x95q\x08\xf4\xa3\x0f\xd9\xce\xc3\xae\xbay\x97 \x84\xe9\x0e\xad\x01\xea3\t\x00\x00\x00\x00'}
|
||||||
}}),
|
}}),
|
||||||
decode_msg(bfh("001000022200000302aaa2012043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000")))
|
decode_msg(bfh("001000022200000302aaa2012043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000")))
|
||||||
|
|
||||||
|
def test_decode_onion_error(self):
|
||||||
|
orf = OnionRoutingFailure.from_bytes(bfh("400f0000000017d2d8b0001d9458"))
|
||||||
|
self.assertEqual(('incorrect_or_unknown_payment_details', {'htlc_msat': 399694000, 'height': 1938520}),
|
||||||
|
OnionWireSerializer.decode_msg(orf.to_bytes()))
|
||||||
|
self.assertEqual({'htlc_msat': 399694000, 'height': 1938520},
|
||||||
|
orf.decode_data())
|
||||||
|
|
||||||
|
orf2 = OnionRoutingFailure(26399, bytes.fromhex("0000000017d2d8b0001d9458"))
|
||||||
|
with self.assertRaises(UnknownMsgType):
|
||||||
|
OnionWireSerializer.decode_msg(orf2.to_bytes())
|
||||||
|
self.assertEqual(None, orf2.decode_data())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user