fix sweeping chan after local force-close using cb
scenario: - user opens a lightning channel and exports an "imported channel backup" - user closes channel via local-force-close - local ctx is published, to_local output has user's funds and they are CSV-locked for days - user restores wallet file from seed and imports channel backup - new wallet file should be able to sweep coins from to_local output (after CSV expires) This was not working previously, as the local_payment_basepoint was not included in the imported channel backups, and the code was interpreting the lack of this as the channel not having option_static_remotekey enabled. This resulted in lnutil.extract_ctn_from_tx using an incorrect funder_payment_basepoint, and lnsweep not recognising the ctx due to the garbage ctn value. The imported channel backup serialisation format is slightly changed to include the previously missing field, and its version number is bumped (0->1). We allow importing both version 0 and version 1 backups, however v0 backups cannot handle the above described scenario (they can only be used to request a remote-force-close). Note that we were/are setting the missing local_payment_basepoint to the pubkey of one of the wallet change addresses, which is bruteforceable if necessary, but I think it is not worth the complexity to add this bruteforce logic. Also note that the bruteforcing could only be done after the local-force-close was broadcast. Ideally people with existing channels and already exported v0 backups should re-export v1 backups... Not sure how to handle this. closes https://github.com/spesmilo/electrum/issues/8516
This commit is contained in:
@@ -184,6 +184,7 @@ class AbstractChannel(Logger, ABC):
|
|||||||
funding_outpoint: Outpoint
|
funding_outpoint: Outpoint
|
||||||
node_id: bytes # note that it might not be the full 33 bytes; for OCB it is only the prefix
|
node_id: bytes # note that it might not be the full 33 bytes; for OCB it is only the prefix
|
||||||
_state: ChannelState
|
_state: ChannelState
|
||||||
|
sweep_address: str
|
||||||
|
|
||||||
def set_short_channel_id(self, short_id: ShortChannelID) -> None:
|
def set_short_channel_id(self, short_id: ShortChannelID) -> None:
|
||||||
self.short_channel_id = short_id
|
self.short_channel_id = short_id
|
||||||
@@ -289,10 +290,10 @@ class AbstractChannel(Logger, ABC):
|
|||||||
if self._sweep_info.get(txid) is None:
|
if self._sweep_info.get(txid) is None:
|
||||||
our_sweep_info = self.create_sweeptxs_for_our_ctx(ctx)
|
our_sweep_info = self.create_sweeptxs_for_our_ctx(ctx)
|
||||||
their_sweep_info = self.create_sweeptxs_for_their_ctx(ctx)
|
their_sweep_info = self.create_sweeptxs_for_their_ctx(ctx)
|
||||||
if our_sweep_info is not None:
|
if our_sweep_info:
|
||||||
self._sweep_info[txid] = our_sweep_info
|
self._sweep_info[txid] = our_sweep_info
|
||||||
self.logger.info(f'we (local) force closed')
|
self.logger.info(f'we (local) force closed')
|
||||||
elif their_sweep_info is not None:
|
elif their_sweep_info:
|
||||||
self._sweep_info[txid] = their_sweep_info
|
self._sweep_info[txid] = their_sweep_info
|
||||||
self.logger.info(f'they (remote) force closed.')
|
self.logger.info(f'they (remote) force closed.')
|
||||||
else:
|
else:
|
||||||
@@ -300,6 +301,12 @@ class AbstractChannel(Logger, ABC):
|
|||||||
self.logger.info(f'not sure who closed.')
|
self.logger.info(f'not sure who closed.')
|
||||||
return self._sweep_info[txid]
|
return self._sweep_info[txid]
|
||||||
|
|
||||||
|
def maybe_sweep_revoked_htlc(self, ctx: Transaction, htlc_tx: Transaction) -> Optional[SweepInfo]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_preimage_from_htlc_txin(self, txin: TxInput) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
def update_onchain_state(self, *, funding_txid: str, funding_height: TxMinedInfo,
|
def update_onchain_state(self, *, funding_txid: str, funding_height: TxMinedInfo,
|
||||||
closing_txid: str, closing_height: TxMinedInfo, keep_watching: bool) -> None:
|
closing_txid: str, closing_height: TxMinedInfo, keep_watching: bool) -> None:
|
||||||
# note: state transitions are irreversible, but
|
# note: state transitions are irreversible, but
|
||||||
@@ -479,15 +486,21 @@ class ChannelBackup(AbstractChannel):
|
|||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
self.config = {}
|
self.config = {}
|
||||||
if self.is_imported:
|
if self.is_imported:
|
||||||
|
assert isinstance(cb, ImportedChannelBackupStorage)
|
||||||
self.init_config(cb)
|
self.init_config(cb)
|
||||||
self.unconfirmed_closing_txid = None # not a state, only for GUI
|
self.unconfirmed_closing_txid = None # not a state, only for GUI
|
||||||
|
|
||||||
def init_config(self, cb):
|
def init_config(self, cb: ImportedChannelBackupStorage):
|
||||||
|
local_payment_pubkey = cb.local_payment_pubkey
|
||||||
|
if local_payment_pubkey is None:
|
||||||
|
self.logger.warning(
|
||||||
|
f"local_payment_pubkey missing from (old-type) channel backup. "
|
||||||
|
f"You should export and re-import a newer backup.")
|
||||||
self.config[LOCAL] = LocalConfig.from_seed(
|
self.config[LOCAL] = LocalConfig.from_seed(
|
||||||
channel_seed=cb.channel_seed,
|
channel_seed=cb.channel_seed,
|
||||||
to_self_delay=cb.local_delay,
|
to_self_delay=cb.local_delay,
|
||||||
|
static_remotekey=local_payment_pubkey,
|
||||||
# dummy values
|
# dummy values
|
||||||
static_remotekey=None,
|
|
||||||
dust_limit_sat=None,
|
dust_limit_sat=None,
|
||||||
max_htlc_value_in_flight_msat=None,
|
max_htlc_value_in_flight_msat=None,
|
||||||
max_accepted_htlcs=None,
|
max_accepted_htlcs=None,
|
||||||
@@ -580,8 +593,6 @@ class ChannelBackup(AbstractChannel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def sweep_address(self) -> str:
|
def sweep_address(self) -> str:
|
||||||
# Since channel backups do not save the static_remotekey, payment_basepoint in
|
|
||||||
# their local config is not static)
|
|
||||||
return self.lnworker.wallet.get_new_sweep_address_for_channel()
|
return self.lnworker.wallet.get_new_sweep_address_for_channel()
|
||||||
|
|
||||||
def get_local_pubkey(self) -> bytes:
|
def get_local_pubkey(self) -> bytes:
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ def create_sweeptxs_for_our_ctx(
|
|||||||
to_local_witness_script = make_commitment_output_to_local_witness_script(
|
to_local_witness_script = make_commitment_output_to_local_witness_script(
|
||||||
their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey).hex()
|
their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey).hex()
|
||||||
to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
|
to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
|
||||||
|
to_remote_address = None
|
||||||
# test if this is our_ctx
|
# test if this is our_ctx
|
||||||
found_to_local = bool(ctx.get_output_idxs_from_address(to_local_address))
|
found_to_local = bool(ctx.get_output_idxs_from_address(to_local_address))
|
||||||
if not chan.is_backup():
|
if not chan.is_backup():
|
||||||
@@ -359,6 +360,7 @@ def create_sweeptxs_for_their_ctx(
|
|||||||
witness_script = make_commitment_output_to_local_witness_script(
|
witness_script = make_commitment_output_to_local_witness_script(
|
||||||
our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey).hex()
|
our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey).hex()
|
||||||
to_local_address = redeem_script_to_address('p2wsh', witness_script)
|
to_local_address = redeem_script_to_address('p2wsh', witness_script)
|
||||||
|
to_remote_address = None
|
||||||
# test if this is their ctx
|
# test if this is their ctx
|
||||||
found_to_local = bool(ctx.get_output_idxs_from_address(to_local_address))
|
found_to_local = bool(ctx.get_output_idxs_from_address(to_local_address))
|
||||||
if not chan.is_backup():
|
if not chan.is_backup():
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from .i18n import _
|
|||||||
from .lnaddr import lndecode
|
from .lnaddr import lndecode
|
||||||
from .bip32 import BIP32Node, BIP32_PRIME
|
from .bip32 import BIP32Node, BIP32_PRIME
|
||||||
from .transaction import BCDataStream, OPPushDataGeneric
|
from .transaction import BCDataStream, OPPushDataGeneric
|
||||||
|
from .logging import get_logger
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -39,6 +40,9 @@ if TYPE_CHECKING:
|
|||||||
from .lnonion import OnionRoutingFailure
|
from .lnonion import OnionRoutingFailure
|
||||||
|
|
||||||
|
|
||||||
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# defined in BOLT-03:
|
# defined in BOLT-03:
|
||||||
HTLC_TIMEOUT_WEIGHT = 663
|
HTLC_TIMEOUT_WEIGHT = 663
|
||||||
HTLC_SUCCESS_WEIGHT = 703
|
HTLC_SUCCESS_WEIGHT = 703
|
||||||
@@ -192,7 +196,7 @@ class LocalConfig(ChannelConfig):
|
|||||||
per_commitment_secret_seed = attr.ib(type=bytes, converter=hex_to_bytes)
|
per_commitment_secret_seed = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_seed(self, **kwargs):
|
def from_seed(cls, **kwargs):
|
||||||
channel_seed = kwargs['channel_seed']
|
channel_seed = kwargs['channel_seed']
|
||||||
static_remotekey = kwargs.pop('static_remotekey')
|
static_remotekey = kwargs.pop('static_remotekey')
|
||||||
node = BIP32Node.from_rootseed(channel_seed, xtype='standard')
|
node = BIP32Node.from_rootseed(channel_seed, xtype='standard')
|
||||||
@@ -202,7 +206,11 @@ class LocalConfig(ChannelConfig):
|
|||||||
kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
|
kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
|
||||||
kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
|
kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
|
||||||
kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
|
kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
|
||||||
kwargs['payment_basepoint'] = OnlyPubkeyKeypair(static_remotekey) if static_remotekey else keypair_generator(LnKeyFamily.PAYMENT_BASE)
|
if static_remotekey:
|
||||||
|
kwargs['payment_basepoint'] = OnlyPubkeyKeypair(static_remotekey)
|
||||||
|
else:
|
||||||
|
# we expect all our channels to use option_static_remotekey, so ending up here likely indicates an issue...
|
||||||
|
kwargs['payment_basepoint'] = keypair_generator(LnKeyFamily.PAYMENT_BASE)
|
||||||
return LocalConfig(**kwargs)
|
return LocalConfig(**kwargs)
|
||||||
|
|
||||||
def validate_params(self, *, funding_sat: int) -> None:
|
def validate_params(self, *, funding_sat: int) -> None:
|
||||||
@@ -236,7 +244,9 @@ class ChannelConstraints(StoredObject):
|
|||||||
funding_txn_minimum_depth = attr.ib(type=int)
|
funding_txn_minimum_depth = attr.ib(type=int)
|
||||||
|
|
||||||
|
|
||||||
CHANNEL_BACKUP_VERSION = 0
|
CHANNEL_BACKUP_VERSION_LATEST = 1
|
||||||
|
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1,)
|
||||||
|
assert CHANNEL_BACKUP_VERSION_LATEST in KNOWN_CHANNEL_BACKUP_VERSIONS
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class ChannelBackupStorage(StoredObject):
|
class ChannelBackupStorage(StoredObject):
|
||||||
@@ -255,13 +265,13 @@ class ChannelBackupStorage(StoredObject):
|
|||||||
@stored_in('onchain_channel_backups')
|
@stored_in('onchain_channel_backups')
|
||||||
@attr.s
|
@attr.s
|
||||||
class OnchainChannelBackupStorage(ChannelBackupStorage):
|
class OnchainChannelBackupStorage(ChannelBackupStorage):
|
||||||
node_id_prefix = attr.ib(type=bytes, converter=hex_to_bytes)
|
node_id_prefix = attr.ib(type=bytes, converter=hex_to_bytes) # remote node pubkey
|
||||||
|
|
||||||
@stored_in('imported_channel_backups')
|
@stored_in('imported_channel_backups')
|
||||||
@attr.s
|
@attr.s
|
||||||
class ImportedChannelBackupStorage(ChannelBackupStorage):
|
class ImportedChannelBackupStorage(ChannelBackupStorage):
|
||||||
node_id = attr.ib(type=bytes, converter=hex_to_bytes)
|
node_id = attr.ib(type=bytes, converter=hex_to_bytes) # remote node pubkey
|
||||||
privkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
privkey = attr.ib(type=bytes, converter=hex_to_bytes) # local node privkey
|
||||||
host = attr.ib(type=str)
|
host = attr.ib(type=str)
|
||||||
port = attr.ib(type=int, converter=int)
|
port = attr.ib(type=int, converter=int)
|
||||||
channel_seed = attr.ib(type=bytes, converter=hex_to_bytes)
|
channel_seed = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||||
@@ -269,10 +279,11 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
|||||||
remote_delay = attr.ib(type=int, converter=int)
|
remote_delay = attr.ib(type=int, converter=int)
|
||||||
remote_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
remote_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||||
remote_revocation_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
remote_revocation_pubkey = attr.ib(type=bytes, converter=hex_to_bytes)
|
||||||
|
local_payment_pubkey = attr.ib(type=bytes, converter=hex_to_bytes) # type: Optional[bytes]
|
||||||
|
|
||||||
def to_bytes(self) -> bytes:
|
def to_bytes(self) -> bytes:
|
||||||
vds = BCDataStream()
|
vds = BCDataStream()
|
||||||
vds.write_uint16(CHANNEL_BACKUP_VERSION)
|
vds.write_uint16(CHANNEL_BACKUP_VERSION_LATEST)
|
||||||
vds.write_boolean(self.is_initiator)
|
vds.write_boolean(self.is_initiator)
|
||||||
vds.write_bytes(self.privkey, 32)
|
vds.write_bytes(self.privkey, 32)
|
||||||
vds.write_bytes(self.channel_seed, 32)
|
vds.write_bytes(self.channel_seed, 32)
|
||||||
@@ -286,6 +297,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
|||||||
vds.write_uint16(self.remote_delay)
|
vds.write_uint16(self.remote_delay)
|
||||||
vds.write_string(self.host)
|
vds.write_string(self.host)
|
||||||
vds.write_uint16(self.port)
|
vds.write_uint16(self.port)
|
||||||
|
vds.write_bytes(self.local_payment_pubkey, 33)
|
||||||
return bytes(vds.input)
|
return bytes(vds.input)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -293,22 +305,40 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
|
|||||||
vds = BCDataStream()
|
vds = BCDataStream()
|
||||||
vds.write(s)
|
vds.write(s)
|
||||||
version = vds.read_uint16()
|
version = vds.read_uint16()
|
||||||
if version != CHANNEL_BACKUP_VERSION:
|
if version not in KNOWN_CHANNEL_BACKUP_VERSIONS:
|
||||||
raise Exception(f"unknown version for channel backup: {version}")
|
raise Exception(f"unknown version for channel backup: {version}")
|
||||||
|
is_initiator = vds.read_boolean()
|
||||||
|
privkey = vds.read_bytes(32)
|
||||||
|
channel_seed = vds.read_bytes(32)
|
||||||
|
node_id = vds.read_bytes(33)
|
||||||
|
funding_txid = vds.read_bytes(32).hex()
|
||||||
|
funding_index = vds.read_uint16()
|
||||||
|
funding_address = vds.read_string()
|
||||||
|
remote_payment_pubkey = vds.read_bytes(33)
|
||||||
|
remote_revocation_pubkey = vds.read_bytes(33)
|
||||||
|
local_delay = vds.read_uint16()
|
||||||
|
remote_delay = vds.read_uint16()
|
||||||
|
host = vds.read_string()
|
||||||
|
port = vds.read_uint16()
|
||||||
|
if version >= 1:
|
||||||
|
local_payment_pubkey = vds.read_bytes(33)
|
||||||
|
else:
|
||||||
|
local_payment_pubkey = None
|
||||||
return ImportedChannelBackupStorage(
|
return ImportedChannelBackupStorage(
|
||||||
is_initiator=vds.read_boolean(),
|
is_initiator=is_initiator,
|
||||||
privkey=vds.read_bytes(32),
|
privkey=privkey,
|
||||||
channel_seed=vds.read_bytes(32),
|
channel_seed=channel_seed,
|
||||||
node_id=vds.read_bytes(33),
|
node_id=node_id,
|
||||||
funding_txid=vds.read_bytes(32).hex(),
|
funding_txid=funding_txid,
|
||||||
funding_index=vds.read_uint16(),
|
funding_index=funding_index,
|
||||||
funding_address=vds.read_string(),
|
funding_address=funding_address,
|
||||||
remote_payment_pubkey=vds.read_bytes(33),
|
remote_payment_pubkey=remote_payment_pubkey,
|
||||||
remote_revocation_pubkey=vds.read_bytes(33),
|
remote_revocation_pubkey=remote_revocation_pubkey,
|
||||||
local_delay=vds.read_uint16(),
|
local_delay=local_delay,
|
||||||
remote_delay=vds.read_uint16(),
|
remote_delay=remote_delay,
|
||||||
host=vds.read_string(),
|
host=host,
|
||||||
port=vds.read_uint16(),
|
port=port,
|
||||||
|
local_payment_pubkey=local_payment_pubkey,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -2537,7 +2537,7 @@ class LNWallet(LNWorker):
|
|||||||
feerate_per_kvbyte = FEERATE_FALLBACK_STATIC_FEE
|
feerate_per_kvbyte = FEERATE_FALLBACK_STATIC_FEE
|
||||||
return max(FEERATE_PER_KW_MIN_RELAY_LIGHTNING, feerate_per_kvbyte // 4)
|
return max(FEERATE_PER_KW_MIN_RELAY_LIGHTNING, feerate_per_kvbyte // 4)
|
||||||
|
|
||||||
def create_channel_backup(self, channel_id):
|
def create_channel_backup(self, channel_id: bytes):
|
||||||
chan = self._channels[channel_id]
|
chan = self._channels[channel_id]
|
||||||
# do not backup old-style channels
|
# do not backup old-style channels
|
||||||
assert chan.is_static_remotekey_enabled()
|
assert chan.is_static_remotekey_enabled()
|
||||||
@@ -2556,7 +2556,9 @@ class LNWallet(LNWorker):
|
|||||||
local_delay = chan.config[LOCAL].to_self_delay,
|
local_delay = chan.config[LOCAL].to_self_delay,
|
||||||
remote_delay = chan.config[REMOTE].to_self_delay,
|
remote_delay = chan.config[REMOTE].to_self_delay,
|
||||||
remote_revocation_pubkey = chan.config[REMOTE].revocation_basepoint.pubkey,
|
remote_revocation_pubkey = chan.config[REMOTE].revocation_basepoint.pubkey,
|
||||||
remote_payment_pubkey = chan.config[REMOTE].payment_basepoint.pubkey)
|
remote_payment_pubkey = chan.config[REMOTE].payment_basepoint.pubkey,
|
||||||
|
local_payment_pubkey=chan.config[LOCAL].payment_basepoint.pubkey,
|
||||||
|
)
|
||||||
|
|
||||||
def export_channel_backup(self, channel_id):
|
def export_channel_backup(self, channel_id):
|
||||||
xpub = self.wallet.get_fingerprint()
|
xpub = self.wallet.get_fingerprint()
|
||||||
|
|||||||
@@ -921,7 +921,7 @@ class TestLNUtil(ElectrumTestCase):
|
|||||||
self.assertEqual(ChannelType(0b10000000001000000000000), channel_type)
|
self.assertEqual(ChannelType(0b10000000001000000000000), channel_type)
|
||||||
|
|
||||||
@as_testnet
|
@as_testnet
|
||||||
async def test_decode_imported_channel_backup(self):
|
async def test_decode_imported_channel_backup_v0(self):
|
||||||
encrypted_cb = "channel_backup:Adn87xcGIs9H2kfp4VpsOaNKWCHX08wBoqq37l1cLYKGlJamTeoaLEwpJA81l1BXF3GP/mRxqkY+whZG9l51G8izIY/kmMSvnh0DOiZEdwaaT/1/MwEHfsEomruFqs+iW24SFJPHbMM7f80bDtIxcLfZkKmgcKBAOlcqtq+dL3U3yH74S8BDDe2L4snaxxpCjF0JjDMBx1UR/28D+QlIi+lbvv1JMaCGXf+AF1+3jLQf8+lVI+rvFdyArws6Ocsvjf+ANQeSGUwW6Nb2xICQcMRgr1DO7bO4pgGu408eYRr2v3ayJBVtnKwSwd49gF5SDSjTDAO4CCM0uj9H5RxyzH7fqotkd9J80MBr84RiBXAeXKz+Ap8608/FVqgQ9BOcn6LhuAQdE5zXpmbQyw5jUGkPvHuseR+rzthzncy01odUceqTNg=="
|
encrypted_cb = "channel_backup:Adn87xcGIs9H2kfp4VpsOaNKWCHX08wBoqq37l1cLYKGlJamTeoaLEwpJA81l1BXF3GP/mRxqkY+whZG9l51G8izIY/kmMSvnh0DOiZEdwaaT/1/MwEHfsEomruFqs+iW24SFJPHbMM7f80bDtIxcLfZkKmgcKBAOlcqtq+dL3U3yH74S8BDDe2L4snaxxpCjF0JjDMBx1UR/28D+QlIi+lbvv1JMaCGXf+AF1+3jLQf8+lVI+rvFdyArws6Ocsvjf+ANQeSGUwW6Nb2xICQcMRgr1DO7bO4pgGu408eYRr2v3ayJBVtnKwSwd49gF5SDSjTDAO4CCM0uj9H5RxyzH7fqotkd9J80MBr84RiBXAeXKz+Ap8608/FVqgQ9BOcn6LhuAQdE5zXpmbQyw5jUGkPvHuseR+rzthzncy01odUceqTNg=="
|
||||||
config = SimpleConfig({'electrum_path': self.electrum_path})
|
config = SimpleConfig({'electrum_path': self.electrum_path})
|
||||||
d = restore_wallet_from_text("9dk", path=None, gap_limit=2, config=config)
|
d = restore_wallet_from_text("9dk", path=None, gap_limit=2, config=config)
|
||||||
@@ -942,6 +942,34 @@ class TestLNUtil(ElectrumTestCase):
|
|||||||
remote_delay=720,
|
remote_delay=720,
|
||||||
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
|
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
|
||||||
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
|
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
|
||||||
|
local_payment_pubkey=None,
|
||||||
|
),
|
||||||
|
decoded_cb,
|
||||||
|
)
|
||||||
|
|
||||||
|
@as_testnet
|
||||||
|
async def test_decode_imported_channel_backup_v1(self):
|
||||||
|
encrypted_cb = "channel_backup:AVYIedu0qSLfY2M2bBxF6dA4RAxcmobp+3h9mxALWWsv5X7hhNg0XYOKNd11FE6BJOZgZnIZ4CCAlHtLNj0/9S5GbNhbNZiQXxeHMwC1lHvtjawkwSejIJyOI52DkDFHBAGZRd4fJjaPJRHnUizWfySVR4zjd08lTinpoIeL7C7tXBW1N6YqceqV7RpeoywlBXJtFfCCuw0hnUKgq3SMlBKapkNAIgGrg15aIHNcYeENxCxr5FD1s7DIwFSECqsBVnu/Ogx2oii8BfuxqJq8vuGq4Ib/BVaSVtdb2E1wklAor/CG0p9Fg9mFWND98JD+64nz9n/knPFFyHxTXErn+ct3ZcStsLYynWKUIocgu38PtzCJ7r5ivqOw4O49fbbzdjcgMUGklPYxjuinETneCo+dCPa1uepOGTqeOYmnjVYtYZYXOlWV1F5OtNoM7MwwJjAbz84="
|
||||||
|
config = SimpleConfig({'electrum_path': self.electrum_path})
|
||||||
|
d = restore_wallet_from_text("9dk", path=None, gap_limit=2, config=config)
|
||||||
|
wallet1 = d['wallet'] # type: Standard_Wallet
|
||||||
|
decoded_cb = ImportedChannelBackupStorage.from_encrypted_str(encrypted_cb, password=wallet1.get_fingerprint())
|
||||||
|
self.assertEqual(
|
||||||
|
ImportedChannelBackupStorage(
|
||||||
|
funding_txid='97767fdefef3152319363b772914d71e5eb70e793b835c13dce20037d3ac13fe',
|
||||||
|
funding_index=1,
|
||||||
|
funding_address='tb1qfsxllwl2edccpar9jas9wsxd4vhcewlxqwmn0w27kurkme3jvkdqn4msdp',
|
||||||
|
is_initiator=True,
|
||||||
|
node_id=bfh('02bf82e22f99dcd7ac1de4aad5152ce48f0694c46ec582567f379e0adbf81e2d0f'),
|
||||||
|
privkey=bfh('7e634853dc47f0bc2f2e0d1054b302fcb414371ddbd889f29ba8aa4e8b62c772'),
|
||||||
|
host='195.201.207.61',
|
||||||
|
port=9739,
|
||||||
|
channel_seed=bfh('ce9bad44ff8521d9f57fd202ad7cdedceb934f0056f42d0f3aa7a576b505332a'),
|
||||||
|
local_delay=1008,
|
||||||
|
remote_delay=720,
|
||||||
|
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
|
||||||
|
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
|
||||||
|
local_payment_pubkey=bfh('0308d686712782a44b0cef220485ad83dae77853a5bf8501a92bb79056c9dcb25a'),
|
||||||
),
|
),
|
||||||
decoded_cb,
|
decoded_cb,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user