lnworker: reserve wallet addresses also for chan backups
We were already reserving wallet addresses for full channels. Now we also do the same for imported channel backups. (but not for onchain, as we don't have enough info for that) Without this, if the same seed is used on multiple devices (with each device having its own set of LN channels), the wallet instances will reuse keys (specifically the payment_basepoint, which for static_remotekey chans is used as the to_remote output). Now with this change, at least if the wallet instances have imported channel backups of each other, this reuse is avoided.
This commit is contained in:
@@ -487,6 +487,14 @@ class AbstractChannel(Logger, ABC):
|
||||
def can_be_deleted(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_wallet_addresses_channel_might_want_reserved(self) -> Sequence[str]:
|
||||
"""Returns a list of addrs that the wallet should not use, to avoid address-reuse.
|
||||
Typically, these addresses are wallet.is_mine, but that is not guaranteed,
|
||||
in which case the wallet can just ignore those.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ChannelBackup(AbstractChannel):
|
||||
"""
|
||||
@@ -638,6 +646,19 @@ class ChannelBackup(AbstractChannel):
|
||||
ret.append(ChanCloseOption.REQUEST_REMOTE_FCLOSE)
|
||||
return ret
|
||||
|
||||
def get_wallet_addresses_channel_might_want_reserved(self) -> Sequence[str]:
|
||||
if self.is_imported:
|
||||
# For v1 imported cbs, we have the local_payment_pubkey, which is
|
||||
# directly used as p2wpkh() of static_remotekey channels.
|
||||
# (for v0 imported cbs, the correct local_payment_pubkey is missing, and so
|
||||
# we might calculate a different address here, which might not be wallet.is_mine,
|
||||
# but that should be harmless)
|
||||
our_payment_pubkey = self.config[LOCAL].payment_basepoint.pubkey
|
||||
to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
|
||||
return [to_remote_address]
|
||||
else: # on-chain backup
|
||||
return []
|
||||
|
||||
|
||||
class Channel(AbstractChannel):
|
||||
# note: try to avoid naming ctns/ctxs/etc as "current" and "pending".
|
||||
|
||||
@@ -840,14 +840,16 @@ class LNWallet(LNWorker):
|
||||
self._channels = {} # type: Dict[bytes, Channel]
|
||||
channels = self.db.get_dict("channels")
|
||||
for channel_id, c in random_shuffled_copy(channels.items()):
|
||||
self._channels[bfh(channel_id)] = Channel(c, lnworker=self)
|
||||
self._channels[bfh(channel_id)] = chan = Channel(c, lnworker=self)
|
||||
self.wallet.set_reserved_addresses_for_chan(chan, reserved=True)
|
||||
|
||||
self._channel_backups = {} # type: Dict[bytes, ChannelBackup]
|
||||
# order is important: imported should overwrite onchain
|
||||
for name in ["onchain_channel_backups", "imported_channel_backups"]:
|
||||
channel_backups = self.db.get_dict(name)
|
||||
for channel_id, storage in channel_backups.items():
|
||||
self._channel_backups[bfh(channel_id)] = ChannelBackup(storage, lnworker=self)
|
||||
self._channel_backups[bfh(channel_id)] = cb = ChannelBackup(storage, lnworker=self)
|
||||
self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
|
||||
|
||||
self._paysessions = dict() # type: Dict[bytes, PaySession]
|
||||
self.sent_htlcs_info = dict() # type: Dict[SentHtlcKey, SentHtlcInfo]
|
||||
@@ -1330,8 +1332,7 @@ class LNWallet(LNWorker):
|
||||
self.add_channel(chan)
|
||||
channels_db = self.db.get_dict('channels')
|
||||
channels_db[chan.channel_id.hex()] = chan.storage
|
||||
for addr in chan.get_wallet_addresses_channel_might_want_reserved():
|
||||
self.wallet.set_reserved_state_of_address(addr, reserved=True)
|
||||
self.wallet.set_reserved_addresses_for_chan(chan, reserved=True)
|
||||
try:
|
||||
self.save_channel(chan)
|
||||
except Exception:
|
||||
@@ -2850,8 +2851,7 @@ class LNWallet(LNWorker):
|
||||
with self.lock:
|
||||
self._channels.pop(chan_id)
|
||||
self.db.get('channels').pop(chan_id.hex())
|
||||
for addr in chan.get_wallet_addresses_channel_might_want_reserved():
|
||||
self.wallet.set_reserved_state_of_address(addr, reserved=False)
|
||||
self.wallet.set_reserved_addresses_for_chan(chan, reserved=False)
|
||||
|
||||
util.trigger_callback('channels_updated', self.wallet)
|
||||
util.trigger_callback('wallet_updated', self.wallet)
|
||||
@@ -2984,6 +2984,7 @@ class LNWallet(LNWorker):
|
||||
with self.lock:
|
||||
cb = ChannelBackup(cb_storage, lnworker=self)
|
||||
self._channel_backups[channel_id] = cb
|
||||
self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
|
||||
self.wallet.save_db()
|
||||
util.trigger_callback('channels_updated', self.wallet)
|
||||
self.lnwatcher.add_channel(cb.funding_outpoint.to_str(), cb.get_funding_address())
|
||||
@@ -3011,6 +3012,7 @@ class LNWallet(LNWorker):
|
||||
raise Exception('Channel not found')
|
||||
with self.lock:
|
||||
self._channel_backups.pop(channel_id)
|
||||
self.wallet.set_reserved_addresses_for_chan(chan, reserved=False)
|
||||
self.wallet.save_db()
|
||||
util.trigger_callback('channels_updated', self.wallet)
|
||||
|
||||
@@ -3097,6 +3099,7 @@ class LNWallet(LNWorker):
|
||||
d = self.db.get_dict("onchain_channel_backups")
|
||||
d[channel_id] = cb_storage
|
||||
cb = ChannelBackup(cb_storage, lnworker=self)
|
||||
self.wallet.set_reserved_addresses_for_chan(cb, reserved=True)
|
||||
self.wallet.save_db()
|
||||
with self.lock:
|
||||
self._channel_backups[bfh(channel_id)] = cb
|
||||
|
||||
@@ -93,6 +93,7 @@ if TYPE_CHECKING:
|
||||
from .network import Network
|
||||
from .exchange_rate import FxThread
|
||||
from .submarine_swaps import SwapData
|
||||
from .lnchannel import AbstractChannel
|
||||
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
@@ -2033,13 +2034,20 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
|
||||
def set_reserved_state_of_address(self, addr: str, *, reserved: bool) -> None:
|
||||
if not self.is_mine(addr):
|
||||
# silently ignore non-ismine addresses
|
||||
return
|
||||
with self.lock:
|
||||
has_changed = (addr in self._reserved_addresses) != reserved
|
||||
if reserved:
|
||||
self._reserved_addresses.add(addr)
|
||||
else:
|
||||
self._reserved_addresses.discard(addr)
|
||||
self.db.put('reserved_addresses', list(self._reserved_addresses))
|
||||
if has_changed:
|
||||
self.db.put('reserved_addresses', list(self._reserved_addresses))
|
||||
|
||||
def set_reserved_addresses_for_chan(self, chan: 'AbstractChannel', *, reserved: bool) -> None:
|
||||
for addr in chan.get_wallet_addresses_channel_might_want_reserved():
|
||||
self.set_reserved_state_of_address(addr, reserved=reserved)
|
||||
|
||||
def can_export(self):
|
||||
return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key')
|
||||
|
||||
Reference in New Issue
Block a user