1
0

lightning: change derivation of funding_pubkey

Ideally, given an on-chain backup, after the remote force-closes, we should be able to spend our anchor output,
to CPFP the remote commitment tx (assuming the channel used OPTION_ANCHORS).
To spend the anchor output, we need to be able to sign with the local funding_privkey.

Previously we derived the funding_key from the channel_seed (which comes from os.urandom).
Prior to anchors, there was no use case for signing with the funding_key given a channel backup.
Now with anchors, we should make its derivation deterministic somehow, in a way so that it can
be derived given just an on-chain backup.
- one way would be to put some more data into the existing OP_RETURN
  - uses block space
  - the OP_RETURNs can be disabled via "use_recoverable_channels"
  - only the initiator can use OP_RETURNs (so what if channel is in incoming dir?)
- instead, new scheme for our funding_key:
  - we derive the funding_privkey from the lnworker root secret (derived from our bip32 seed)
  - for outgoing channels:
    - lnworker_root_secret + remote_node_id + funding_tx_nlocktime
  - for incoming channels:
    - lnworker_root_secret + remote_node_id + remote_funding_pubkey
  - a check is added to avoid reusing the same key between channels:
      not letting to user open more than one channel with the same peer in a single block
  - only the first 16 bytes of the remote_node_id are used, as the onchain backup OP_RETURNs only contain that
- as the funding_privkey cannot be derived from the channel_seed anymore, it is included in the
imported channel backups, which in turn need a new version defined
  - a wallet db upgrade is used to update already stored imported cbs
  - alternatively we could keep the imported cbs as-is, so no new version, no new funding_privkey field, as it is clearly somewhat redundant given on-chain backups can reconstruct it
    - however adding the field seems easier
      - otherwise the existing code would try to derive the funding_privkey from the channel_seed
      - also note: atm there is no field in the imported backups to distinguish anchor channels vs static-remotekey channels
This commit is contained in:
SomberNight
2025-01-14 16:14:01 +00:00
parent 8f5b395ddc
commit cba073dfd1
10 changed files with 190 additions and 24 deletions

View File

