Browse Source

ln: cooperative close with remote peer initiating

regtest_lnd
Janus 6 years ago
committed by SomberNight
parent
commit
6fa4f2fefd
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 38
      electrum/lnbase.py
  2. 29
      electrum/lnhtlc.py
  3. 83
      electrum/lnutil.py
  4. 3
      electrum/lnworker.py

38
electrum/lnbase.py

@ -291,6 +291,7 @@ class Peer(PrintError):
self.commitment_signed = defaultdict(asyncio.Queue)
self.announcement_signatures = defaultdict(asyncio.Queue)
self.update_fail_htlc = defaultdict(asyncio.Queue)
self.closing_signed = defaultdict(asyncio.Queue)
self.localfeatures = (0x08 if request_initial_sync else 0)
self.invoices = lnworker.invoices
self.attempted_route = {}
@ -393,7 +394,7 @@ class Peer(PrintError):
self._sn = 0
return o
def process_message(self, message):
async def process_message(self, message):
message_type, payload = decode_msg(message)
#self.print_error("Received '%s'" % message_type.upper())
try:
@ -404,6 +405,9 @@ class Peer(PrintError):
# raw message is needed to check signature
if message_type=='node_announcement':
payload['raw'] = message
if asyncio.iscoroutinefunction(f):
await f(payload)
else:
f(payload)
def on_error(self, payload):
@ -451,7 +455,7 @@ class Peer(PrintError):
self.send_message(gen_msg("init", gflen=0, lflen=1, localfeatures=self.localfeatures))
# read init
msg = await self.read_message()
self.process_message(msg)
await self.process_message(msg)
self.initialized.set_result(True)
@aiosafe
@ -462,7 +466,7 @@ class Peer(PrintError):
while True:
self.ping_if_required()
msg = await self.read_message()
self.process_message(msg)
await self.process_message(msg)
def close_and_cleanup(self):
try:
@ -1076,3 +1080,31 @@ class Peer(PrintError):
if feerate_per_kvbyte is None:
feerate_per_kvbyte = FEERATE_FALLBACK_STATIC_FEE
return max(253, feerate_per_kvbyte // 4)
def on_closing_signed(self, payload):
chan_id = payload["channel_id"]
if chan_id not in self.closing_signed: raise Exception("Got unknown closing_signed")
self.closing_signed[chan_id].put_nowait(payload)
async def on_shutdown(self, payload):
# length of scripts allowed in BOLT-02
if int.from_bytes(payload['len'], 'big') not in (3+20+2, 2+20+1, 2+20, 2+32):
raise Exception('scriptpubkey length in received shutdown message invalid: ' + str(payload['len']))
chan = self.channels[payload['channel_id']]
scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
self.send_message(gen_msg('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey))
signature, fee = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'])
self.send_message(gen_msg('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature))
while chan.get_state() != 'CLOSED':
try:
closing_signed = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1)
except asyncio.TimeoutError:
pass
else:
fee = closing_signed['fee_satoshis']
signature, _ = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=fee)
self.send_message(gen_msg('closing_signed', channel_id=chan.channel_id, fee_satoshis=fee, signature=signature))
self.print_error('REMOTE PEER CLOSED CHANNEL')

29
electrum/lnhtlc.py

@ -3,9 +3,10 @@ from collections import namedtuple
import binascii
import json
from enum import Enum, auto
from typing import Optional
from .util import bfh, PrintError, bh2u
from .bitcoin import Hash
from .bitcoin import Hash, TYPE_SCRIPT
from .bitcoin import redeem_script_to_address
from .crypto import sha256
from . import ecc
@ -15,8 +16,7 @@ from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blin
from .lnutil import sign_and_get_sig_string
from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
from .lnutil import funding_output_script, extract_ctn_from_tx_and_chan
from .lnutil import LOCAL, REMOTE, SENT, RECEIVED, HTLCOwner
from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_outputs
from .transaction import Transaction
@ -730,3 +730,26 @@ class HTLCStateMachine(PrintError):
for_us,
chan.constraints.is_initiator,
htlcs=htlcs)
def make_closing_tx(self, local_script: bytes, remote_script: bytes, fee_sat: Optional[int] = None) -> (bytes, int):
if fee_sat is None:
fee_sat = self.pending_local_fee
_, outputs = make_outputs(fee_sat * 1000, True,
self.local_state.amount_msat,
self.remote_state.amount_msat,
(TYPE_SCRIPT, bh2u(local_script)),
(TYPE_SCRIPT, bh2u(remote_script)),
[], self.local_config.dust_limit_sat)
closing_tx = make_closing_tx(self.local_config.multisig_key.pubkey,
self.remote_config.multisig_key.pubkey,
self.local_config.payment_basepoint.pubkey,
self.remote_config.payment_basepoint.pubkey,
# TODO hardcoded we_are_initiator:
True, *self.funding_outpoint, self.constraints.capacity,
outputs)
der_sig = bfh(closing_tx.sign_txin(0, self.local_config.multisig_key.privkey))
sig = ecc.sig_string_from_der_sig(der_sig[:-1])
return sig, fee_sat

83
electrum/lnutil.py

@ -1,7 +1,7 @@
from enum import IntFlag
import json
from collections import namedtuple
from typing import NamedTuple
from typing import NamedTuple, List, Tuple
from .util import bfh, bh2u, inv_dict
from .crypto import sha256
@ -270,24 +270,15 @@ def make_htlc_tx_with_open_channel(chan, pcp, for_us, we_receive, amount_msat, c
htlc_tx = make_htlc_tx(cltv_expiry, inputs=htlc_tx_inputs, output=htlc_tx_output)
return htlc_tx
def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
remote_payment_pubkey, payment_basepoint,
remote_payment_basepoint, revocation_pubkey,
delayed_pubkey, to_self_delay, funding_txid,
funding_pos, funding_sat, local_amount, remote_amount,
dust_limit_sat, local_feerate, for_us, we_are_initiator,
htlcs):
def make_funding_input(local_funding_pubkey: bytes, remote_funding_pubkey: bytes,
payment_basepoint: bytes, remote_payment_basepoint: bytes, we_are_initiator: bool,
funding_pos: int, funding_txid: bytes, funding_sat: int):
pubkeys = sorted([bh2u(local_funding_pubkey), bh2u(remote_funding_pubkey)])
payments = [payment_basepoint, remote_payment_basepoint]
if not we_are_initiator:
payments.reverse()
obs = get_obscured_ctn(ctn, *payments)
locktime = (0x20 << 24) + (obs & 0xffffff)
sequence = (0x80 << 24) + (obs >> 24)
# commitment tx input
c_inputs = [{
c_input = {
'type': 'p2wsh',
'x_pubkeys': pubkeys,
'signatures': [None, None],
@ -296,19 +287,15 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
'prevout_hash': funding_txid,
'value': funding_sat,
'coinbase': False,
'sequence': sequence
}]
# commitment tx outputs
local_address = make_commitment_output_to_local_address(revocation_pubkey, to_self_delay, delayed_pubkey)
remote_address = make_commitment_output_to_remote_address(remote_payment_pubkey)
# TODO trim htlc outputs here while also considering 2nd stage htlc transactions
fee = local_feerate * overall_weight(len(htlcs))
fee = fee // 1000 * 1000
we_pay_fee = for_us == we_are_initiator
to_local_amt = local_amount - (fee if we_pay_fee else 0)
to_local = TxOutput(bitcoin.TYPE_ADDRESS, local_address, to_local_amt // 1000)
to_remote_amt = remote_amount - (fee if not we_pay_fee else 0)
to_remote = TxOutput(bitcoin.TYPE_ADDRESS, remote_address, to_remote_amt // 1000)
}
return c_input, payments
def make_outputs(fee_msat: int, we_pay_fee: bool, local_amount: int, remote_amount: int,
local_tupl, remote_tupl, htlcs: List[Tuple[bytes, int]], dust_limit_sat: int) -> Tuple[List[TxOutput], List[TxOutput]]:
to_local_amt = local_amount - (fee_msat if we_pay_fee else 0)
to_local = TxOutput(*local_tupl, to_local_amt // 1000)
to_remote_amt = remote_amount - (fee_msat if not we_pay_fee else 0)
to_remote = TxOutput(*remote_tupl, to_remote_amt // 1000)
c_outputs = [to_local, to_remote]
for script, msat_amount in htlcs:
c_outputs += [TxOutput(bitcoin.TYPE_ADDRESS,
@ -317,6 +304,37 @@ def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
# trim outputs
c_outputs_filtered = list(filter(lambda x:x[2]>= dust_limit_sat, c_outputs))
return c_outputs, c_outputs_filtered
def make_commitment(ctn, local_funding_pubkey, remote_funding_pubkey,
remote_payment_pubkey, payment_basepoint,
remote_payment_basepoint, revocation_pubkey,
delayed_pubkey, to_self_delay, funding_txid,
funding_pos, funding_sat, local_amount, remote_amount,
dust_limit_sat, local_feerate, for_us, we_are_initiator,
htlcs):
c_input, payments = make_funding_input(local_funding_pubkey, remote_funding_pubkey,
payment_basepoint, remote_payment_basepoint, we_are_initiator, funding_pos,
funding_txid, funding_sat)
obs = get_obscured_ctn(ctn, *payments)
locktime = (0x20 << 24) + (obs & 0xffffff)
sequence = (0x80 << 24) + (obs >> 24)
c_input['sequence'] = sequence
c_inputs = [c_input]
# commitment tx outputs
local_address = make_commitment_output_to_local_address(revocation_pubkey, to_self_delay, delayed_pubkey)
remote_address = make_commitment_output_to_remote_address(remote_payment_pubkey)
# TODO trim htlc outputs here while also considering 2nd stage htlc transactions
fee = local_feerate * overall_weight(len(htlcs))
fee = fee // 1000 * 1000
we_pay_fee = for_us == we_are_initiator
c_outputs, c_outputs_filtered = make_outputs(fee, we_pay_fee, local_amount, remote_amount,
(bitcoin.TYPE_ADDRESS, local_address), (bitcoin.TYPE_ADDRESS, remote_address), htlcs, dust_limit_sat)
assert sum(x[2] for x in c_outputs) <= funding_sat
# create commitment tx
@ -445,3 +463,14 @@ SENT = HTLCOwner.SENT
RECEIVED = HTLCOwner.RECEIVED
LOCAL = HTLCOwner.LOCAL
REMOTE = HTLCOwner.REMOTE
def make_closing_tx(local_funding_pubkey: bytes, remote_funding_pubkey: bytes,
payment_basepoint: bytes, remote_payment_basepoint: bytes, we_are_initiator: bool,
funding_txid: bytes, funding_pos: int, funding_sat: int, outputs: List[TxOutput]):
c_input, payments = make_funding_input(local_funding_pubkey, remote_funding_pubkey,
payment_basepoint, remote_payment_basepoint, we_are_initiator, funding_pos,
funding_txid, funding_sat)
c_input['sequence'] = 0xFFFF_FFFF
tx = Transaction.from_io([c_input], outputs, locktime=0, version=2)
tx.BIP_LI01_sort()
return tx

3
electrum/lnworker.py

@ -218,7 +218,8 @@ class LNWorker(PrintError):
def list_channels(self):
with self.lock:
return [str(x) for x in self.channels]
# we output the funding_outpoint instead of the channel_id because lnd uses channel_point (funding outpoint) to identify channels
return [(chan.funding_outpoint.to_str(), chan.get_state()) for channel_id, chan in self.channels.items()]
def close_channel(self, chan_id):
chan = self.channels[chan_id]

Loading…
Cancel
Save