mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-31 01:11:35 +00:00
wallet: implement reserving addresses, and use it for LN SRK to_remote
- Use change addresses (instead of receive) for the static_remotekey to_remote outputs, and reserve these to greatly reduce the chance of address-reuse - Use change addresses (instead of receive) for LN channel sweep addresses. Note that these atm are not getting reserved.
This commit is contained in:
parent
6457bb141d
commit
6040e953a3
4 changed files with 50 additions and 5 deletions
|
@ -51,7 +51,7 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
|
|||
ScriptHtlc, PaymentFailure, calc_fees_for_commitment_tx, RemoteMisbehaving, make_htlc_output_witness_script,
|
||||
ShortChannelID, map_htlcs_to_ctx_output_idxs, LNPeerAddr,
|
||||
LN_MAX_HTLC_VALUE_MSAT, fee_for_htlc_output, offered_htlc_trim_threshold_sat,
|
||||
received_htlc_trim_threshold_sat)
|
||||
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address)
|
||||
from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
|
||||
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
|
||||
from .lnhtlc import HTLCManager
|
||||
|
@ -601,6 +601,14 @@ class Channel(AbstractChannel):
|
|||
def is_static_remotekey_enabled(self) -> bool:
|
||||
return bool(self.storage.get('static_remotekey_enabled'))
|
||||
|
||||
def get_wallet_addresses_channel_might_want_reserved(self) -> Sequence[str]:
|
||||
ret = []
|
||||
if self.is_static_remotekey_enabled():
|
||||
our_payment_pubkey = self.config[LOCAL].payment_basepoint.pubkey
|
||||
to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
|
||||
ret.append(to_remote_address)
|
||||
return ret
|
||||
|
||||
def get_feerate(self, subject: HTLCOwner, *, ctn: int) -> int:
|
||||
# returns feerate in sat/kw
|
||||
return self.hm.get_feerate(subject, ctn)
|
||||
|
|
|
@ -496,7 +496,7 @@ class Peer(Logger):
|
|||
# we will want to derive that key
|
||||
wallet = self.lnworker.wallet
|
||||
assert wallet.txin_type == 'p2wpkh'
|
||||
addr = wallet.get_unused_address()
|
||||
addr = wallet.get_new_sweep_address_for_channel()
|
||||
static_remotekey = bfh(wallet.get_public_key(addr))
|
||||
else:
|
||||
static_remotekey = None
|
||||
|
|
|
@ -488,7 +488,7 @@ class LNWallet(LNWorker):
|
|||
self.features |= LnFeatures.OPTION_STATIC_REMOTEKEY_REQ
|
||||
self.payments = self.db.get_dict('lightning_payments') # RHASH -> amount, direction, is_paid
|
||||
self.preimages = self.db.get_dict('lightning_preimages') # RHASH -> preimage
|
||||
self.sweep_address = wallet.get_receiving_address()
|
||||
self.sweep_address = wallet.get_new_sweep_address_for_channel() # TODO possible address-reuse
|
||||
self.logs = defaultdict(list) # type: Dict[str, List[PaymentAttemptLog]] # key is RHASH # (not persisted)
|
||||
self.is_routing = set() # (not persisted) keys of invoices that are in PR_ROUTING state
|
||||
# used in tests
|
||||
|
@ -770,6 +770,8 @@ 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.save_backup()
|
||||
|
||||
def mktx_for_open_channel(self, *, coins: Sequence[PartialTxInput], funding_sat: int,
|
||||
|
@ -1309,6 +1311,8 @@ 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)
|
||||
|
||||
util.trigger_callback('channels_updated', self.wallet)
|
||||
util.trigger_callback('wallet_updated', self.wallet)
|
||||
|
@ -1404,7 +1408,8 @@ class LNBackups(Logger):
|
|||
|
||||
@property
|
||||
def sweep_address(self) -> str:
|
||||
return self.wallet.get_receiving_address()
|
||||
# TODO possible address-reuse
|
||||
return self.wallet.get_new_sweep_address_for_channel()
|
||||
|
||||
def channel_state_changed(self, chan):
|
||||
util.trigger_callback('channel', chan)
|
||||
|
|
|
@ -247,6 +247,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
self.fiat_value = db.get_dict('fiat_value')
|
||||
self.receive_requests = db.get_dict('payment_requests')
|
||||
self.invoices = db.get_dict('invoices')
|
||||
self._reserved_addresses = set(db.get('reserved_addresses', []))
|
||||
|
||||
self._prepare_onchain_invoice_paid_detection()
|
||||
self.calc_unused_change_addresses()
|
||||
|
@ -386,7 +387,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
addrs = self._unused_change_addresses
|
||||
else:
|
||||
addrs = self.get_change_addresses()
|
||||
self._unused_change_addresses = [addr for addr in addrs if not self.is_used(addr)]
|
||||
self._unused_change_addresses = [addr for addr in addrs
|
||||
if not self.is_used(addr) and not self.is_address_reserved(addr)]
|
||||
return list(self._unused_change_addresses)
|
||||
|
||||
def is_deterministic(self) -> bool:
|
||||
|
@ -1046,6 +1048,22 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
max_change = self.max_change_outputs if self.multiple_change else 1
|
||||
return change_addrs[:max_change]
|
||||
|
||||
@check_returned_address_for_corruption
|
||||
def get_new_sweep_address_for_channel(self) -> str:
|
||||
# Recalc and get unused change addresses
|
||||
addrs = self.calc_unused_change_addresses()
|
||||
if addrs:
|
||||
selected_addr = addrs[0]
|
||||
else:
|
||||
# if there are none, take one randomly from the last few
|
||||
addrs = self.get_change_addresses(slice_start=-self.gap_limit_for_change)
|
||||
if addrs:
|
||||
selected_addr = random.choice(addrs)
|
||||
else: # fallback for e.g. imported wallets
|
||||
selected_addr = self.get_receiving_address()
|
||||
assert is_address(selected_addr), f"not valid bitcoin address: {selected_addr}"
|
||||
return selected_addr
|
||||
|
||||
def make_unsigned_transaction(self, *, coins: Sequence[PartialTxInput],
|
||||
outputs: List[PartialTxOutput], fee=None,
|
||||
change_addr: str = None, is_sweep=False) -> PartialTransaction:
|
||||
|
@ -1182,6 +1200,20 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||
self.frozen_coins -= set(utxos)
|
||||
self.db.put('frozen_coins', list(self.frozen_coins))
|
||||
|
||||
def is_address_reserved(self, addr: str) -> bool:
|
||||
# note: atm 'reserved' status is only taken into consideration for 'change addresses'
|
||||
return addr in self._reserved_addresses
|
||||
|
||||
def set_reserved_state_of_address(self, addr: str, *, reserved: bool) -> None:
|
||||
if not self.is_mine(addr):
|
||||
return
|
||||
with self.lock:
|
||||
if reserved:
|
||||
self._reserved_addresses.add(addr)
|
||||
else:
|
||||
self._reserved_addresses.discard(addr)
|
||||
self.db.put('reserved_addresses', list(self._reserved_addresses))
|
||||
|
||||
def can_export(self):
|
||||
return not self.is_watching_only() and hasattr(self.keystore, 'get_private_key')
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue