1
0

Replace wallet backup with channel backups

- channels can be backed up individually
 - backups are added to lnwatcher
 - AbstractChannel ancestor class
This commit is contained in:
ThomasV
2020-03-13 11:44:29 +01:00
parent e5b1596b69
commit 8f41aeb783
16 changed files with 705 additions and 216 deletions

View File

@@ -64,6 +64,9 @@ from .lnrouter import RouteEdge, LNPaymentRoute, is_route_sane_to_use
from .address_synchronizer import TX_HEIGHT_LOCAL
from . import lnsweep
from .lnwatcher import LNWalletWatcher
from .crypto import pw_encode_bytes, pw_decode_bytes, PW_HASH_VERSION_LATEST
from .lnutil import ChannelBackupStorage
from .lnchannel import ChannelBackup
if TYPE_CHECKING:
from .network import Network
@@ -219,7 +222,8 @@ class LNWorker(Logger):
return peer
def peer_closed(self, peer: Peer) -> None:
self.peers.pop(peer.pubkey)
if peer.pubkey in self.peers:
self.peers.pop(peer.pubkey)
def num_peers(self) -> int:
return sum([p.is_initialized() for p in self.peers.values()])
@@ -492,7 +496,8 @@ class LNWallet(LNWorker):
self.lnwatcher = LNWalletWatcher(self, network)
self.lnwatcher.start_network(network)
self.network = network
for chan_id, chan in self.channels.items():
for chan in self.channels.values():
self.lnwatcher.add_channel(chan.funding_outpoint.to_str(), chan.get_funding_address())
super().start_network(network)
@@ -763,8 +768,6 @@ class LNWallet(LNWorker):
def open_channel(self, *, connect_str: str, funding_tx: PartialTransaction,
funding_sat: int, push_amt_sat: int, password: str = None,
timeout: Optional[int] = 20) -> Tuple[Channel, PartialTransaction]:
if self.wallet.is_lightning_backup():
raise Exception(_('Cannot create channel: this is a backup file'))
if funding_sat > LN_MAX_FUNDING_SAT:
raise Exception(_("Requested channel capacity is over protocol allowed maximum."))
coro = self._open_channel_coroutine(connect_str=connect_str, funding_tx=funding_tx, funding_sat=funding_sat,
@@ -1319,3 +1322,100 @@ class LNWallet(LNWorker):
if feerate_per_kvbyte is None:
feerate_per_kvbyte = FEERATE_FALLBACK_STATIC_FEE
return max(253, feerate_per_kvbyte // 4)
def create_channel_backup(self, channel_id):
chan = self.channels[channel_id]
peer_addresses = list(chan.get_peer_addresses())
peer_addr = peer_addresses[0]
return ChannelBackupStorage(
node_id = chan.node_id,
privkey = self.node_keypair.privkey,
funding_txid = chan.funding_outpoint.txid,
funding_index = chan.funding_outpoint.output_index,
funding_address = chan.get_funding_address(),
host = peer_addr.host,
port = peer_addr.port,
is_initiator = chan.constraints.is_initiator,
channel_seed = chan.config[LOCAL].channel_seed,
local_delay = chan.config[LOCAL].to_self_delay,
remote_delay = chan.config[REMOTE].to_self_delay,
remote_revocation_pubkey = chan.config[REMOTE].revocation_basepoint.pubkey,
remote_payment_pubkey = chan.config[REMOTE].payment_basepoint.pubkey)
def export_channel_backup(self, channel_id):
xpub = self.wallet.get_fingerprint()
backup_bytes = self.create_channel_backup(channel_id).to_bytes()
assert backup_bytes == ChannelBackupStorage.from_bytes(backup_bytes).to_bytes(), "roundtrip failed"
encrypted = pw_encode_bytes(backup_bytes, xpub, version=PW_HASH_VERSION_LATEST)
assert backup_bytes == pw_decode_bytes(encrypted, xpub, version=PW_HASH_VERSION_LATEST), "encrypt failed"
return encrypted
class LNBackups(Logger):
def __init__(self, wallet: 'Abstract_Wallet'):
Logger.__init__(self)
self.features = LnFeatures(0)
self.features |= LnFeatures.OPTION_DATA_LOSS_PROTECT_OPT
self.features |= LnFeatures.OPTION_STATIC_REMOTEKEY_OPT
self.taskgroup = SilentTaskGroup()
self.lock = threading.RLock()
self.wallet = wallet
self.db = wallet.db
self.sweep_address = wallet.get_receiving_address()
self.channel_backups = {}
for channel_id, cb in self.db.get_dict("channel_backups").items():
self.channel_backups[bfh(channel_id)] = ChannelBackup(cb, sweep_address=self.sweep_address, lnworker=self)
def peer_closed(self, chan):
pass
async def on_channel_update(self, chan):
pass
def channel_by_txo(self, txo):
with self.lock:
channel_backups = list(self.channel_backups.values())
for chan in channel_backups:
if chan.funding_outpoint.to_str() == txo:
return chan
def start_network(self, network: 'Network'):
assert network
self.lnwatcher = LNWalletWatcher(self, network)
self.lnwatcher.start_network(network)
self.network = network
for cb in self.channel_backups.values():
self.lnwatcher.add_channel(cb.funding_outpoint.to_str(), cb.get_funding_address())
def import_channel_backup(self, encrypted):
xpub = self.wallet.get_fingerprint()
x = pw_decode_bytes(encrypted, xpub, version=PW_HASH_VERSION_LATEST)
cb = ChannelBackupStorage.from_bytes(x)
channel_id = cb.channel_id().hex()
d = self.db.get_dict("channel_backups")
if channel_id in d:
raise Exception('Channel already in wallet')
d[channel_id] = cb
self.channel_backups[bfh(channel_id)] = ChannelBackup(cb, sweep_address=self.sweep_address, lnworker=self)
self.wallet.save_db()
self.network.trigger_callback('channels_updated', self.wallet)
def remove_channel_backup(self, channel_id):
d = self.db.get_dict("channel_backups")
if channel_id.hex() not in d:
raise Exception('Channel not found')
d.pop(channel_id.hex())
self.channel_backups.pop(channel_id)
self.wallet.save_db()
self.network.trigger_callback('channels_updated', self.wallet)
@log_exceptions
async def request_force_close(self, channel_id):
cb = self.channel_backups[channel_id].cb
peer_addr = LNPeerAddr(cb.host, cb.port, cb.node_id)
transport = LNTransport(cb.privkey, peer_addr)
peer = Peer(self, cb.node_id, transport)
await self.taskgroup.spawn(peer._message_loop())
await peer.initialized
await self.taskgroup.spawn(peer.trigger_force_close(channel_id))