backups: restore from closing tx, sweep to_remote
* add a method for backups to sweep to_remote * to_remote sweeping needs the payment_basepoint's private key to sign the sweep transaction * we restore the private key from our funding multisig pubkey (pubished with the closing transaction) and a static payment key secret
This commit is contained in:
@@ -56,9 +56,10 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
|
||||
ShortChannelID, map_htlcs_to_ctx_output_idxs,
|
||||
fee_for_htlc_output, offered_htlc_trim_threshold_sat,
|
||||
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, FIXED_ANCHOR_SAT,
|
||||
ChannelType, LNProtocolWarning)
|
||||
ChannelType, LNProtocolWarning, ctx_has_anchors)
|
||||
from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
|
||||
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
|
||||
from .lnsweep import create_sweeptx_their_backup_ctx
|
||||
from .lnhtlc import HTLCManager
|
||||
from .lnmsg import encode_msg, decode_msg
|
||||
from .address_synchronizer import TX_HEIGHT_LOCAL
|
||||
@@ -594,14 +595,19 @@ class ChannelBackup(AbstractChannel):
|
||||
return True
|
||||
|
||||
def create_sweeptxs_for_their_ctx(self, ctx):
|
||||
return {}
|
||||
return create_sweeptx_their_backup_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
|
||||
|
||||
def create_sweeptxs_for_our_ctx(self, ctx):
|
||||
if self.is_imported:
|
||||
return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
|
||||
else:
|
||||
# backup from op_return
|
||||
return {}
|
||||
return
|
||||
|
||||
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 None
|
||||
|
||||
def get_funding_address(self):
|
||||
return self.cb.funding_address
|
||||
|
||||
@@ -18,14 +18,15 @@ from .lnutil import (make_commitment_output_to_remote_address, make_commitment_o
|
||||
LOCAL, REMOTE, make_htlc_output_witness_script,
|
||||
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)
|
||||
from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,
|
||||
PartialTxOutput, TxOutpoint)
|
||||
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script,
|
||||
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING)
|
||||
from .transaction import (Transaction, TxInput, PartialTransaction, PartialTxInput,
|
||||
PartialTxOutput, TxOutpoint, script_GetOp, match_script_against_template)
|
||||
from .simple_config import SimpleConfig
|
||||
from .logging import get_logger, Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lnchannel import Channel, AbstractChannel
|
||||
from .lnchannel import Channel, AbstractChannel, ChannelBackup
|
||||
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
@@ -352,6 +353,69 @@ def extract_ctx_secrets(chan: 'Channel', ctx: Transaction):
|
||||
return ctn, their_pcp, is_revocation, per_commitment_secret
|
||||
|
||||
|
||||
def extract_funding_pubkeys_from_ctx(txin: TxInput) -> Tuple[bytes, bytes]:
|
||||
"""Extract the two funding pubkeys from the published commitment transaction.
|
||||
|
||||
We expect to see a witness script of: OP_2 pk1 pk2 OP_2 OP_CHECKMULTISIG"""
|
||||
elements = txin.witness_elements()
|
||||
witness_script = elements[-1]
|
||||
assert match_script_against_template(witness_script, SCRIPT_TEMPLATE_FUNDING)
|
||||
parsed_script = [x for x in script_GetOp(witness_script)]
|
||||
pubkey1 = parsed_script[1][1]
|
||||
pubkey2 = parsed_script[2][1]
|
||||
return (pubkey1, pubkey2)
|
||||
|
||||
|
||||
def create_sweeptx_their_backup_ctx(
|
||||
*, chan: 'ChannelBackup',
|
||||
ctx: Transaction,
|
||||
sweep_address: str) -> 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."""
|
||||
|
||||
if ctx_has_anchors(ctx):
|
||||
# for anchors we need to sweep to_remote
|
||||
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:
|
||||
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):
|
||||
our_payment_pubkey = candidate_basepoint
|
||||
to_remote_address = candidate_to_remote_address
|
||||
_logger.debug(f'found funding pubkey')
|
||||
break
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# we are dealing with static_remotekey which is locked to a wallet address
|
||||
return {}
|
||||
|
||||
# to_remote
|
||||
csv_delay = 1
|
||||
our_payment_privkey = ecc.ECPrivkey(our_payment_pubkey.privkey)
|
||||
output_idxs = ctx.get_output_idxs_from_address(to_remote_address)
|
||||
if output_idxs:
|
||||
output_idx = output_idxs.pop()
|
||||
prevout = ctx.txid() + ':%d' % output_idx
|
||||
sweep_tx = lambda: create_sweeptx_their_ctx_to_remote(
|
||||
sweep_address=sweep_address,
|
||||
ctx=ctx,
|
||||
output_idx=output_idx,
|
||||
our_payment_privkey=our_payment_privkey,
|
||||
config=chan.lnworker.config,
|
||||
has_anchors=True
|
||||
)
|
||||
txs[prevout] = SweepInfo(
|
||||
name='their_ctx_to_remote_backup',
|
||||
csv_delay=csv_delay,
|
||||
cltv_abs=0,
|
||||
gen_tx=sweep_tx)
|
||||
return txs
|
||||
|
||||
|
||||
def create_sweeptxs_for_their_ctx(
|
||||
*, chan: 'Channel',
|
||||
ctx: Transaction,
|
||||
|
||||
@@ -21,7 +21,7 @@ from .util import format_short_id as format_short_channel_id
|
||||
|
||||
from .crypto import sha256, pw_decode_with_version_and_mac
|
||||
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
|
||||
PartialTxOutput, opcodes, TxOutput)
|
||||
PartialTxOutput, opcodes, TxOutput, OPPushDataPubkey)
|
||||
from . import bitcoin, crypto, transaction
|
||||
from . import descriptor
|
||||
from .bitcoin import (redeem_script_to_address, address_to_script,
|
||||
@@ -57,6 +57,8 @@ FIXED_ANCHOR_SAT = 330
|
||||
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
|
||||
DUST_LIMIT_MAX = 1000
|
||||
|
||||
SCRIPT_TEMPLATE_FUNDING = [opcodes.OP_2, OPPushDataPubkey, OPPushDataPubkey, opcodes.OP_2, opcodes.OP_CHECKMULTISIG]
|
||||
|
||||
|
||||
from .json_db import StoredObject, stored_in, stored_as
|
||||
|
||||
@@ -1264,6 +1266,14 @@ def extract_ctn_from_tx_and_chan(tx: Transaction, chan: 'AbstractChannel') -> in
|
||||
funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
|
||||
fundee_payment_basepoint=fundee_conf.payment_basepoint.pubkey)
|
||||
|
||||
def ctx_has_anchors(tx: Transaction):
|
||||
output_values = [output.value for output in tx.outputs()]
|
||||
if FIXED_ANCHOR_SAT in output_values:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class LnFeatureContexts(enum.Flag):
|
||||
INIT = enum.auto()
|
||||
|
||||
Reference in New Issue
Block a user