@@ -415,6 +415,10 @@ class AbstractChannel(Logger, ABC):
def get_funding_address(self) -> str:
pass
def get_funding_tx(self) -> Optional[Transaction]:
funding_txid = self.funding_outpoint.txid
return self.lnworker.lnwatcher.adb.get_transaction(funding_txid)
@abstractmethod
def get_sweep_address(self) -> str:
"""Returns a wallet address we can use to sweep coins to.
@@ -534,6 +538,12 @@ class ChannelBackup(AbstractChannel):
self.logger.warning(
f"local_payment_pubkey missing from (old-type) channel backup. "
f"You should export and re-import a newer backup.")
multisig_funding_keypair = None
if multisig_funding_secret := cb.multisig_funding_privkey:
multisig_funding_keypair = Keypair(
privkey=multisig_funding_secret,
pubkey=ecc.ECPrivkey(multisig_funding_secret).get_public_key_bytes(),
)
self.config[LOCAL] = LocalConfig.from_seed(
channel_seed=cb.channel_seed,
to_self_delay=cb.local_delay,
@@ -542,6 +552,7 @@ class ChannelBackup(AbstractChannel):
# 2. static_remotekey: to_remote sweep not necessary due to wallet address
# 3. anchor outputs: sweep to_remote by deriving the key from the funding pubkeys
static_remotekey=local_payment_pubkey,
multisig_key=multisig_funding_keypair,
# dummy values
static_payment_key=None,
dust_limit_sat=None,
@@ -594,7 +605,9 @@ class ChannelBackup(AbstractChannel):
return True
def create_sweeptxs_for_their_ctx(self, ctx):
return sweep_their_ctx_to_remote_backup(chan=self, ctx=ctx)
funding_tx = self.get_funding_tx()
assert funding_tx
return sweep_their_ctx_to_remote_backup(chan=self, ctx=ctx, funding_tx=funding_tx)
def create_sweeptxs_for_our_ctx(self, ctx):
if self.is_imported:

View File

@@ -46,7 +46,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc, ChannelConf
IncompatibleLightningFeatures, derive_payment_secret_from_payment_preimage,
ChannelType, LNProtocolWarning, validate_features, IncompatibleOrInsaneFeatures)
from .lnutil import FeeUpdate, channel_id_from_funding_tx, PaymentFeeBudget
from .lnutil import serialize_htlc_key
from .lnutil import serialize_htlc_key, Keypair
from .lntransport import LNTransport, LNTransportBase, LightningPeerConnectionClosed, HandshakeFailed
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
from .interface import GracefulDisconnect
@@ -673,7 +673,15 @@ class Peer(Logger, EventListener):
self.logger.info(f"upfront shutdown script received: {upfront_shutdown_script}")
return upfront_shutdown_script
def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner, channel_type: ChannelType) -> LocalConfig:
def make_local_config(
self,
*,
funding_sat: int,
push_msat: int,
initiator: HTLCOwner,
channel_type: ChannelType,
multisig_funding_keypair: Optional[Keypair], # if None, will get derived from channel_seed
) -> LocalConfig:
channel_seed = os.urandom(32)
initial_msat = funding_sat * 1000 - push_msat if initiator == LOCAL else push_msat
@@ -692,6 +700,14 @@ class Peer(Logger, EventListener):
static_payment_key = None
static_remotekey = bytes.fromhex(wallet.get_public_key(addr))
if multisig_funding_keypair:
for chan in self.lnworker.channels.values(): # check against all chans of lnworker, for sanity
if multisig_funding_keypair.pubkey == chan.config[LOCAL].multisig_key.pubkey:
raise Exception(
"Refusing to reuse multisig_funding_keypair for new channel. "
"Wait one block before opening another channel with this peer."
)
dust_limit_sat = bitcoin.DUST_LIMIT_P2PKH
reserve_sat = max(funding_sat // 100, dust_limit_sat)
# for comparison of defaults, see
@@ -702,6 +718,7 @@ class Peer(Logger, EventListener):
channel_seed=channel_seed,
static_remotekey=static_remotekey,
static_payment_key=static_payment_key,
multisig_key=multisig_funding_keypair,
upfront_shutdown_script=upfront_shutdown_script,
to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV,
dust_limit_sat=dust_limit_sat,
@@ -787,7 +804,21 @@ class Peer(Logger, EventListener):
'type': our_channel_type.to_bytes_minimal()
}
local_config = self.make_local_config(funding_sat, push_msat, LOCAL, our_channel_type)
if self.use_anchors():
multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_we_opened(
funding_root_secret=self.lnworker.funding_root_keypair.privkey,
remote_node_id_or_prefix=self.pubkey,
nlocktime=funding_tx.locktime,
)
else:
multisig_funding_keypair = None
local_config = self.make_local_config(
funding_sat=funding_sat,
push_msat=push_msat,
initiator=LOCAL,
channel_type=our_channel_type,
multisig_funding_keypair=multisig_funding_keypair,
)
# if it includes open_channel_tlvs: MUST include upfront_shutdown_script.
open_channel_tlvs['upfront_shutdown_script'] = {
'shutdown_scriptpubkey': local_config.upfront_shutdown_script
@@ -1019,7 +1050,21 @@ class Peer(Logger, EventListener):
if not channel_type.complies_with_features(self.features):
raise Exception("sender has sent a channel type we don't support")
local_config = self.make_local_config(funding_sat, push_msat, REMOTE, channel_type)
if self.use_anchors():
multisig_funding_keypair = lnutil.derive_multisig_funding_key_if_they_opened(
funding_root_secret=self.lnworker.funding_root_keypair.privkey,
remote_node_id_or_prefix=self.pubkey,
remote_funding_pubkey=payload['funding_pubkey'],
)
else:
multisig_funding_keypair = None
local_config = self.make_local_config(
funding_sat=funding_sat,
push_msat=push_msat,
initiator=REMOTE,
channel_type=channel_type,
multisig_funding_keypair=multisig_funding_keypair,
)
upfront_shutdown_script = self.upfront_shutdown_script_from_payload(
payload, 'open')

View File

@@ -19,7 +19,8 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
get_ordered_channel_configs, get_per_commitment_secret_from_seed,
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script,
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING)
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING, Keypair,
derive_multisig_funding_key_if_we_opened, derive_multisig_funding_key_if_they_opened)
from .transaction import (Transaction, TxInput, PartialTxInput,
PartialTxOutput, TxOutpoint, script_GetOp, match_script_against_template)
from .simple_config import SimpleConfig
@@ -483,7 +484,9 @@ def extract_funding_pubkeys_from_ctx(txin: TxInput) -> Tuple[bytes, bytes]:
def sweep_their_ctx_to_remote_backup(
*, chan: 'ChannelBackup',
ctx: Transaction) -> Optional[Dict[str, SweepInfo]]:
ctx: Transaction,
funding_tx: Transaction,
) -> Optional[Dict[str, SweepInfo]]:
txs = {} # type: Dict[str, SweepInfo]
"""If we only have a backup, and the remote force-closed with their ctx,
and anchors are enabled, we need to sweep to_remote."""
@@ -493,7 +496,7 @@ def sweep_their_ctx_to_remote_backup(
funding_pubkeys = extract_funding_pubkeys_from_ctx(ctx.inputs()[0])
_logger.debug(f'checking their ctx for funding pubkeys: {[pk.hex() for pk in funding_pubkeys]}')
# check which of the pubkey was ours
for pubkey in funding_pubkeys:
for fp_idx, pubkey in enumerate(funding_pubkeys):
candidate_basepoint = derive_payment_basepoint(chan.lnworker.static_payment_key.privkey, funding_pubkey=pubkey)
candidate_to_remote_address = make_commitment_output_to_remote_address(candidate_basepoint.pubkey, has_anchors=True)
if ctx.get_output_idxs_from_address(candidate_to_remote_address):
@@ -508,8 +511,33 @@ def sweep_their_ctx_to_remote_backup(
return {}
# remote anchor
# derive funding_privkey ("multisig_key")
# note: for imported backups, we already have this as 'local_config.multisig_key'
# but for on-chain backups, we need to derive it.
# For symmetry, we derive it now regardless of type
our_funding_pubkey = funding_pubkeys[fp_idx]
their_funding_pubkey = funding_pubkeys[1 - fp_idx]
remote_node_id = chan.node_id # for onchain backups, this is only the prefix
if chan.is_initiator():
funding_kp_cand = derive_multisig_funding_key_if_we_opened(
funding_root_secret=chan.lnworker.funding_root_keypair.privkey,
remote_node_id_or_prefix=remote_node_id,
nlocktime=funding_tx.locktime,
)
else:
funding_kp_cand = derive_multisig_funding_key_if_they_opened(
funding_root_secret=chan.lnworker.funding_root_keypair.privkey,
remote_node_id_or_prefix=remote_node_id,
remote_funding_pubkey=their_funding_pubkey,
)
assert funding_kp_cand.pubkey == our_funding_pubkey, f"funding pubkey mismatch1. {chan.is_initiator()=}"
our_ms_funding_keypair = funding_kp_cand
# sanity check funding_privkey, if we had it already (if backup is imported):
if local_config := chan.config.get(LOCAL):
if txin := sweep_ctx_anchor(ctx=ctx, multisig_key=local_config.multisig_key):
assert our_ms_funding_keypair == local_config.multisig_key, f"funding pubkey mismatch2. {chan.is_initiator()=}"
if our_ms_funding_keypair:
if txin := sweep_ctx_anchor(ctx=ctx, multisig_key=our_ms_funding_keypair):
txs[txin.prevout.to_str()] = SweepInfo(
name='remote_anchor',
csv_delay=0,
@@ -517,9 +545,6 @@ def sweep_their_ctx_to_remote_backup(
txin=txin,
txout=None,
)
else:
# fixme: onchain channel backups do not store the channel seed
pass
# to_remote
csv_delay = 1
@@ -810,7 +835,7 @@ def sweep_their_ctx_to_remote(
return txin
def sweep_ctx_anchor(*, ctx: Transaction, multisig_key)-> Optional[PartialTxInput]:
def sweep_ctx_anchor(*, ctx: Transaction, multisig_key: Keypair) -> Optional[PartialTxInput]:
from .lnutil import make_commitment_output_to_anchor_address, make_commitment_output_to_anchor_witness_script
local_funding_pubkey = multisig_key.pubkey
local_anchor_address = make_commitment_output_to_anchor_address(local_funding_pubkey)

View File

@@ -224,7 +224,8 @@ class LocalConfig(ChannelConfig):
node = BIP32Node.from_rootseed(channel_seed, xtype='standard')
keypair_generator = lambda family: generate_keypair(node, family)
kwargs['per_commitment_secret_seed'] = keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey
kwargs['multisig_key'] = keypair_generator(LnKeyFamily.MULTISIG)
if kwargs['multisig_key'] is None:
kwargs['multisig_key'] = keypair_generator(LnKeyFamily.MULTISIG)
kwargs['htlc_basepoint'] = keypair_generator(LnKeyFamily.HTLC_BASE)
kwargs['delayed_basepoint'] = keypair_generator(LnKeyFamily.DELAY_BASE)
kwargs['revocation_basepoint'] = keypair_generator(LnKeyFamily.REVOCATION_BASE)
@@ -279,8 +280,8 @@ class ChannelConstraints(StoredObject):
funding_txn_minimum_depth = attr.ib(type=int)
CHANNEL_BACKUP_VERSION_LATEST = 1
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1,)
CHANNEL_BACKUP_VERSION_LATEST = 2
KNOWN_CHANNEL_BACKUP_VERSIONS = (0, 1, 2, )
assert CHANNEL_BACKUP_VERSION_LATEST in KNOWN_CHANNEL_BACKUP_VERSIONS
@attr.s
@@ -315,6 +316,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
remote_payment_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]
multisig_funding_privkey = attr.ib(type=bytes, converter=hex_to_bytes) # type: Optional[bytes]
def to_bytes(self) -> bytes:
vds = BCDataStream()
@@ -333,6 +335,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
vds.write_string(self.host)
vds.write_uint16(self.port)
vds.write_bytes(self.local_payment_pubkey, 33)
vds.write_bytes(self.multisig_funding_privkey, 32)
return bytes(vds.input)
@staticmethod
@@ -359,6 +362,10 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
local_payment_pubkey = vds.read_bytes(33)
else:
local_payment_pubkey = None
if version >= 2:
multisig_funding_privkey = vds.read_bytes(32)
else:
multisig_funding_privkey = None
return ImportedChannelBackupStorage(
is_initiator=is_initiator,
privkey=privkey,
@@ -374,6 +381,7 @@ class ImportedChannelBackupStorage(ChannelBackupStorage):
host=host,
port=port,
local_payment_pubkey=local_payment_pubkey,
multisig_funding_privkey=multisig_funding_privkey,
)
@staticmethod
@@ -616,6 +624,54 @@ def derive_payment_basepoint(static_payment_secret: bytes, funding_pubkey: bytes
)
def derive_multisig_funding_key_if_we_opened(
*,
funding_root_secret: bytes,
remote_node_id_or_prefix: bytes,
nlocktime: int,
) -> Keypair:
from .lnworker import NODE_ID_PREFIX_LEN
assert isinstance(funding_root_secret, bytes)
assert len(funding_root_secret) == 32
assert isinstance(remote_node_id_or_prefix, bytes)
assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
assert isinstance(nlocktime, int)
nlocktime_bytes = int.to_bytes(nlocktime, length=4, byteorder="little", signed=False)
node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
funding_key = ecc.ECPrivkey(bitcoin.bip340_tagged_hash(
tag=b"electrum/ln_multisig_funding_key/we_opened",
msg=funding_root_secret + node_id_prefix + nlocktime_bytes,
))
return Keypair(
pubkey=funding_key.get_public_key_bytes(),
privkey=funding_key.get_secret_bytes(),
)
def derive_multisig_funding_key_if_they_opened(
*,
funding_root_secret: bytes,
remote_node_id_or_prefix: bytes,
remote_funding_pubkey: bytes,
) -> Keypair:
from .lnworker import NODE_ID_PREFIX_LEN
assert isinstance(funding_root_secret, bytes)
assert len(funding_root_secret) == 32
assert isinstance(remote_node_id_or_prefix, bytes)
assert len(remote_node_id_or_prefix) in (NODE_ID_PREFIX_LEN, 33)
assert isinstance(remote_funding_pubkey, bytes)
assert len(remote_funding_pubkey) == 33
node_id_prefix = remote_node_id_or_prefix[0:NODE_ID_PREFIX_LEN]
funding_key = ecc.ECPrivkey(bitcoin.bip340_tagged_hash(
tag=b"electrum/ln_multisig_funding_key/they_opened",
msg=funding_root_secret + node_id_prefix + remote_funding_pubkey,
))
return Keypair(
pubkey=funding_key.get_public_key_bytes(),
privkey=funding_key.get_secret_bytes(),
)
def make_htlc_tx_output(
amount_msat,
local_feerate,
@@ -1693,6 +1749,7 @@ class LnKeyFamily(IntEnum):
BACKUP_CIPHER = 7 | BIP32_PRIME
PAYMENT_SECRET_KEY = 8 | BIP32_PRIME
NOSTR_KEY = 9 | BIP32_PRIME
FUNDING_ROOT_KEY = 10 | BIP32_PRIME
def generate_keypair(node: BIP32Node, key_family: LnKeyFamily) -> Keypair:

View File

@@ -132,7 +132,7 @@ class LNWatcher(Logger, EventListener):
if not keep_watching:
await self.unwatch_channel(address, funding_outpoint)
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bool:
async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx: Transaction) -> bool:
raise NotImplementedError() # implemented by subclasses
async def update_channel_state(self, *, funding_outpoint: str, funding_txid: str,

View File

@@ -103,6 +103,7 @@ NUM_PEERS_TARGET = 4
# onchain channel backup data
CB_VERSION = 0
CB_MAGIC_BYTES = bytes([0, 0, 0, CB_VERSION])
NODE_ID_PREFIX_LEN = 16
FALLBACK_NODE_LIST_TESTNET = (
@@ -830,6 +831,7 @@ class LNWallet(LNWorker):
self.backup_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.BACKUP_CIPHER).privkey
self.static_payment_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_BASE)
self.payment_secret_key = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.PAYMENT_SECRET_KEY).privkey
self.funding_root_keypair = generate_keypair(BIP32Node.from_xkey(xprv), LnKeyFamily.FUNDING_ROOT_KEY)
Logger.__init__(self)
features = LNWALLET_FEATURES
if self.config.ENABLE_ANCHOR_CHANNELS:
@@ -1352,8 +1354,8 @@ class LNWallet(LNWorker):
self.remove_channel(chan.channel_id)
raise
def cb_data(self, node_id):
return CB_MAGIC_BYTES + node_id[0:16]
def cb_data(self, node_id: bytes) -> bytes:
return CB_MAGIC_BYTES + node_id[0:NODE_ID_PREFIX_LEN]
def decrypt_cb_data(self, encrypted_data, funding_address):
funding_scripthash = bytes.fromhex(address_to_scripthash(funding_address))
@@ -1373,6 +1375,8 @@ class LNWallet(LNWorker):
funding_sat: int,
node_id: bytes,
fee_est=None) -> PartialTransaction:
from .wallet import get_locktime_for_new_transaction
outputs = [PartialTxOutput.from_address_and_value(DummyAddress.CHANNEL, funding_sat)]
if self.has_recoverable_channels():
dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
@@ -1382,6 +1386,9 @@ class LNWallet(LNWorker):
outputs=outputs,
fee=fee_est)
tx.set_rbf(False)
# rm randomness from locktime, as we use the locktime as entropy for deriving the funding_privkey
# (and it would be confusing to get a collision as a consequence of the randomness)
tx.locktime = get_locktime_for_new_transaction(self.network, include_random_component=False)
return tx
def suggest_funding_amount(self, amount_to_pay, coins):
@@ -2960,6 +2967,7 @@ class LNWallet(LNWorker):
remote_revocation_pubkey = chan.config[REMOTE].revocation_basepoint.pubkey,
remote_payment_pubkey = chan.config[REMOTE].payment_basepoint.pubkey,
local_payment_pubkey=chan.config[LOCAL].payment_basepoint.pubkey,
multisig_funding_privkey=chan.config[LOCAL].multisig_key.privkey,
)
def export_channel_backup(self, channel_id):
@@ -3095,7 +3103,7 @@ class LNWallet(LNWorker):
encrypted_data = o2.scriptpubkey[2:]
data = self.decrypt_cb_data(encrypted_data, funding_address)
if data.startswith(CB_MAGIC_BYTES):
node_id_prefix = data[4:]
node_id_prefix = data[len(CB_MAGIC_BYTES):]
if node_id_prefix is None:
return
funding_txid = tx.txid()

View File

@@ -206,7 +206,11 @@ async def sweep(
return tx
def get_locktime_for_new_transaction(network: 'Network') -> int:
def get_locktime_for_new_transaction(
network: 'Network',
*,
include_random_component: bool = True,
) -> int:
# if no network or not up to date, just set locktime to zero
if not network:
return 0
@@ -225,8 +229,9 @@ def get_locktime_for_new_transaction(network: 'Network') -> int:
locktime = min(chain_height, server_height)
# sometimes pick locktime a bit further back, to help privacy
# of setups that need more time (offline/multisig/coinjoin/...)
if random.randint(0, 9) == 0:
locktime = max(0, locktime - random.randint(0, 99))
if include_random_component:
if random.randint(0, 9) == 0:
locktime = max(0, locktime - random.randint(0, 99))
locktime = max(0, locktime)
return locktime

View File

@@ -72,7 +72,7 @@ class WalletUnfinished(WalletFileException):
# seed_version is now used for the version of the wallet file
OLD_SEED_VERSION = 4 # electrum versions < 2.0
NEW_SEED_VERSION = 11 # electrum versions >= 2.0
FINAL_SEED_VERSION = 59 # electrum >= 2.7 will set this to prevent
FINAL_SEED_VERSION = 60 # electrum >= 2.7 will set this to prevent
# old versions from overwriting new format
@@ -234,6 +234,7 @@ class WalletDBUpgrader(Logger):
self._convert_version_57()
self._convert_version_58()
self._convert_version_59()
self._convert_version_60()
self.put('seed_version', FINAL_SEED_VERSION) # just to be sure
def _convert_wallet_type(self):
@@ -1146,6 +1147,15 @@ class WalletDBUpgrader(Logger):
self.data['channels'] = channels
self.data['seed_version'] = 59
def _convert_version_60(self):
if not self._is_upgrade_method_needed(59, 59):
return
cbs = self.data.get('imported_channel_backups', {})
for channel_id, cb in list(cbs.items()):
if 'multisig_funding_privkey' not in cb:
cb['multisig_funding_privkey'] = None
self.data['seed_version'] = 60
def _convert_imported(self):
if not self._is_upgrade_method_needed(0, 13):
return

View File

@@ -156,6 +156,7 @@ if [[ $1 == "backup" ]]; then
echo "alice opens channel"
bob_node=$($bob nodeid)
channel1=$($alice open_channel $bob_node 0.15 --password='')
new_blocks 1 # cannot open multiple chans with same node in same block
$alice setconfig use_recoverable_channels False
channel2=$($alice open_channel $bob_node 0.15 --password='')
new_blocks 3

View File

@@ -1086,6 +1086,7 @@ class TestLNUtil(ElectrumTestCase):
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
local_payment_pubkey=None,
multisig_funding_privkey=None,
),
decoded_cb,
)
@@ -1113,6 +1114,7 @@ class TestLNUtil(ElectrumTestCase):
remote_payment_pubkey=bfh('02a1bbc818e2e88847016a93c223eb4adef7bb8becb3709c75c556b6beb3afe7bd'),
remote_revocation_pubkey=bfh('022f28b7d8d1f05768ada3df1b0966083b8058e1e7197c57393e302ec118d7f0ae'),
local_payment_pubkey=bfh('0308d686712782a44b0cef220485ad83dae77853a5bf8501a92bb79056c9dcb25a'),
multisig_funding_privkey=None,
),
decoded_cb,
)