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,
|
ShortChannelID, map_htlcs_to_ctx_output_idxs,
|
||||||
fee_for_htlc_output, offered_htlc_trim_threshold_sat,
|
fee_for_htlc_output, offered_htlc_trim_threshold_sat,
|
||||||
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, FIXED_ANCHOR_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_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
|
||||||
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
|
from .lnsweep import create_sweeptx_for_their_revoked_htlc, SweepInfo
|
||||||
|
from .lnsweep import create_sweeptx_their_backup_ctx
|
||||||
from .lnhtlc import HTLCManager
|
from .lnhtlc import HTLCManager
|
||||||
from .lnmsg import encode_msg, decode_msg
|
from .lnmsg import encode_msg, decode_msg
|
||||||
from .address_synchronizer import TX_HEIGHT_LOCAL
|
from .address_synchronizer import TX_HEIGHT_LOCAL
|
||||||
@@ -594,14 +595,19 @@ class ChannelBackup(AbstractChannel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def create_sweeptxs_for_their_ctx(self, ctx):
|
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):
|
def create_sweeptxs_for_our_ctx(self, ctx):
|
||||||
if self.is_imported:
|
if self.is_imported:
|
||||||
return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
|
return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address())
|
||||||
else:
|
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):
|
def get_funding_address(self):
|
||||||
return self.cb.funding_address
|
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,
|
LOCAL, REMOTE, make_htlc_output_witness_script,
|
||||||
get_ordered_channel_configs, get_per_commitment_secret_from_seed,
|
get_ordered_channel_configs, get_per_commitment_secret_from_seed,
|
||||||
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
|
RevocationStore, extract_ctn_from_tx_and_chan, UnableToDeriveSecret, SENT, RECEIVED,
|
||||||
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script)
|
map_htlcs_to_ctx_output_idxs, Direction, make_commitment_output_to_remote_witness_script,
|
||||||
from .transaction import (Transaction, TxOutput, PartialTransaction, PartialTxInput,
|
derive_payment_basepoint, ctx_has_anchors, SCRIPT_TEMPLATE_FUNDING)
|
||||||
PartialTxOutput, TxOutpoint)
|
from .transaction import (Transaction, TxInput, PartialTransaction, PartialTxInput,
|
||||||
|
PartialTxOutput, TxOutpoint, script_GetOp, match_script_against_template)
|
||||||
from .simple_config import SimpleConfig
|
from .simple_config import SimpleConfig
|
||||||
from .logging import get_logger, Logger
|
from .logging import get_logger, Logger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lnchannel import Channel, AbstractChannel
|
from .lnchannel import Channel, AbstractChannel, ChannelBackup
|
||||||
|
|
||||||
|
|
||||||
_logger = get_logger(__name__)
|
_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
|
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(
|
def create_sweeptxs_for_their_ctx(
|
||||||
*, chan: 'Channel',
|
*, chan: 'Channel',
|
||||||
ctx: Transaction,
|
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 .crypto import sha256, pw_decode_with_version_and_mac
|
||||||
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
|
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
|
||||||
PartialTxOutput, opcodes, TxOutput)
|
PartialTxOutput, opcodes, TxOutput, OPPushDataPubkey)
|
||||||
from . import bitcoin, crypto, transaction
|
from . import bitcoin, crypto, transaction
|
||||||
from . import descriptor
|
from . import descriptor
|
||||||
from .bitcoin import (redeem_script_to_address, address_to_script,
|
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
|
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
|
||||||
DUST_LIMIT_MAX = 1000
|
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
|
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,
|
funder_payment_basepoint=funder_conf.payment_basepoint.pubkey,
|
||||||
fundee_payment_basepoint=fundee_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):
|
class LnFeatureContexts(enum.Flag):
|
||||||
INIT = enum.auto()
|
INIT = enum.auto()
|
||||||
|
|||||||
Reference in New Issue
Block a user