|
|
@ -30,7 +30,8 @@ from .transaction import Transaction, TxOutput, PartialTxOutput, match_script_ag |
|
|
|
from .logging import Logger |
|
|
|
from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment, |
|
|
|
process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage, |
|
|
|
ProcessedOnionPacket) |
|
|
|
ProcessedOnionPacket, UnsupportedOnionPacketVersion, InvalidOnionMac, InvalidOnionPubkey, |
|
|
|
OnionFailureCodeMetaFlag) |
|
|
|
from .lnchannel import Channel, RevokeAndAck, htlcsum, RemoteCtnTooFarInFuture, channel_states, peer_states |
|
|
|
from . import lnutil |
|
|
|
from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, |
|
|
@ -229,7 +230,7 @@ class Peer(Logger): |
|
|
|
chan.set_remote_update(payload['raw']) |
|
|
|
self.logger.info("saved remote_update") |
|
|
|
|
|
|
|
def on_announcement_signatures(self, chan, payload): |
|
|
|
def on_announcement_signatures(self, chan: Channel, payload): |
|
|
|
if chan.config[LOCAL].was_announced: |
|
|
|
h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) |
|
|
|
else: |
|
|
@ -900,7 +901,7 @@ class Peer(Logger): |
|
|
|
if chan.config[LOCAL].funding_locked_received and chan.short_channel_id: |
|
|
|
self.mark_open(chan) |
|
|
|
|
|
|
|
def on_funding_locked(self, chan, payload): |
|
|
|
def on_funding_locked(self, chan: Channel, payload): |
|
|
|
self.logger.info(f"on_funding_locked. channel: {bh2u(chan.channel_id)}") |
|
|
|
if not chan.config[LOCAL].funding_locked_received: |
|
|
|
their_next_point = payload["next_per_commitment_point"] |
|
|
@ -926,7 +927,7 @@ class Peer(Logger): |
|
|
|
asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) |
|
|
|
|
|
|
|
@log_exceptions |
|
|
|
async def handle_announcements(self, chan): |
|
|
|
async def handle_announcements(self, chan: Channel): |
|
|
|
h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan) |
|
|
|
announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get() |
|
|
|
remote_node_sig = announcement_signatures_msg["node_signature"] |
|
|
@ -1002,11 +1003,11 @@ class Peer(Logger): |
|
|
|
) |
|
|
|
return msg_hash, node_signature, bitcoin_signature |
|
|
|
|
|
|
|
def on_update_fail_htlc(self, chan, payload): |
|
|
|
def on_update_fail_htlc(self, chan: Channel, payload): |
|
|
|
htlc_id = int.from_bytes(payload["id"], "big") |
|
|
|
reason = payload["reason"] |
|
|
|
self.logger.info(f"on_update_fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") |
|
|
|
chan.receive_fail_htlc(htlc_id, reason) |
|
|
|
chan.receive_fail_htlc(htlc_id, reason) # TODO handle exc and maybe fail channel (e.g. bad htlc_id) |
|
|
|
self.maybe_send_commitment(chan) |
|
|
|
|
|
|
|
def maybe_send_commitment(self, chan: Channel): |
|
|
@ -1057,7 +1058,7 @@ class Peer(Logger): |
|
|
|
next_per_commitment_point=rev.next_per_commitment_point) |
|
|
|
self.maybe_send_commitment(chan) |
|
|
|
|
|
|
|
def on_commitment_signed(self, chan, payload): |
|
|
|
def on_commitment_signed(self, chan: Channel, payload): |
|
|
|
if chan.peer_state == peer_states.BAD: |
|
|
|
return |
|
|
|
self.logger.info(f'on_commitment_signed. chan {chan.short_channel_id}. ctn: {chan.get_next_ctn(LOCAL)}.') |
|
|
@ -1075,19 +1076,29 @@ class Peer(Logger): |
|
|
|
chan.receive_new_commitment(payload["signature"], htlc_sigs) |
|
|
|
self.send_revoke_and_ack(chan) |
|
|
|
|
|
|
|
def on_update_fulfill_htlc(self, chan, payload): |
|
|
|
def on_update_fulfill_htlc(self, chan: Channel, payload): |
|
|
|
preimage = payload["payment_preimage"] |
|
|
|
payment_hash = sha256(preimage) |
|
|
|
htlc_id = int.from_bytes(payload["id"], "big") |
|
|
|
self.logger.info(f"on_update_fulfill_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") |
|
|
|
chan.receive_htlc_settle(preimage, htlc_id) |
|
|
|
chan.receive_htlc_settle(preimage, htlc_id) # TODO handle exc and maybe fail channel (e.g. bad htlc_id) |
|
|
|
self.lnworker.save_preimage(payment_hash, preimage) |
|
|
|
self.maybe_send_commitment(chan) |
|
|
|
|
|
|
|
def on_update_fail_malformed_htlc(self, chan, payload): |
|
|
|
self.logger.info(f"on_update_fail_malformed_htlc. error {payload['data'].decode('ascii')}") |
|
|
|
def on_update_fail_malformed_htlc(self, chan: Channel, payload): |
|
|
|
htlc_id = payload["id"] |
|
|
|
failure_code = payload["failure_code"] |
|
|
|
self.logger.info(f"on_update_fail_malformed_htlc. chan {chan.get_id_for_log()}. " |
|
|
|
f"htlc_id {htlc_id}. failure_code={failure_code}") |
|
|
|
if failure_code & OnionFailureCodeMetaFlag.BADONION == 0: |
|
|
|
asyncio.ensure_future(self.lnworker.try_force_closing(chan.channel_id)) |
|
|
|
raise RemoteMisbehaving(f"received update_fail_malformed_htlc with unexpected failure code: {failure_code}") |
|
|
|
reason = b'' # TODO somehow propagate "failure_code" ? |
|
|
|
chan.receive_fail_htlc(htlc_id, reason) # TODO handle exc and maybe fail channel (e.g. bad htlc_id) |
|
|
|
self.maybe_send_commitment(chan) |
|
|
|
# TODO when forwarding, we need to propagate this "update_fail_malformed_htlc" downstream |
|
|
|
|
|
|
|
def on_update_add_htlc(self, chan, payload): |
|
|
|
def on_update_add_htlc(self, chan: Channel, payload): |
|
|
|
payment_hash = payload["payment_hash"] |
|
|
|
htlc_id = int.from_bytes(payload["id"], 'big') |
|
|
|
self.logger.info(f"on_update_add_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}") |
|
|
@ -1211,19 +1222,27 @@ class Peer(Logger): |
|
|
|
id=htlc_id, |
|
|
|
payment_preimage=preimage) |
|
|
|
|
|
|
|
def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket, |
|
|
|
def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: Optional[OnionPacket], |
|
|
|
reason: OnionRoutingFailureMessage): |
|
|
|
self.logger.info(f"fail_htlc. chan {chan.short_channel_id}. htlc_id {htlc_id}. reason: {reason}") |
|
|
|
assert chan.can_send_ctx_updates(), f"cannot send updates: {chan.short_channel_id}" |
|
|
|
chan.fail_htlc(htlc_id) |
|
|
|
error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey) |
|
|
|
self.send_message("update_fail_htlc", |
|
|
|
channel_id=chan.channel_id, |
|
|
|
id=htlc_id, |
|
|
|
len=len(error_packet), |
|
|
|
reason=error_packet) |
|
|
|
|
|
|
|
def on_revoke_and_ack(self, chan, payload): |
|
|
|
if onion_packet: |
|
|
|
error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey) |
|
|
|
self.send_message("update_fail_htlc", |
|
|
|
channel_id=chan.channel_id, |
|
|
|
id=htlc_id, |
|
|
|
len=len(error_packet), |
|
|
|
reason=error_packet) |
|
|
|
else: |
|
|
|
assert len(reason.data) == 32, f"unexpected reason when sending 'update_fail_malformed_htlc': {reason!r}" |
|
|
|
self.send_message("update_fail_malformed_htlc", |
|
|
|
channel_id=chan.channel_id, |
|
|
|
id=htlc_id, |
|
|
|
sha256_of_onion=reason.data, |
|
|
|
failure_code=reason.code) |
|
|
|
|
|
|
|
def on_revoke_and_ack(self, chan: Channel, payload): |
|
|
|
if chan.peer_state == peer_states.BAD: |
|
|
|
return |
|
|
|
self.logger.info(f'on_revoke_and_ack. chan {chan.short_channel_id}. ctn: {chan.get_oldest_unrevoked_ctn(REMOTE)}') |
|
|
@ -1232,7 +1251,7 @@ class Peer(Logger): |
|
|
|
self.lnworker.save_channel(chan) |
|
|
|
self.maybe_send_commitment(chan) |
|
|
|
|
|
|
|
def on_update_fee(self, chan, payload): |
|
|
|
def on_update_fee(self, chan: Channel, payload): |
|
|
|
feerate = int.from_bytes(payload["feerate_per_kw"], "big") |
|
|
|
chan.update_fee(feerate, False) |
|
|
|
|
|
|
@ -1282,7 +1301,7 @@ class Peer(Logger): |
|
|
|
return txid |
|
|
|
|
|
|
|
@log_exceptions |
|
|
|
async def on_shutdown(self, chan, payload): |
|
|
|
async def on_shutdown(self, chan: Channel, payload): |
|
|
|
their_scriptpubkey = payload['scriptpubkey'] |
|
|
|
# BOLT-02 restrict the scriptpubkey to some templates: |
|
|
|
if not (match_script_against_template(their_scriptpubkey, transaction.SCRIPTPUBKEY_TEMPLATE_WITNESS_V0) |
|
|
@ -1404,33 +1423,44 @@ class Peer(Logger): |
|
|
|
if chan.get_oldest_unrevoked_ctn(REMOTE) <= remote_ctn: |
|
|
|
continue |
|
|
|
chan.logger.info(f'found unfulfilled htlc: {htlc_id}') |
|
|
|
onion_packet = OnionPacket.from_bytes(bytes.fromhex(onion_packet_hex)) |
|
|
|
htlc = chan.hm.log[REMOTE]['adds'][htlc_id] |
|
|
|
payment_hash = htlc.payment_hash |
|
|
|
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) |
|
|
|
preimage, error = None, None |
|
|
|
if processed_onion.are_we_final: |
|
|
|
preimage, error = self.maybe_fulfill_htlc( |
|
|
|
chan=chan, |
|
|
|
htlc=htlc, |
|
|
|
onion_packet=onion_packet, |
|
|
|
processed_onion=processed_onion) |
|
|
|
elif not forwarded: |
|
|
|
error = self.maybe_forward_htlc( |
|
|
|
chan=chan, |
|
|
|
htlc=htlc, |
|
|
|
onion_packet=onion_packet, |
|
|
|
processed_onion=processed_onion) |
|
|
|
if not error: |
|
|
|
unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, True |
|
|
|
error = None # type: Optional[OnionRoutingFailureMessage] |
|
|
|
preimage = None |
|
|
|
onion_packet_bytes = bytes.fromhex(onion_packet_hex) |
|
|
|
onion_packet = None |
|
|
|
try: |
|
|
|
onion_packet = OnionPacket.from_bytes(onion_packet_bytes) |
|
|
|
processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey) |
|
|
|
except UnsupportedOnionPacketVersion: |
|
|
|
error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_VERSION, data=sha256(onion_packet_bytes)) |
|
|
|
except InvalidOnionPubkey: |
|
|
|
error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_KEY, data=sha256(onion_packet_bytes)) |
|
|
|
except InvalidOnionMac: |
|
|
|
error = OnionRoutingFailureMessage(code=OnionFailureCode.INVALID_ONION_HMAC, data=sha256(onion_packet_bytes)) |
|
|
|
else: |
|
|
|
f = self.lnworker.pending_payments[payment_hash] |
|
|
|
if f.done(): |
|
|
|
success, preimage, error = f.result() |
|
|
|
if preimage: |
|
|
|
await self.lnworker.enable_htlc_settle.wait() |
|
|
|
self.fulfill_htlc(chan, htlc.htlc_id, preimage) |
|
|
|
done.add(htlc_id) |
|
|
|
if processed_onion.are_we_final: |
|
|
|
preimage, error = self.maybe_fulfill_htlc( |
|
|
|
chan=chan, |
|
|
|
htlc=htlc, |
|
|
|
onion_packet=onion_packet, |
|
|
|
processed_onion=processed_onion) |
|
|
|
elif not forwarded: |
|
|
|
error = self.maybe_forward_htlc( |
|
|
|
chan=chan, |
|
|
|
htlc=htlc, |
|
|
|
onion_packet=onion_packet, |
|
|
|
processed_onion=processed_onion) |
|
|
|
if not error: |
|
|
|
unfulfilled[htlc_id] = local_ctn, remote_ctn, onion_packet_hex, True |
|
|
|
else: |
|
|
|
f = self.lnworker.pending_payments[payment_hash] |
|
|
|
if f.done(): |
|
|
|
success, preimage, error = f.result() |
|
|
|
if preimage: |
|
|
|
await self.lnworker.enable_htlc_settle.wait() |
|
|
|
self.fulfill_htlc(chan, htlc.htlc_id, preimage) |
|
|
|
done.add(htlc_id) |
|
|
|
if error: |
|
|
|
self.fail_htlc(chan, htlc.htlc_id, onion_packet, error) |
|
|
|
done.add(htlc_id) |
|
|
|