62
62
LnKeyFamily , LOCAL , REMOTE , MIN_FINAL_CLTV_DELTA_FOR_INVOICE , SENT , RECEIVED , HTLCOwner , UpdateAddHtlc , LnFeatures ,
63
63
ShortChannelID , HtlcLog , NoPathFound , InvalidGossipMsg , FeeBudgetExceeded , ImportedChannelBackupStorage ,
64
64
OnchainChannelBackupStorage , ln_compare_features , IncompatibleLightningFeatures , PaymentFeeBudget ,
65
- NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE , GossipForwardingMessage
65
+ NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE , GossipForwardingMessage , ZEROCONF_TIMEOUT , FeeTooLow
66
66
)
67
67
from .lnonion import decode_onion_error , OnionFailureCode , OnionRoutingFailure , OnionPacket
68
68
from .lnmsg import decode_msg
76
76
create_trampoline_route_and_onion , is_legacy_relay , trampolines_by_id , hardcoded_trampoline_nodes ,
77
77
is_hardcoded_trampoline
78
78
)
79
+ from .network import TxBroadcastServerReturnedError
79
80
80
81
if TYPE_CHECKING :
81
82
from .network import Network
@@ -892,6 +893,10 @@ def __init__(self, wallet: 'Abstract_Wallet', xprv):
892
893
self .swap_manager = SwapManager (wallet = self .wallet , lnworker = self )
893
894
self .onion_message_manager = OnionMessageManager (self )
894
895
896
+ # to keep track of the channels we sold as just-in-time provider
897
+ if self .config .ACCEPT_ZEROCONF_CHANNELS :
898
+ self .sold_just_in_time_channels = self .db .get_dict ('sold_just_in_time_channels' ) # type: Dict[str, int] # channel_id -> revenue sat (opening fee - funding tx fee)
899
+
895
900
def has_deterministic_node_id (self ) -> bool :
896
901
return bool (self .db .get ('lightning_xprv' ))
897
902
@@ -1129,7 +1134,7 @@ def get_lightning_history(self) -> Dict[str, LightningHistoryItem]:
1129
1134
balance_msat = sum ([x .amount_msat for x in out .values ()])
1130
1135
lb = sum (chan .balance (LOCAL ) if not chan .is_closed_or_closing () else 0
1131
1136
for chan in self .channels .values ())
1132
- assert balance_msat == lb
1137
+ assert balance_msat == lb , f"balance_msat: { balance_msat } != lb: { lb } "
1133
1138
return out
1134
1139
1135
1140
def get_groups_for_onchain_history (self ) -> Dict [str , str ]:
@@ -1235,7 +1240,6 @@ def _scid_alias_of_node(self, nodeid: bytes) -> bytes:
1235
1240
def get_static_jit_scid_alias (self ) -> bytes :
1236
1241
return self ._scid_alias_of_node (self .node_keypair .pubkey )
1237
1242
1238
- @log_exceptions
1239
1243
async def open_channel_just_in_time (
1240
1244
self ,
1241
1245
* ,
@@ -1244,36 +1248,69 @@ async def open_channel_just_in_time(
1244
1248
next_cltv_abs : int ,
1245
1249
payment_hash : bytes ,
1246
1250
next_onion : OnionPacket ,
1247
- ) -> str :
1251
+ ) -> Optional [str ]:
1252
+ """Wrapper around __open_channel_just_in_time to allow for cleaner htlc locking and preimage deletion"""
1253
+
1254
+ # prevent settling the htlc until the channel opening was successfully so we can fail it if needed
1255
+ self .dont_settle_htlcs [payment_hash .hex ()] = None
1256
+ try :
1257
+ return await self .__open_channel_just_in_time (
1258
+ next_peer = next_peer ,
1259
+ next_amount_msat_htlc = next_amount_msat_htlc ,
1260
+ next_cltv_abs = next_cltv_abs ,
1261
+ payment_hash = payment_hash ,
1262
+ next_onion = next_onion ,
1263
+ )
1264
+ except Exception :
1265
+ self .logger .warning (f"failed to open jit channel" , exc_info = True )
1266
+ # ensure that we not accidentally store a preimage on exception
1267
+ self .preimages .pop (payment_hash .hex (), None )
1268
+ raise
1269
+ finally :
1270
+ del self .dont_settle_htlcs [payment_hash .hex ()]
1271
+
1272
+ @log_exceptions
1273
+ async def __open_channel_just_in_time (
1274
+ self ,
1275
+ * ,
1276
+ next_peer : Peer ,
1277
+ next_amount_msat_htlc : int ,
1278
+ next_cltv_abs : int ,
1279
+ payment_hash : bytes ,
1280
+ next_onion : OnionPacket ,
1281
+ ) -> Optional [str ]:
1248
1282
# if an exception is raised during negotiation, we raise an OnionRoutingFailure.
1249
1283
# this will cancel the incoming HTLC
1284
+ assert self .config .ZEROCONF_RELATIVE_OPENING_FEE_PPM > 0 , self .config .ZEROCONF_MIN_OPENING_FEE_SAT > 0
1250
1285
1251
- # prevent settling the htlc until the channel opening was successfull so we can fail it if needed
1252
- self .dont_settle_htlcs [payment_hash .hex ()] = None
1286
+ next_chan = None
1253
1287
try :
1254
1288
funding_sat = 2 * (next_amount_msat_htlc // 1000 ) # try to fully spend htlcs
1255
1289
password = self .wallet .get_unlocked_password () if self .wallet .has_password () else None
1256
- channel_opening_fee = next_amount_msat_htlc // 100
1257
- if channel_opening_fee // 1000 < self .config .ZEROCONF_MIN_OPENING_FEE :
1258
- self .logger .info (f'rejecting JIT channel: payment too low' )
1290
+ channel_opening_fee_msat = (next_amount_msat_htlc * self .config .ZEROCONF_RELATIVE_OPENING_FEE_PPM ) // 1_000_000
1291
+ if channel_opening_fee_msat // 1000 < self .config .ZEROCONF_MIN_OPENING_FEE_SAT :
1292
+ self .logger .info (f'rejecting JIT channel: payment too low: posible fee '
1293
+ f'{ channel_opening_fee_msat // 1000 } sat < min fee { self .config .ZEROCONF_MIN_OPENING_FEE_SAT } sat' )
1294
+ raise OnionRoutingFailure (code = OnionFailureCode .INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b'payment too low' )
1295
+ self .logger .info (f'jit channel opening fee (sats): { channel_opening_fee_msat // 1000 } ' )
1296
+ try :
1297
+ next_chan , funding_tx = await self .open_channel_with_peer (
1298
+ next_peer , funding_sat ,
1299
+ push_sat = 0 ,
1300
+ zeroconf = True ,
1301
+ public = False ,
1302
+ opening_fee_msat = channel_opening_fee_msat ,
1303
+ password = password ,
1304
+ )
1305
+ except FeeTooLow :
1306
+ self .logger .info (f"rejecting JIT channel: tx fee too high in relation to revenue" , exc_info = True )
1259
1307
raise OnionRoutingFailure (code = OnionFailureCode .INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS , data = b'payment too low' )
1260
- self .logger .info (f'channel opening fee (sats): { channel_opening_fee // 1000 } ' )
1261
- next_chan , funding_tx = await self .open_channel_with_peer (
1262
- next_peer , funding_sat ,
1263
- push_sat = 0 ,
1264
- zeroconf = True ,
1265
- public = False ,
1266
- opening_fee = channel_opening_fee ,
1267
- password = password ,
1268
- )
1269
1308
async def wait_for_channel ():
1270
1309
while not next_chan .is_open ():
1271
- await asyncio .sleep (1 )
1310
+ await asyncio .sleep (0. 1 )
1272
1311
await util .wait_for2 (wait_for_channel (), LN_P2P_NETWORK_TIMEOUT )
1273
- next_chan .save_remote_scid_alias (self ._scid_alias_of_node (next_peer .pubkey ))
1274
- self .logger .info (f'JIT channel is open' )
1275
- next_amount_msat_htlc -= channel_opening_fee
1276
- # fixme: some checks are missing
1312
+ self .logger .info (f'JIT channel is open (funding not broadcasted yet)' )
1313
+ next_amount_msat_htlc -= channel_opening_fee_msat
1277
1314
htlc = next_peer .send_htlc (
1278
1315
chan = next_chan ,
1279
1316
payment_hash = payment_hash ,
@@ -1282,30 +1319,78 @@ async def wait_for_channel():
1282
1319
onion = next_onion )
1283
1320
async def wait_for_preimage ():
1284
1321
while self .get_preimage (payment_hash ) is None :
1285
- await asyncio .sleep (1 )
1322
+ await asyncio .sleep (0. 1 )
1286
1323
await util .wait_for2 (wait_for_preimage (), LN_P2P_NETWORK_TIMEOUT )
1287
-
1288
- # We have been paid and can broadcast
1289
- # todo: if broadcasting raise an exception, we should try to rebroadcast
1290
- await self .network .broadcast_transaction (funding_tx )
1291
1324
except OnionRoutingFailure :
1292
1325
raise
1293
1326
except Exception :
1327
+ if next_chan :
1328
+ # the chan was already established, so it has to get cleaned up again
1329
+ await self .cleanup_failed_jit_channel (next_chan )
1330
+ raise OnionRoutingFailure (code = OnionFailureCode .TEMPORARY_NODE_FAILURE , data = b'' )
1331
+
1332
+ disable_zeroconf = True
1333
+ try :
1334
+ # after 10 mins `update_unfunded_state` will remove the channel on client side so we can fail here too
1335
+ await util .wait_for2 (self .broadcast_jit_channel_and_wait_for_mempool (next_chan , funding_tx ), ZEROCONF_TIMEOUT - 30 )
1336
+ disable_zeroconf = False
1337
+ except Exception :
1338
+ # the risk of the funding tx getting mined later is low as we weren't able to get it into the mempool
1339
+ await self .cleanup_failed_jit_channel (next_chan )
1294
1340
raise OnionRoutingFailure (code = OnionFailureCode .TEMPORARY_NODE_FAILURE , data = b'' )
1295
1341
finally :
1296
- del self .dont_settle_htlcs [payment_hash .hex ()]
1342
+ if disable_zeroconf :
1343
+ self .logger .warning (f"disabling zeroconf channels to prevent further issues. Check your wallet for consistency." )
1344
+ self .config .ACCEPT_ZEROCONF_CHANNELS = False
1345
+ self .features &= ~ LnFeatures .OPTION_ZEROCONF_OPT
1297
1346
1347
+ self .sold_just_in_time_channels [funding_tx .txid ()] = channel_opening_fee_msat // 1000 - funding_tx .get_fee ()
1298
1348
htlc_key = serialize_htlc_key (next_chan .get_scid_or_local_alias (), htlc .htlc_id )
1299
1349
return htlc_key
1300
1350
1351
+ async def cleanup_failed_jit_channel (self , chan : Channel ):
1352
+ """Closes a channel that has no published funding tx (e.g. in case of a failed jit open)"""
1353
+ try :
1354
+ # try to send shutdown to signal peer that channel is dead
1355
+ await util .wait_for2 (self .close_channel (chan .channel_id ), LN_P2P_NETWORK_TIMEOUT )
1356
+ except Exception :
1357
+ self .logger .debug (f"chan shutdown to failed zeroconf peer failed " , exc_info = True )
1358
+ chan .set_state (ChannelState .REDEEMED , force = True )
1359
+ self .lnwatcher .adb .remove_transaction (chan .funding_outpoint .txid )
1360
+ self .lnwatcher .unwatch_channel (chan .get_funding_address (), chan .funding_outpoint .to_str ())
1361
+ self .remove_channel (chan .channel_id )
1362
+
1363
+ async def broadcast_jit_channel_and_wait_for_mempool (self , channel : Channel , funding_tx : Transaction ) -> None :
1364
+ last_broadcast_attempt = 0
1365
+ while True :
1366
+ if time .time () - last_broadcast_attempt > 60 :
1367
+ try :
1368
+ await self .network .broadcast_transaction (funding_tx )
1369
+ self .logger .info (f"broadcasted jit channel open txid: { funding_tx .txid ()} " )
1370
+ except TxBroadcastServerReturnedError :
1371
+ self .logger .error (f"we constructed a weird JIT funding tx. Reverting channel again." , exc_info = True )
1372
+ raise
1373
+ except Exception :
1374
+ self .logger .warning (f"Broadcasting jit channel open tx { funding_tx .txid ()} failed." , exc_info = True )
1375
+ last_broadcast_attempt = time .time ()
1376
+
1377
+ # check if the funding tx is at least in the mempool by now
1378
+ funding_info = channel .get_funding_height ()
1379
+ if funding_info is not None :
1380
+ _ , height , _ = funding_info
1381
+ if height > TX_HEIGHT_LOCAL :
1382
+ return
1383
+
1384
+ await asyncio .sleep (1 )
1385
+
1301
1386
@log_exceptions
1302
1387
async def open_channel_with_peer (
1303
1388
self , peer , funding_sat , * ,
1304
1389
push_sat : int = 0 ,
1305
1390
public : bool = False ,
1306
1391
zeroconf : bool = False ,
1307
- opening_fee : int = None ,
1308
- password = None ):
1392
+ opening_fee_msat : int = None ,
1393
+ password = None ) -> Tuple [ Channel , PartialTransaction ] :
1309
1394
if self .config .ENABLE_ANCHOR_CHANNELS :
1310
1395
self .wallet .unlock (password )
1311
1396
coins = self .wallet .get_spendable_coins (None )
@@ -1316,14 +1401,17 @@ async def open_channel_with_peer(
1316
1401
funding_sat = funding_sat ,
1317
1402
node_id = node_id ,
1318
1403
fee_policy = fee_policy )
1404
+ if opening_fee_msat and funding_tx .get_fee () * 1000 > opening_fee_msat * 0.5 :
1405
+ raise FeeTooLow (f"This channel open is too expensive: fees paid: { funding_tx .get_fee ()} sat. "
1406
+ f"opening fee: { opening_fee_msat // 1000 } sat." )
1319
1407
chan , funding_tx = await self ._open_channel_coroutine (
1320
1408
peer = peer ,
1321
1409
funding_tx = funding_tx ,
1322
1410
funding_sat = funding_sat ,
1323
1411
push_sat = push_sat ,
1324
1412
public = public ,
1325
1413
zeroconf = zeroconf ,
1326
- opening_fee = opening_fee ,
1414
+ opening_fee_msat = opening_fee_msat ,
1327
1415
password = password )
1328
1416
return chan , funding_tx
1329
1417
@@ -1336,7 +1424,7 @@ async def _open_channel_coroutine(
1336
1424
push_sat : int ,
1337
1425
public : bool ,
1338
1426
zeroconf = False ,
1339
- opening_fee = None ,
1427
+ opening_fee_msat = None ,
1340
1428
password : Optional [str ],
1341
1429
) -> Tuple [Channel , PartialTransaction ]:
1342
1430
@@ -1351,7 +1439,7 @@ async def _open_channel_coroutine(
1351
1439
push_msat = push_sat * 1000 ,
1352
1440
public = public ,
1353
1441
zeroconf = zeroconf ,
1354
- opening_fee = opening_fee ,
1442
+ opening_fee = opening_fee_msat ,
1355
1443
temp_channel_id = os .urandom (32 ))
1356
1444
chan , funding_tx = await util .wait_for2 (coro , LN_P2P_NETWORK_TIMEOUT )
1357
1445
util .trigger_callback ('channels_updated' , self .wallet )
0 commit comments