Merge pull request #10463 from f321x/jit_2
lnwallet: zeroconf/just-in-time improvements and tests
This commit is contained in:
@@ -20,6 +20,7 @@ from .util import read_QIcon, WWLabel, MessageBoxMixin, MONOSPACE_FONT, get_icon
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
from electrum.wallet import Request
|
||||
|
||||
|
||||
class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
@@ -101,6 +102,9 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.receive_zeroconf_button = QPushButton(_('Accept'))
|
||||
self.receive_zeroconf_button.clicked.connect(self.on_accept_zeroconf)
|
||||
|
||||
self.previous_request = None # type: Optional['Request']
|
||||
self.confirmed_zeroconf_for_this_request = False # type: bool
|
||||
|
||||
def on_receive_rebalance():
|
||||
if self.receive_rebalance_button.suggestion:
|
||||
chan1, chan2, delta = self.receive_rebalance_button.suggestion
|
||||
@@ -221,7 +225,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
|
||||
def update_receive_widgets(self):
|
||||
b = self.config.GUI_QT_RECEIVE_TAB_QR_VISIBLE
|
||||
self.receive_widget.update_visibility(b)
|
||||
self.receive_widget.update_visibility(b, bool(self.receive_help_text.text()))
|
||||
|
||||
def update_current_request(self):
|
||||
if len(self.request_list.selectionModel().selectedRows(0)) > 1:
|
||||
@@ -229,6 +233,9 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
else:
|
||||
key = self.request_list.get_current_key()
|
||||
req = self.wallet.get_request(key) if key else None
|
||||
if req != self.previous_request:
|
||||
self.previous_request = req
|
||||
self.confirmed_zeroconf_for_this_request = False
|
||||
if req is None:
|
||||
self.receive_e.setText('')
|
||||
self.addr = self.URI = self.lnaddr = ''
|
||||
@@ -243,7 +250,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.ln_help = help_texts.ln_help
|
||||
can_rebalance = help_texts.can_rebalance()
|
||||
can_swap = help_texts.can_swap()
|
||||
can_zeroconf = help_texts.can_zeroconf()
|
||||
can_zeroconf = help_texts.can_zeroconf() if not self.confirmed_zeroconf_for_this_request else False
|
||||
self.receive_rebalance_button.suggestion = help_texts.ln_rebalance_suggestion
|
||||
self.receive_swap_button.suggestion = help_texts.ln_swap_suggestion
|
||||
self.receive_rebalance_button.setVisible(can_rebalance)
|
||||
@@ -253,25 +260,26 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
|
||||
self.receive_zeroconf_button.setVisible(can_zeroconf)
|
||||
self.receive_zeroconf_button.setEnabled(can_zeroconf)
|
||||
text, data, help_text, title = self.get_tab_data()
|
||||
if self.confirmed_zeroconf_for_this_request and help_texts.can_zeroconf():
|
||||
help_text = ''
|
||||
# set help before receive_e so we don't flicker from qr to help
|
||||
self.receive_help_text.setText(help_text)
|
||||
self.receive_e.setText(text)
|
||||
self.receive_qr.setData(data)
|
||||
self.receive_help_text.setText(help_text)
|
||||
for w in [self.receive_e, self.receive_qr]:
|
||||
w.setEnabled(bool(text) and (not help_text or can_zeroconf))
|
||||
w.setToolTip(help_text)
|
||||
# macOS hack (similar to #4777)
|
||||
self.receive_e.repaint()
|
||||
# always show
|
||||
if can_zeroconf:
|
||||
# show the help message if zeroconf so user can first accept it and still sees the invoice
|
||||
# after accepting
|
||||
self.receive_widget.show_help()
|
||||
self.receive_widget.setVisible(True)
|
||||
self.toggle_qr_button.setEnabled(True)
|
||||
self.update_receive_qr_window()
|
||||
|
||||
def on_accept_zeroconf(self):
|
||||
self.receive_zeroconf_button.setVisible(False)
|
||||
self.confirmed_zeroconf_for_this_request = True
|
||||
self.receive_help_text.setText('')
|
||||
self.update_receive_widgets()
|
||||
|
||||
def get_tab_data(self):
|
||||
@@ -386,8 +394,8 @@ class ReceiveWidget(QWidget):
|
||||
|
||||
self.setLayout(vbox)
|
||||
|
||||
def update_visibility(self, is_qr):
|
||||
if str(self.textedit.toPlainText()):
|
||||
def update_visibility(self, is_qr: bool, show_help: bool):
|
||||
if str(self.textedit.toPlainText()) and not show_help:
|
||||
self.help_widget.setVisible(False)
|
||||
self.textedit.setVisible(not is_qr)
|
||||
self.qr.setVisible(is_qr)
|
||||
|
||||
+31
-19
@@ -41,7 +41,7 @@ from .bitcoin import redeem_script_to_address
|
||||
from .crypto import sha256, sha256d
|
||||
from .transaction import Transaction, PartialTransaction, TxInput, Sighash
|
||||
from .logging import Logger
|
||||
from .lntransport import LNPeerAddr
|
||||
from .lntransport import LNPeerAddr, extract_nodeid, ConnStringFormatError
|
||||
from .lnonion import OnionRoutingFailure
|
||||
from . import lnutil
|
||||
from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints,
|
||||
@@ -59,7 +59,7 @@ from .lnsweep import sweep_their_htlctx_justice, sweep_our_htlctx, SweepInfo, Ma
|
||||
from .lnsweep import sweep_their_ctx_to_remote_backup
|
||||
from .lnhtlc import HTLCManager
|
||||
from .lnmsg import encode_msg, decode_msg
|
||||
from .address_synchronizer import TX_HEIGHT_LOCAL
|
||||
from .address_synchronizer import TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONFIRMED
|
||||
from .lnutil import CHANNEL_OPENING_TIMEOUT_BLOCKS, CHANNEL_OPENING_TIMEOUT_SEC
|
||||
from .lnutil import ChannelBackupStorage, ImportedChannelBackupStorage, OnchainChannelBackupStorage
|
||||
from .lnutil import format_short_channel_id
|
||||
@@ -224,6 +224,7 @@ class AbstractChannel(Logger, ABC):
|
||||
return self._state
|
||||
|
||||
def is_funded(self) -> bool:
|
||||
# NOTE: also true for unfunded zeroconf channels (OPEN > FUNDED)
|
||||
return self.get_state() >= ChannelState.FUNDED
|
||||
|
||||
def is_open(self) -> bool:
|
||||
@@ -375,26 +376,33 @@ class AbstractChannel(Logger, ABC):
|
||||
self.logger.warning(f"dropping incoming channel, funding tx not found in mempool")
|
||||
self.lnworker.remove_channel(self.channel_id)
|
||||
elif self.is_zeroconf() and state in [ChannelState.OPEN, ChannelState.CLOSING, ChannelState.FORCE_CLOSING]:
|
||||
chan_age = now() - self.storage['init_timestamp']
|
||||
# handling zeroconf channels with no funding tx, can happen if broadcasting fails on LSP side
|
||||
# or if the LSP did double spent the funding tx/never published it intentionally
|
||||
# only remove a timed out OPEN channel if we are connected to the network to prevent removing it if we went
|
||||
# offline before seeing the funding tx
|
||||
if state != ChannelState.OPEN or chan_age > ZEROCONF_TIMEOUT and self.lnworker.network.is_connected():
|
||||
# we delete the channel if its in closing state (either initiated manually by client or by LSP on failure)
|
||||
# or if the channel is not seeing any funding tx after 10 minutes to prevent further usage (limit damage)
|
||||
self.set_state(ChannelState.REDEEMED, force=True)
|
||||
local_balance_sat = int(self.balance(LOCAL) // 1000)
|
||||
if local_balance_sat > 0:
|
||||
# or if the LSP did double spent the funding tx/never published it intentionally.
|
||||
if not self.lnworker.wallet.is_up_to_date() or not self.lnworker.network \
|
||||
or self.lnworker.network.blockchain().is_tip_stale():
|
||||
# ensure we are up to date to prevent accidentally dropping a channel that is funded
|
||||
return
|
||||
chan_age = now() - self.storage['init_timestamp']
|
||||
if chan_age > ZEROCONF_TIMEOUT:
|
||||
# freeze the channel to avoid receiving even more into this unfunded channel.
|
||||
# NOTE: we don't reject htlcs arriving on frozen channels, this only really
|
||||
# stops us from including the channel in invoice routing hints.
|
||||
if isinstance(self, Channel):
|
||||
self.set_frozen_for_receiving(True)
|
||||
|
||||
# un-trust the LSP so the user doesn't accept another channel from the same provider
|
||||
# compare the node id's as the user might already have changed to another one
|
||||
if self.node_id == self.lnworker.trusted_zeroconf_node_id:
|
||||
self.lnworker.config.ZEROCONF_TRUSTED_NODE = ''
|
||||
|
||||
if self.has_funding_timed_out():
|
||||
self.lnworker.remove_channel(self.channel_id)
|
||||
# remove remaining local transactions from the wallet, this will also remove child transactions (closing tx)
|
||||
# self.lnworker.lnwatcher.adb.remove_transaction(self.funding_outpoint.txid)
|
||||
if (local_balance_sat := int(self.balance(LOCAL) // 1000)) > 0:
|
||||
self.logger.warning(
|
||||
f"we may have been scammed out of {local_balance_sat} sat by our "
|
||||
f"JIT provider: {self.lnworker.config.ZEROCONF_TRUSTED_NODE} or he didn't use our preimage")
|
||||
self.lnworker.config.ZEROCONF_TRUSTED_NODE = ''
|
||||
# FIXME this is broken: lnwatcher.unwatch_channel does not exist
|
||||
self.lnworker.lnwatcher.unwatch_channel(self.get_funding_address(), self.funding_outpoint.to_str())
|
||||
# remove remaining local transactions from the wallet, this will also remove child transactions (closing tx)
|
||||
self.lnworker.lnwatcher.adb.remove_transaction(self.funding_outpoint.txid)
|
||||
self.lnworker.remove_channel(self.channel_id)
|
||||
|
||||
def update_funded_state(self, *, funding_txid: str, funding_height: TxMinedInfo) -> None:
|
||||
self.save_funding_height(txid=funding_txid, height=funding_height.height(), timestamp=funding_height.timestamp)
|
||||
@@ -420,6 +428,9 @@ class AbstractChannel(Logger, ABC):
|
||||
# remove zeroconf flag as we are now confirmed, this is to prevent an electrum server causing
|
||||
# us to remove a channel later in update_unfunded_state by omitting its funding tx
|
||||
self.remove_zeroconf_flag()
|
||||
# unfreeze in case it was frozen in update_unfunded_state
|
||||
if isinstance(self, Channel):
|
||||
self.set_frozen_for_receiving(False)
|
||||
|
||||
def update_closed_state(self, *, funding_txid: str, funding_height: TxMinedInfo,
|
||||
closing_txid: str, closing_height: TxMinedInfo, keep_watching: bool) -> None:
|
||||
@@ -843,7 +854,8 @@ class Channel(AbstractChannel):
|
||||
return self.is_redeemed()
|
||||
|
||||
def has_funding_timed_out(self):
|
||||
if self.is_initiator() or self.is_funded():
|
||||
funding_height = self.get_funding_height()
|
||||
if self.is_initiator() or funding_height and funding_height[1] > TX_HEIGHT_UNCONFIRMED:
|
||||
return False
|
||||
if self.lnworker.network.blockchain().is_tip_stale() or not self.lnworker.wallet.is_up_to_date():
|
||||
return False
|
||||
|
||||
+14
-6
@@ -101,6 +101,11 @@ class Peer(Logger, EventListener):
|
||||
self.pubkey = pubkey # remote pubkey
|
||||
self.privkey = self.transport.privkey # local privkey
|
||||
self.features = self.lnworker.features # type: LnFeatures
|
||||
if lnworker == lnworker.network.lngossip or \
|
||||
lnworker.config.ZEROCONF_TRUSTED_NODE and pubkey != lnworker.trusted_zeroconf_node_id:
|
||||
# don't signal zeroconf support if we are client (a trusted node is configured),
|
||||
# and Peer is not our trusted node
|
||||
self.features &= ~LnFeatures.OPTION_ZEROCONF_OPT
|
||||
self.their_features = LnFeatures(0) # type: LnFeatures
|
||||
self.node_ids = [self.pubkey, privkey_to_pubkey(self.privkey)]
|
||||
assert self.node_ids[0] != self.node_ids[1]
|
||||
@@ -1259,13 +1264,16 @@ class Peer(Logger, EventListener):
|
||||
# store the temp id now, so that it is recognized for e.g. 'error' messages
|
||||
self.temp_id_to_id[temp_chan_id] = None
|
||||
self._cleanup_temp_channelids()
|
||||
channel_opening_fee_tlv = open_channel_tlvs.get('channel_opening_fee', {})
|
||||
channel_opening_fee = channel_opening_fee_tlv.get('channel_opening_fee')
|
||||
if channel_opening_fee:
|
||||
# todo check that the fee is reasonable
|
||||
channel_opening_fee = open_channel_tlvs.get('channel_opening_fee', {}).get('channel_opening_fee')
|
||||
if channel_opening_fee: # just-in-time channel opening
|
||||
assert is_zeroconf
|
||||
self.logger.info(f"just-in-time opening fee: {channel_opening_fee} msat")
|
||||
pass
|
||||
# the opening fee consists of the fee configured by the LSP + mining fees of the funding tx
|
||||
channel_opening_fee_sat = channel_opening_fee // 1000
|
||||
if channel_opening_fee_sat > funding_sat * 0.1:
|
||||
# TODO: if there will be some discovery channel where LSPs announce their fees
|
||||
# we should compare against the fees they announced here.
|
||||
raise Exception(f"{channel_opening_fee_sat=} exceeding fee limit, rejecting channel ({funding_sat=})")
|
||||
self.logger.info(f"just-in-time channel: {channel_opening_fee_sat=}")
|
||||
|
||||
if channel_type & ChannelType.OPTION_ANCHORS_ZERO_FEE_HTLC_TX:
|
||||
multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_they_opened(
|
||||
|
||||
+81
-26
@@ -74,7 +74,7 @@ from .lnutil import (
|
||||
OnchainChannelBackupStorage, ln_compare_features, IncompatibleLightningFeatures, PaymentFeeBudget,
|
||||
NBLOCK_CLTV_DELTA_TOO_FAR_INTO_FUTURE, GossipForwardingMessage, MIN_FUNDING_SAT,
|
||||
MIN_FINAL_CLTV_DELTA_BUFFER_INVOICE, RecvMPPResolution, ReceivedMPPStatus, ReceivedMPPHtlc,
|
||||
PaymentSuccess, ChannelType, LocalConfig, Keypair,
|
||||
PaymentSuccess, ChannelType, LocalConfig, Keypair, ZEROCONF_TIMEOUT,
|
||||
)
|
||||
from .lnonion import (
|
||||
decode_onion_error, OnionFailureCode, OnionRoutingFailure, OnionPacket,
|
||||
@@ -1010,7 +1010,7 @@ class LNWallet(Logger):
|
||||
features = LNWALLET_FEATURES
|
||||
if self.config.ENABLE_ANCHOR_CHANNELS:
|
||||
features |= LnFeatures.OPTION_ANCHORS_ZERO_FEE_HTLC_OPT
|
||||
if self.config.ACCEPT_ZEROCONF_CHANNELS:
|
||||
if self.config.OPEN_ZEROCONF_CHANNELS:
|
||||
features |= LnFeatures.OPTION_ZEROCONF_OPT
|
||||
if self.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS or self.config.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS:
|
||||
features |= LnFeatures.OPTION_ONION_MESSAGE_OPT
|
||||
@@ -1485,34 +1485,42 @@ class LNWallet(Logger):
|
||||
payment_hash: bytes,
|
||||
next_onion: OnionPacket,
|
||||
) -> str:
|
||||
assert self.config.OPEN_ZEROCONF_CHANNELS
|
||||
# if an exception is raised during negotiation, we raise an OnionRoutingFailure.
|
||||
# this will cancel the incoming HTLC
|
||||
|
||||
next_chan: Optional[Channel] = None
|
||||
# prevent settling the htlc until the channel opening was successful so we can fail it if needed
|
||||
self.dont_settle_htlcs[payment_hash.hex()] = None
|
||||
try:
|
||||
funding_sat = 2 * (next_amount_msat_htlc // 1000) # try to fully spend htlcs
|
||||
assert self.config.ZEROCONF_CHANNEL_SIZE_PERCENT >= 120, "ZEROCONF_CHANNEL_SIZE_PERCENT below min of 120%"
|
||||
assert self.config.ZEROCONF_OPENING_FEE_PPM >= 0, f"invalid {self.config.ZEROCONF_OPENING_FEE_PPM=}"
|
||||
funding_sat = (self.config.ZEROCONF_CHANNEL_SIZE_PERCENT * (next_amount_msat_htlc // 1000)) // 100
|
||||
password = self.wallet.get_unlocked_password() if self.wallet.has_password() else None
|
||||
channel_opening_fee = next_amount_msat_htlc // 100
|
||||
if channel_opening_fee // 1000 < self.config.ZEROCONF_MIN_OPENING_FEE:
|
||||
self.logger.info(f'rejecting JIT channel: payment too low')
|
||||
channel_opening_base_fee_msat = (next_amount_msat_htlc * self.config.ZEROCONF_OPENING_FEE_PPM) // 1_000_000
|
||||
if channel_opening_base_fee_msat // 1000 < self.config.ZEROCONF_MIN_OPENING_FEE:
|
||||
self.logger.info(
|
||||
f'rejecting JIT channel: {(channel_opening_base_fee_msat // 1000)=} < {self.config.ZEROCONF_MIN_OPENING_FEE=}'
|
||||
)
|
||||
raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'payment too low')
|
||||
self.logger.info(f'channel opening fee (sats): {channel_opening_fee//1000}')
|
||||
next_chan, funding_tx = await self.open_channel_with_peer(
|
||||
next_peer, funding_sat,
|
||||
push_sat=0,
|
||||
zeroconf=True,
|
||||
public=False,
|
||||
opening_fee=channel_opening_fee,
|
||||
opening_base_fee_msat=channel_opening_base_fee_msat,
|
||||
password=password,
|
||||
)
|
||||
async def wait_for_channel():
|
||||
while not next_chan.is_open():
|
||||
await asyncio.sleep(1)
|
||||
await util.wait_for2(wait_for_channel(), LN_P2P_NETWORK_TIMEOUT)
|
||||
next_chan.save_remote_scid_alias(self._scid_alias_of_node(next_peer.pubkey))
|
||||
self.logger.info(f'JIT channel is open')
|
||||
next_amount_msat_htlc -= channel_opening_fee
|
||||
self.logger.info(f'JIT channel is open (will forward htlc and await preimage now)')
|
||||
self.logger.info(f'channel opening fee (sats): {channel_opening_base_fee_msat//1000} + {funding_tx.get_fee()} mining fee')
|
||||
next_amount_msat_htlc -= channel_opening_base_fee_msat + funding_tx.get_fee() * 1000
|
||||
if next_amount_msat_htlc < 1_000:
|
||||
self.logger.info(f'rejecting JIT channel: payment too low after deducting mining fees')
|
||||
raise OnionRoutingFailure(code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, data=b'payment too low after deducting mining fees')
|
||||
# fixme: some checks are missing
|
||||
htlc = next_peer.send_htlc(
|
||||
chan=next_chan,
|
||||
@@ -1525,12 +1533,32 @@ class LNWallet(Logger):
|
||||
await asyncio.sleep(1)
|
||||
await util.wait_for2(wait_for_preimage(), LN_P2P_NETWORK_TIMEOUT)
|
||||
|
||||
# We have been paid and can broadcast
|
||||
# todo: if broadcasting raise an exception, we should try to rebroadcast
|
||||
await self.network.broadcast_transaction(funding_tx)
|
||||
except OnionRoutingFailure:
|
||||
raise
|
||||
except Exception:
|
||||
# We have been paid and can broadcast.
|
||||
# Channel providers should run their own, trusted Electrum server as
|
||||
# we could lose funds here if the server broadcasts the tx but omits it from us
|
||||
first_broadcast_ts = time.time()
|
||||
while time.time() - first_broadcast_ts < ZEROCONF_TIMEOUT * 0.75:
|
||||
if await self.network.try_broadcasting(funding_tx, "jit channel funding"):
|
||||
break
|
||||
await asyncio.sleep(30)
|
||||
# we cannot rely on success of try_broadcasting to determine broadcasting success
|
||||
# as broadcasting might fail with some harmless error like 'transaction already in mempool'
|
||||
tx_mined_info = self.wallet.adb.get_tx_height(funding_tx.txid())
|
||||
if tx_mined_info.height() > TX_HEIGHT_LOCAL:
|
||||
self.logger.debug(f"found our jit channel funding tx: {tx_mined_info.height()=}")
|
||||
break
|
||||
else:
|
||||
raise OnionRoutingFailure(
|
||||
code=OnionFailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
|
||||
data=b'failed to broadcast funding transaction',
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"failed to open just in time channel: {repr(e)}")
|
||||
if next_chan:
|
||||
await self._cleanup_failed_jit_channel(next_chan)
|
||||
self._preimages.pop(payment_hash.hex(), None)
|
||||
if isinstance(e, OnionRoutingFailure):
|
||||
raise
|
||||
raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
|
||||
finally:
|
||||
del self.dont_settle_htlcs[payment_hash.hex()]
|
||||
@@ -1538,13 +1566,30 @@ class LNWallet(Logger):
|
||||
htlc_key = serialize_htlc_key(next_chan.get_scid_or_local_alias(), htlc.htlc_id)
|
||||
return htlc_key
|
||||
|
||||
async def _cleanup_failed_jit_channel(self, chan: Channel):
|
||||
"""
|
||||
Removes a just in time channel where we didn't broadcast the funding
|
||||
transaction, e.g. when the client didn't release the preimage.
|
||||
"""
|
||||
funding_height = chan.get_funding_height()
|
||||
if funding_height is not None and funding_height[1] > TX_HEIGHT_LOCAL:
|
||||
raise Exception("must not delete the channel if it has been broadcast")
|
||||
# try to be nice and send shutdown to signal peer that this channel is dead
|
||||
try:
|
||||
await util.wait_for2(self.close_channel(chan.channel_id), LN_P2P_NETWORK_TIMEOUT)
|
||||
except Exception:
|
||||
self.logger.debug(f"sending chan shutdown to failed zeroconf peer failed ", exc_info=True)
|
||||
chan.set_state(ChannelState.REDEEMED, force=True)
|
||||
self.lnwatcher.adb.remove_transaction(chan.funding_outpoint.txid)
|
||||
self.remove_channel(chan.channel_id)
|
||||
|
||||
@log_exceptions
|
||||
async def open_channel_with_peer(
|
||||
self, peer, funding_sat, *,
|
||||
push_sat: int = 0,
|
||||
public: bool = False,
|
||||
zeroconf: bool = False,
|
||||
opening_fee: int = None,
|
||||
opening_base_fee_msat: Optional[int] = None,
|
||||
password=None):
|
||||
if self.config.ENABLE_ANCHOR_CHANNELS:
|
||||
self.wallet.unlock(password)
|
||||
@@ -1556,6 +1601,9 @@ class LNWallet(Logger):
|
||||
funding_sat=funding_sat,
|
||||
node_id=node_id,
|
||||
fee_policy=fee_policy)
|
||||
if opening_base_fee_msat:
|
||||
# add funding tx fee on top of the opening fee to avoid opening channels at a loss
|
||||
opening_base_fee_msat += funding_tx.get_fee() * 1000
|
||||
chan, funding_tx = await self._open_channel_coroutine(
|
||||
peer=peer,
|
||||
funding_tx=funding_tx,
|
||||
@@ -1563,7 +1611,7 @@ class LNWallet(Logger):
|
||||
push_sat=push_sat,
|
||||
public=public,
|
||||
zeroconf=zeroconf,
|
||||
opening_fee=opening_fee,
|
||||
opening_fee=opening_base_fee_msat,
|
||||
password=password)
|
||||
return chan, funding_tx
|
||||
|
||||
@@ -3351,17 +3399,24 @@ class LNWallet(Logger):
|
||||
return False
|
||||
|
||||
def can_get_zeroconf_channel(self) -> bool:
|
||||
if not self.config.ACCEPT_ZEROCONF_CHANNELS and self.config.ZEROCONF_TRUSTED_NODE:
|
||||
# check if zeroconf is accepted and client has trusted zeroconf node configured
|
||||
if not self.config.OPEN_ZEROCONF_CHANNELS:
|
||||
return False
|
||||
try:
|
||||
node_id = extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0]
|
||||
except ConnStringFormatError:
|
||||
# invalid connection string
|
||||
node_id = self.trusted_zeroconf_node_id
|
||||
if not node_id:
|
||||
return False
|
||||
# only return True if we are connected to the zeroconf provider
|
||||
return self.lnpeermgr.get_peer_by_pubkey(node_id) is not None
|
||||
|
||||
@property
|
||||
def trusted_zeroconf_node_id(self) -> Optional[bytes]:
|
||||
if not self.config.ZEROCONF_TRUSTED_NODE:
|
||||
return None
|
||||
try:
|
||||
return extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0]
|
||||
except ConnStringFormatError:
|
||||
self.logger.warning(f"invalid zeroconf node connection string configured")
|
||||
return None
|
||||
|
||||
def _suggest_channels_for_rebalance(self, direction, amount_sat) -> Sequence[Tuple[Channel, int]]:
|
||||
"""
|
||||
Suggest a channel and amount to send/receive with that channel, so that we will be able to receive/send amount_sat
|
||||
@@ -4012,7 +4067,7 @@ class LNWallet(Logger):
|
||||
|
||||
# do we have a connection to the node?
|
||||
next_peer = self.lnpeermgr.get_peer_by_pubkey(outgoing_node_id)
|
||||
if next_peer and next_peer.accepts_zeroconf():
|
||||
if next_peer and next_peer.accepts_zeroconf() and self.features.supports(LnFeatures.OPTION_ZEROCONF_OPT):
|
||||
self.logger.info(f'JIT: found next_peer')
|
||||
for next_chan in next_peer.channels.values():
|
||||
if next_chan.can_pay(amt_to_forward):
|
||||
|
||||
@@ -954,9 +954,15 @@ Warning: setting this to too low will result in lots of payment failures."""),
|
||||
# anchor outputs channels
|
||||
ENABLE_ANCHOR_CHANNELS = ConfigVar('enable_anchor_channels', default=True, type_=bool)
|
||||
# zeroconf channels
|
||||
ACCEPT_ZEROCONF_CHANNELS = ConfigVar('accept_zeroconf_channels', default=False, type_=bool)
|
||||
OPEN_ZEROCONF_CHANNELS = ConfigVar('open_zeroconf_channels', default=False, type_=bool)
|
||||
ZEROCONF_TRUSTED_NODE = ConfigVar('zeroconf_trusted_node', default='', type_=str)
|
||||
# minimum absolute fee in sat for which we will open a channel just in time
|
||||
ZEROCONF_MIN_OPENING_FEE = ConfigVar('zeroconf_min_opening_fee', default=5000, type_=int)
|
||||
# fee in ppm of the outgoing htlcs value we charge for opening new channels just in time
|
||||
ZEROCONF_OPENING_FEE_PPM = ConfigVar('zeroconf_opening_fee_ppm', default=10_000, type_=int)
|
||||
# size of the channel the lsp opens to the client in percent of the outgoing htlcs value
|
||||
# (before deducting fees). required to be at least 120% to leave some buffer for the channel reserve
|
||||
ZEROCONF_CHANNEL_SIZE_PERCENT = ConfigVar('zeroconf_channel_size_percent', default=200, type_=int)
|
||||
LN_UTXO_RESERVE = ConfigVar(
|
||||
'ln_utxo_reserve',
|
||||
default=10000,
|
||||
|
||||
+1
-1
@@ -3455,7 +3455,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
zeroconf_nodeid = extract_nodeid(self.config.ZEROCONF_TRUSTED_NODE)[0]
|
||||
except Exception:
|
||||
zeroconf_nodeid = None
|
||||
can_get_zeroconf_channel = (self.lnworker and self.config.ACCEPT_ZEROCONF_CHANNELS
|
||||
can_get_zeroconf_channel = (self.lnworker and self.config.OPEN_ZEROCONF_CHANNELS
|
||||
and self.lnworker.lnpeermgr.get_peer_by_pubkey(zeroconf_nodeid) is not None)
|
||||
status = self.get_invoice_status(req)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user