@ -24,7 +24,7 @@ from aiorpcx import run_in_thread
from . import constants
from . import keystore
from . util import profiler
from . util import PR_UNPAID , PR_EXPIRED , PR_PAID , PR_INFLIGHT , PR_FAILED
from . util import PR_UNPAID , PR_EXPIRED , PR_PAID , PR_INFLIGHT , PR_FAILED , PR_ROUTING
from . util import PR_TYPE_LN
from . lnutil import LN_MAX_FUNDING_SAT
from . keystore import BIP32_KeyStore
@ -69,6 +69,9 @@ if TYPE_CHECKING:
from . wallet import Abstract_Wallet
SAVED_PR_STATUS = [ PR_PAID , PR_UNPAID , PR_INFLIGHT ] # status that are persisted
NUM_PEERS_TARGET = 4
PEER_RETRY_INTERVAL = 600 # seconds
PEER_RETRY_INTERVAL_FOR_CHANNELS = 30 # seconds
@ -421,7 +424,8 @@ class LNWallet(LNWorker):
self . preimages = self . db . get_dict ( ' lightning_preimages ' ) # RHASH -> preimage
self . sweep_address = wallet . get_receiving_address ( )
self . lock = threading . RLock ( )
self . logs = defaultdict ( list ) # type: Dict[str, List[PaymentAttemptLog]] # key is RHASH
self . logs = defaultdict ( list ) # (not persisted) type: Dict[str, List[PaymentAttemptLog]] # key is RHASH
self . is_routing = set ( ) # (not persisted) keys of invoices that are in PR_ROUTING state
# used in tests
self . enable_htlc_settle = asyncio . Event ( )
self . enable_htlc_settle . set ( )
@ -920,25 +924,35 @@ class LNWallet(LNWorker):
self . wallet . set_label ( key , lnaddr . get_description ( ) )
log = self . logs [ key ]
success = False
reason = ' '
for i in range ( attempts ) :
try :
# note: this call does path-finding which takes ~1 second
# -> we will BLOCK the asyncio loop... (could just run in a thread and await,
# but then the graph could change while the path-finding runs on it)
self . set_invoice_status ( key , PR_ROUTING )
self . network . trigger_callback ( ' invoice_status ' , key )
route = self . _create_route_from_invoice ( decoded_invoice = lnaddr )
self . set_payment_status ( payment_hash , PR_INFLIGHT )
self . set_invoice_status ( key , PR_INFLIGHT )
self . network . trigger_callback ( ' invoice_status ' , key )
payment_attempt_log = await self . _pay_to_route ( route , lnaddr )
except Exception as e :
log . append ( PaymentAttemptLog ( success = False , exception = e ) )
self . set_payment_status ( payment_hash , PR_UNPAID )
self . set_invoice_status ( key , PR_UNPAID )
reason = str ( e )
break
log . append ( payment_attempt_log )
success = payment_attempt_log . success
if success :
break
self . logger . debug ( f ' payment attempts log for RHASH { key } : { repr ( log ) } ' )
else :
reason = ' failed after %d attempts ' % attemps
self . network . trigger_callback ( ' invoice_status ' , key )
if success :
self . network . trigger_callback ( ' payment_succeeded ' , key )
else :
self . network . trigger_callback ( ' payment_failed ' , key , reason )
self . logger . debug ( f ' payment attempts log for RHASH { key } : { repr ( log ) } ' )
return success
async def _pay_to_route ( self , route : LNPaymentRoute , lnaddr : LnAddr ) - > PaymentAttemptLog :
@ -1038,6 +1052,7 @@ class LNWallet(LNWorker):
f " min_final_cltv_expiry: { addr . get_min_final_cltv_expiry ( ) } " ) )
return addr
@profiler
def _create_route_from_invoice ( self , decoded_invoice ) - > LNPaymentRoute :
amount_msat = int ( decoded_invoice . amount * COIN * 1000 )
invoice_pubkey = decoded_invoice . pubkey . serialize ( )
@ -1170,7 +1185,7 @@ class LNWallet(LNWorker):
def save_payment_info ( self , info : PaymentInfo ) - > None :
key = info . payment_hash . hex ( )
assert info . status in [ PR_PAID , PR_UNPAID , PR_INFLIGHT ]
assert info . status in SAVED_PR_STATUS
with self . lock :
self . payments [ key ] = info . amount , info . direction , info . status
self . wallet . save_db ( )
@ -1184,19 +1199,29 @@ class LNWallet(LNWorker):
return status
def get_invoice_status ( self , key ) :
log = self . logs [ key ]
if key in self . is_routing :
return PR_ROUTING
# status may be PR_FAILED
status = self . get_payment_status ( bfh ( key ) )
log = self . logs [ key ]
if status == PR_UNPAID and log :
status = PR_FAILED
return status
def set_invoice_status ( self , key , status ) :
if status == PR_ROUTING :
self . is_routing . add ( key )
elif key in self . is_routing :
self . is_routing . remove ( key )
if status in SAVED_PR_STATUS :
self . save_payment_status ( bfh ( key ) , status )
async def await_payment ( self , payment_hash ) :
success , preimage , reason = await self . pending_payments [ payment_hash ]
self . pending_payments . pop ( payment_hash )
return success , preimage , reason
def set_payment_status ( self , payment_hash : bytes , status ) :
def sav e_payment_status ( self , payment_hash : bytes , status ) :
try :
info = self . get_payment_info ( payment_hash )
except UnknownPaymentHash :
@ -1206,27 +1231,29 @@ class LNWallet(LNWorker):
self . save_payment_info ( info )
def payment_failed ( self , chan , payment_hash : bytes , reason ) :
self . set _payment_status ( payment_hash , PR_UNPAID )
self . sav e_payment_status ( payment_hash , PR_UNPAID )
f = self . pending_payments . get ( payment_hash )
if f and not f . cancelled ( ) :
f . set_result ( ( False , None , reason ) )
else :
chan . logger . info ( ' received unexpected payment_failed, probably from previous session ' )
self . network . trigger_callback ( ' invoice_status ' , payment_hash . hex ( ) )
self . network . trigger_callback ( ' invoice_status ' , key )
self . network . trigger_callback ( ' payment_failed ' , payment_hash . hex ( ) )
def payment_sent ( self , chan , payment_hash : bytes ) :
self . set _payment_status ( payment_hash , PR_PAID )
self . sav e_payment_status ( payment_hash , PR_PAID )
preimage = self . get_preimage ( payment_hash )
f = self . pending_payments . get ( payment_hash )
if f and not f . cancelled ( ) :
f . set_result ( ( True , preimage , None ) )
else :
chan . logger . info ( ' received unexpected payment_sent, probably from previous session ' )
self . network . trigger_callback ( ' invoice_status ' , payment_hash . hex ( ) )
self . network . trigger_callback ( ' invoice_status ' , key )
self . network . trigger_callback ( ' payment_succeeded ' , payment_hash . hex ( ) )
self . network . trigger_callback ( ' ln_payment_completed ' , payment_hash , chan . channel_id )
def payment_received ( self , chan , payment_hash : bytes ) :
self . set _payment_status ( payment_hash , PR_PAID )
self . sav e_payment_status ( payment_hash , PR_PAID )
self . network . trigger_callback ( ' request_status ' , payment_hash . hex ( ) , PR_PAID )
self . network . trigger_callback ( ' ln_payment_completed ' , payment_hash , chan . channel_id )