@ -25,7 +25,7 @@ from . import transaction
from . transaction import PartialTxOutput , match_script_against_template
from . logging import Logger
from . lnonion import ( new_onion_packet , OnionFailureCode , calc_hops_data_for_payment ,
process_onion_packet , OnionPacket , construct_onion_error , OnionRoutingFailureMessage ,
process_onion_packet , OnionPacket , construct_onion_error , OnionRoutingFailure ,
ProcessedOnionPacket , UnsupportedOnionPacketVersion , InvalidOnionMac , InvalidOnionPubkey ,
OnionFailureCodeMetaFlag )
from . lnchannel import Channel , RevokeAndAck , RemoteCtnTooFarInFuture , ChannelState , PeerState
@ -1267,7 +1267,7 @@ class Peer(Logger):
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 = OnionRoutingFailureMessage ( code = failure_code , data = payload [ " sha256_of_onion " ] )
reason = OnionRoutingFailure ( code = failure_code , data = payload [ " sha256_of_onion " ] )
chan . receive_fail_htlc ( htlc_id , error_bytes = None , reason = reason )
self . maybe_send_commitment ( chan )
@ -1295,56 +1295,56 @@ class Peer(Logger):
def maybe_forward_htlc ( self , chan : Channel , htlc : UpdateAddHtlc , * ,
onion_packet : OnionPacket , processed_onion : ProcessedOnionPacket
) - > Tuple [ Optional [ bytes ] , Optional [ int ] , Optional [ OnionRoutingFailureMessage ] ] :
) - > Tuple [ Optional [ bytes ] , Optional [ int ] , Optional [ OnionRoutingFailure ] ] :
# Forward HTLC
# FIXME: there are critical safety checks MISSING here
forwarding_enabled = self . network . config . get ( ' lightning_forward_payments ' , False )
if not forwarding_enabled :
self . logger . info ( f " forwarding is disabled. failing htlc. " )
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . PERMANENT_CHANNEL_FAILURE , data = b ' ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . PERMANENT_CHANNEL_FAILURE , data = b ' ' )
chain = self . network . blockchain ( )
if chain . is_tip_stale ( ) :
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
try :
next_chan_scid = processed_onion . hop_data . payload [ " short_channel_id " ] [ " short_channel_id " ]
except :
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
next_chan = self . lnworker . get_channel_by_short_id ( next_chan_scid )
local_height = chain . height ( )
if next_chan is None :
self . logger . info ( f " cannot forward htlc. cannot find next_chan { next_chan_scid } " )
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . UNKNOWN_NEXT_PEER , data = b ' ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . UNKNOWN_NEXT_PEER , data = b ' ' )
outgoing_chan_upd = next_chan . get_outgoing_gossip_channel_update ( ) [ 2 : ]
outgoing_chan_upd_len = len ( outgoing_chan_upd ) . to_bytes ( 2 , byteorder = " big " )
if not next_chan . can_send_update_add_htlc ( ) :
self . logger . info ( f " cannot forward htlc. next_chan { next_chan_scid } cannot send ctx updates. "
f " chan state { next_chan . get_state ( ) !r} , peer state: { next_chan . peer_state !r} " )
data = outgoing_chan_upd_len + outgoing_chan_upd
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . TEMPORARY_CHANNEL_FAILURE , data = data )
raise OnionRoutingFailur e ( code = OnionFailureCode . TEMPORARY_CHANNEL_FAILURE , data = data )
try :
next_cltv_expiry = processed_onion . hop_data . payload [ " outgoing_cltv_value " ] [ " outgoing_cltv_value " ]
except :
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
if htlc . cltv_expiry - next_cltv_expiry < NBLOCK_OUR_CLTV_EXPIRY_DELTA :
data = htlc . cltv_expiry . to_bytes ( 4 , byteorder = " big " ) + outgoing_chan_upd_len + outgoing_chan_upd
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . INCORRECT_CLTV_EXPIRY , data = data )
raise OnionRoutingFailur e ( code = OnionFailureCode . INCORRECT_CLTV_EXPIRY , data = data )
if htlc . cltv_expiry - lnutil . MIN_FINAL_CLTV_EXPIRY_ACCEPTED < = local_height \
or next_cltv_expiry < = local_height :
data = outgoing_chan_upd_len + outgoing_chan_upd
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . EXPIRY_TOO_SOON , data = data )
raise OnionRoutingFailur e ( code = OnionFailureCode . EXPIRY_TOO_SOON , data = data )
if max ( htlc . cltv_expiry , next_cltv_expiry ) > local_height + lnutil . NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE :
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . EXPIRY_TOO_FAR , data = b ' ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . EXPIRY_TOO_FAR , data = b ' ' )
try :
next_amount_msat_htlc = processed_onion . hop_data . payload [ " amt_to_forward " ] [ " amt_to_forward " ]
except :
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
raise OnionRoutingFailur e ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
forwarding_fees = fee_for_edge_msat (
forwarded_amount_msat = next_amount_msat_htlc ,
fee_base_msat = lnutil . OUR_FEE_BASE_MSAT ,
fee_proportional_millionths = lnutil . OUR_FEE_PROPORTIONAL_MILLIONTHS )
if htlc . amount_msat - next_amount_msat_htlc < forwarding_fees :
data = next_amount_msat_htlc . to_bytes ( 8 , byteorder = " big " ) + outgoing_chan_upd_len + outgoing_chan_upd
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . FEE_INSUFFICIENT , data = data )
raise OnionRoutingFailur e ( code = OnionFailureCode . FEE_INSUFFICIENT , data = data )
self . logger . info ( f ' forwarding htlc to { next_chan . node_id } ' )
next_htlc = UpdateAddHtlc (
amount_msat = next_amount_msat_htlc ,
@ -1366,19 +1366,18 @@ class Peer(Logger):
except BaseException as e :
self . logger . info ( f " failed to forward htlc: error sending message. { e } " )
data = outgoing_chan_upd_len + outgoing_chan_upd
return None , None , OnionRoutingFailureMessag e ( code = OnionFailureCode . TEMPORARY_CHANNEL_FAILURE , data = data )
return next_chan_scid , next_htlc . htlc_id , None
raise OnionRoutingFailur e ( code = OnionFailureCode . TEMPORARY_CHANNEL_FAILURE , data = data )
return next_chan_scid , next_htlc . htlc_id
def maybe_fulfill_htlc (
self , * ,
chan : Channel ,
htlc : UpdateAddHtlc ,
processed_onion : ProcessedOnionPacket ) - > Tuple [ Optional [ bytes ] , Optional [ OnionRoutingFailureMessage ] ] :
processed_onion : ProcessedOnionPacket ) - > Tuple [ Optional [ bytes ] , Optional [ OnionRoutingFailure ] ] :
info = self . lnworker . get_payment_info ( htlc . payment_hash )
if info is None :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
preimage = self . lnworker . get_preimage ( htlc . payment_hash )
try :
payment_secret_from_onion = processed_onion . hop_data . payload [ " payment_data " ] [ " payment_secret " ]
@ -1386,59 +1385,49 @@ class Peer(Logger):
pass # skip
else :
if payment_secret_from_onion != derive_payment_secret_from_payment_preimage ( preimage ) :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
expected_received_msat = info . amount_msat
# Check that our blockchain tip is sufficiently recent so that we have an approx idea of the height.
# We should not release the preimage for an HTLC that its sender could already time out as
# then they might try to force-close and it becomes a race.
chain = self . network . blockchain ( )
if chain . is_tip_stale ( ) :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
local_height = chain . height ( )
if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > htlc . cltv_expiry :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . FINAL_EXPIRY_TOO_SOON , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . FINAL_EXPIRY_TOO_SOON , data = b ' ' )
try :
cltv_from_onion = processed_onion . hop_data . payload [ " outgoing_cltv_value " ] [ " outgoing_cltv_value " ]
except :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
if cltv_from_onion != htlc . cltv_expiry :
reason = OnionRoutingFailureMessag e(
raise OnionRoutingFailur e(
code = OnionFailureCode . FINAL_INCORRECT_CLTV_EXPIRY ,
data = htlc . cltv_expiry . to_bytes ( 4 , byteorder = " big " ) )
return None , reason
try :
amt_to_forward = processed_onion . hop_data . payload [ " amt_to_forward " ] [ " amt_to_forward " ]
except :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_PAYLOAD , data = b ' \x00 \x00 \x00 ' )
try :
total_msat = processed_onion . hop_data . payload [ " payment_data " ] [ " total_msat " ]
except :
total_msat = amt_to_forward # fall back to "amt_to_forward"
if amt_to_forward != htlc . amount_msat :
reason = OnionRoutingFailureMessag e(
raise OnionRoutingFailur e(
code = OnionFailureCode . FINAL_INCORRECT_HTLC_AMOUNT ,
data = total_msat . to_bytes ( 8 , byteorder = " big " ) )
return None , reason
if expected_received_msat is None :
return preimage , None
return preimage
if not ( expected_received_msat < = total_msat < = 2 * expected_received_msat ) :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b ' ' )
accepted , expired = self . lnworker . htlc_received ( chan . short_channel_id , htlc , expected_received_msat )
if accepted :
return preimage , None
return preimage
elif expired :
reason = OnionRoutingFailureMessage ( code = OnionFailureCode . MPP_TIMEOUT , data = b ' ' )
return None , reason
raise OnionRoutingFailure ( code = OnionFailureCode . MPP_TIMEOUT , data = b ' ' )
else :
# waiting for more htlcs
return None , None
return None
def fulfill_htlc ( self , chan : Channel , htlc_id : int , preimage : bytes ) :
self . logger . info ( f " _fulfill_htlc. chan { chan . short_channel_id } . htlc_id { htlc_id } " )
@ -1461,7 +1450,7 @@ class Peer(Logger):
len = len ( error_bytes ) ,
reason = error_bytes )
def fail_malformed_htlc ( self , * , chan : Channel , htlc_id : int , reason : OnionRoutingFailureMessage ) :
def fail_malformed_htlc ( self , * , chan : Channel , htlc_id : int , reason : OnionRoutingFailure ) :
self . logger . info ( f " fail_malformed_htlc. chan { chan . short_channel_id } . htlc_id { htlc_id } . " )
assert chan . can_send_ctx_updates ( ) , f " cannot send updates: { chan . short_channel_id } "
chan . fail_htlc ( htlc_id )
@ -1679,60 +1668,29 @@ class Peer(Logger):
for htlc_id , ( local_ctn , remote_ctn , onion_packet_hex , forwarding_info ) in unfulfilled . items ( ) :
if not chan . hm . is_add_htlc_irrevocably_committed_yet ( htlc_proposer = REMOTE , htlc_id = htlc_id ) :
continue
#chan.logger.info(f'found unfulfilled htlc: {htlc_id}')
htlc = chan . hm . get_htlc_by_id ( REMOTE , htlc_id )
payment_hash = htlc . payment_hash
error_reason = None # type: Optional[OnionRoutingFailureMessage]
error_reason = None # type: Optional[OnionRoutingFailure]
error_bytes = None # type: Optional[bytes]
preimage = None
fw_info = 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_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_VERSION , data = sha256 ( onion_packet_bytes ) )
except InvalidOnionPubkey :
error_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_KEY , data = sha256 ( onion_packet_bytes ) )
except InvalidOnionMac :
error_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_HMAC , data = sha256 ( onion_packet_bytes ) )
except Exception as e :
self . logger . info ( f " error processing onion packet: { e !r} " )
error_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_VERSION , data = sha256 ( onion_packet_bytes ) )
except OnionRoutingFailure as e :
error_reason = e
else :
if self . network . config . get ( ' test_fail_malformed_htlc ' ) :
error_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . INVALID_ONION_VERSION , data = sha256 ( onion_packet_bytes ) )
if self . network . config . get ( ' test_fail_htlcs_with_temp_node_failure ' ) :
error_reason = OnionRoutingFailureMessage ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
if not error_reason :
if processed_onion . are_we_final :
preimage , error_reason = self . maybe_fulfill_htlc (
chan = chan ,
htlc = htlc ,
processed_onion = processed_onion )
elif not forwarding_info :
next_chan_id , next_htlc_id , error_reason = self . maybe_forward_htlc (
chan = chan ,
htlc = htlc ,
onion_packet = onion_packet ,
processed_onion = processed_onion )
if next_chan_id :
fw_info = ( next_chan_id . hex ( ) , next_htlc_id )
unfulfilled [ htlc_id ] = local_ctn , remote_ctn , onion_packet_hex , fw_info
else :
preimage = self . lnworker . get_preimage ( payment_hash )
next_chan_id_hex , htlc_id = forwarding_info
next_chan = self . lnworker . get_channel_by_short_id ( bytes . fromhex ( next_chan_id_hex ) )
if next_chan :
error_bytes , error_reason = next_chan . pop_fail_htlc_reason ( htlc_id )
try :
preimage , fw_info , error_bytes = self . process_unfulfilled_htlc ( chan , htlc_id , htlc , forwarding_info , onion_packet_bytes , onion_packet )
except OnionRoutingFailure as e :
error_bytes = construct_onion_error ( e , onion_packet , our_onion_private_key = self . privkey )
if fw_info :
unfulfilled [ htlc_id ] = local_ctn , remote_ctn , onion_packet_hex , fw_info
elif preimage or error_reason or error_bytes :
if preimage :
await self . lnworker . enable_htlc_settle . wait ( )
self . fulfill_htlc ( chan , htlc . htlc_id , preimage )
done . add ( htlc_id )
if error_reason or error_bytes :
if onion_packet and error_reason :
error_bytes = construct_onion_error ( error_reason , onion_packet , our_onion_private_key = self . privkey )
if error_bytes :
elif error_bytes :
self . fail_htlc (
chan = chan ,
htlc_id = htlc . htlc_id ,
@ -1746,3 +1704,57 @@ class Peer(Logger):
# cleanup
for htlc_id in done :
unfulfilled . pop ( htlc_id )
def process_unfulfilled_htlc ( self , chan , htlc_id , htlc , forwarding_info , onion_packet_bytes , onion_packet ) :
"""
returns either preimage or fw_info or error_bytes or ( None , None , None )
raise an OnionRoutingFailure if we need to fail the htlc
"""
payment_hash = htlc . payment_hash
processed_onion = self . process_onion_packet ( onion_packet , payment_hash , onion_packet_bytes )
if processed_onion . are_we_final :
preimage = self . maybe_fulfill_htlc (
chan = chan ,
htlc = htlc ,
processed_onion = processed_onion )
elif not forwarding_info :
next_chan_id , next_htlc_id = self . maybe_forward_htlc (
chan = chan ,
htlc = htlc ,
onion_packet = onion_packet ,
processed_onion = processed_onion )
if next_chan_id :
fw_info = ( next_chan_id . hex ( ) , next_htlc_id )
return None , fw_info , None
else :
preimage = self . lnworker . get_preimage ( payment_hash )
next_chan_id_hex , htlc_id = forwarding_info
next_chan = self . lnworker . get_channel_by_short_id ( bytes . fromhex ( next_chan_id_hex ) )
if next_chan :
error_bytes , error_reason = next_chan . pop_fail_htlc_reason ( htlc_id )
if error_bytes :
return None , None , error_bytes
if error_reason :
raise error_reason
if preimage :
return preimage , None , None
return None , None , None
def process_onion_packet ( self , onion_packet , payment_hash , onion_packet_bytes ) :
failure_data = sha256 ( onion_packet_bytes )
try :
processed_onion = process_onion_packet ( onion_packet , associated_data = payment_hash , our_onion_private_key = self . privkey )
except UnsupportedOnionPacketVersion :
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_VERSION , data = failure_data )
except InvalidOnionPubkey :
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_KEY , data = failure_data )
except InvalidOnionMac :
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_HMAC , data = failure_data )
except Exception as e :
self . logger . info ( f " error processing onion packet: { e !r} " )
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_VERSION , data = failure_data )
if self . network . config . get ( ' test_fail_malformed_htlc ' ) :
raise OnionRoutingFailure ( code = OnionFailureCode . INVALID_ONION_VERSION , data = failure_data )
if self . network . config . get ( ' test_fail_htlcs_with_temp_node_failure ' ) :
raise OnionRoutingFailure ( code = OnionFailureCode . TEMPORARY_NODE_FAILURE , data = b ' ' )
return processed_onion