electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 7f206d6e4c00a1221f360158552500dc1fc2d79d
parent 83c60441cf3f1272c825ce5df8c6e7ec01c6c78e
Author: Janus <ysangkok@gmail.com>
Date:   Wed, 20 Jun 2018 15:46:22 +0200

ln: close channels

Diffstat:
Mlib/lnbase.py | 27+++++++++++++++++----------
Mlib/lnworker.py | 43++++++++++++++++++++++++++++++++++++-------
2 files changed, 53 insertions(+), 17 deletions(-)

diff --git a/lib/lnbase.py b/lib/lnbase.py @@ -278,8 +278,8 @@ ChannelConfig = namedtuple("ChannelConfig", [ "payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint", "to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"]) OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"]) -RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "last_per_commitment_point", "next_htlc_id"]) -LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced"]) +RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "current_per_commitment_point", "next_htlc_id"]) +LocalState = namedtuple("LocalState", ["ctn", "per_commitment_secret_seed", "amount_msat", "next_htlc_id", "funding_locked_received", "was_announced", "current_commitment_signature"]) ChannelConstraints = namedtuple("ChannelConstraints", ["feerate", "capacity", "is_initiator", "funding_txn_minimum_depth"]) OpenChannel = namedtuple("OpenChannel", ["channel_id", "short_channel_id", "funding_outpoint", "local_config", "remote_config", "remote_state", "local_state", "constraints", "node_id"]) @@ -578,6 +578,10 @@ def is_synced(network): synced = server_height != 0 and network.is_up_to_date() and local_height >= server_height return synced +def funding_output_script(local_config, remote_config): + pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) + return transaction.multisig_script(pubkeys, 2) + class Peer(PrintError): def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False): @@ -787,7 +791,7 @@ class Peer(PrintError): self.process_message(msg) self.initialized.set_result(True) # reestablish channels - [self.reestablish_channel(c) for c in self.channels.values()] + [self.reestablish_channel(c) for c in self.channels.values() if self.lnworker.channel_state[c.channel_id] != "CLOSED"] # loop while True: self.ping_if_required() @@ -871,8 +875,8 @@ class Peer(PrintError): self.print_error('remote delay', remote_config.to_self_delay) self.print_error('funding_txn_minimum_depth', funding_txn_minimum_depth) # create funding tx - pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)]) - redeem_script = transaction.multisig_script(pubkeys, 2) + redeem_script = funding_output_script(local_config, remote_config) + print("REDEEM SCRIPT", redeem_script) funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script) funding_output = (bitcoin.TYPE_ADDRESS, funding_address, funding_sat) funding_tx = wallet.mktx([funding_output], password, config, 1000) @@ -894,7 +898,7 @@ class Peer(PrintError): remote_state=RemoteState( ctn = -1, next_per_commitment_point=remote_per_commitment_point, - last_per_commitment_point=None, + current_per_commitment_point=None, amount_msat=remote_amount, revocation_store=their_revocation_store, next_htlc_id = 0 @@ -905,7 +909,8 @@ class Peer(PrintError): amount_msat=local_amount, next_htlc_id = 0, funding_locked_received = False, - was_announced = False + was_announced = False, + current_commitment_signature = None ), constraints=ChannelConstraints(capacity=funding_sat, feerate=local_feerate, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth) ) @@ -923,7 +928,7 @@ class Peer(PrintError): # broadcast funding tx success, _txid = self.network.broadcast_transaction(funding_tx) assert success, success - return chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0)) + return chan._replace(remote_state=chan.remote_state._replace(ctn=0),local_state=chan.local_state._replace(ctn=0, current_commitment_signature=remote_sig)) def reestablish_channel(self, chan): self.channel_state[chan.channel_id] = 'REESTABLISHING' @@ -949,7 +954,7 @@ class Peer(PrintError): if local_ctn != chan.local_state.ctn: raise Exception("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn)) their = channel_reestablish_msg["my_current_per_commitment_point"] - our = chan.remote_state.last_per_commitment_point + our = chan.remote_state.current_per_commitment_point if our is None: our = chan.remote_state.next_per_commitment_point if our != their: @@ -976,7 +981,7 @@ class Peer(PrintError): if not chan.local_state.funding_locked_received: our_next_point = chan.remote_state.next_per_commitment_point their_next_point = payload["next_per_commitment_point"] - new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, last_per_commitment_point=our_next_point) + new_remote_state = chan.remote_state._replace(next_per_commitment_point=their_next_point, current_per_commitment_point=our_next_point) new_local_state = chan.local_state._replace(funding_locked_received = True) chan = chan._replace(remote_state=new_remote_state, local_state=new_local_state) self.lnworker.save_channel(chan) @@ -1257,6 +1262,8 @@ class Peer(PrintError): def on_commitment_signed(self, payload): self.print_error("commitment_signed", payload) channel_id = payload['channel_id'] + chan = self.channels[channel_id] + self.save_channel(chan._replace(local_state=chan.local_state._replace(current_commitment_signature=payload['signature']))) self.commitment_signed[channel_id].put_nowait(payload) def on_update_fulfill_htlc(self, payload): diff --git a/lib/lnworker.py b/lib/lnworker.py @@ -7,18 +7,19 @@ import threading from collections import defaultdict from . import constants -from .bitcoin import sha256, COIN +from .bitcoin import sha256, COIN, address_to_scripthash, redeem_script_to_address from .util import bh2u, bfh, PrintError from .constants import set_testnet, set_simnet -from .lnbase import Peer, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore, calc_short_channel_id, privkey_to_pubkey +from .lnbase import Peer, Outpoint, ChannelConfig, LocalState, RemoteState, Keypair, OnlyPubkeyKeypair, OpenChannel, ChannelConstraints, RevocationStore, calc_short_channel_id, privkey_to_pubkey, funding_output_script from .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode from . import lnrouter -from .ecc import ECPrivkey +from .ecc import ECPrivkey, CURVE_ORDER, der_sig_from_sig_string +from .transaction import Transaction is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key") def maybeDecode(k, v): - if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "last_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed"] and v is not None: + if k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature"] and v is not None: return binascii.unhexlify(v) return v @@ -110,7 +111,15 @@ class LNWorker(PrintError): def add_peer(self, host, port, pubkey): node_id = bfh(pubkey) - channels = self.channels_for_peer(node_id) + for chan_id in self.channels_for_peer(node_id): + chan = self.channels[chan_id] + script = funding_output_script(chan.local_config, chan.remote_config) + funding_address = redeem_script_to_address('p2wsh', script) + scripthash = address_to_scripthash(funding_address) + utxos = self.network.listunspent_for_scripthash(scripthash) + outpoints = [Outpoint(x["tx_hash"], x["tx_pos"]) for x in utxos] + if chan.funding_outpoint not in outpoints: + self.channel_state[chan.channel_id] = "CLOSED" peer = Peer(self, host, int(port), node_id, request_initial_sync=self.config.get("request_initial_sync", True)) self.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop())) self.peers[node_id] = peer @@ -122,8 +131,8 @@ class LNWorker(PrintError): self.channels[openchannel.channel_id] = openchannel for node_id, peer in self.peers.items(): peer.channels = self.channels_for_peer(node_id) - if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.last_per_commitment_point: - raise Exception("Tried to save channel with next_point == last_point, this should not happen") + if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point: + raise Exception("Tried to save channel with next_point == current_point, this should not happen") dumped = serialize_channels(self.channels) self.wallet.storage.put("channels", dumped) self.wallet.storage.write() @@ -213,3 +222,23 @@ class LNWorker(PrintError): def list_channels(self): return serialize_channels(self.channels) + + def close_channel(self, chan_id): + from .lnhtlc import HTLCStateMachine + chan = self.channels[chan_id] + # local_commitment always gives back the next expected local_commitment, + # but in this case, we want the current one. So substract one ctn number + tx = HTLCStateMachine(chan._replace(local_state=chan.local_state._replace(ctn=chan.local_state.ctn - 1))).local_commitment + tx.sign({bh2u(chan.local_config.multisig_key.pubkey): (chan.local_config.multisig_key.privkey, True)}) + remote_sig = chan.local_state.current_commitment_signature + remote_sig = der_sig_from_sig_string(remote_sig) + b"\x01" + none_idx = tx._inputs[0]["signatures"].index(None) + Transaction.add_signature_to_txin(tx._inputs[0], none_idx, bh2u(remote_sig)) + tx.raw = None # trigger reserialization + assert tx.is_complete() + suc, msg = self.network.broadcast_transaction(tx) + self.channel_state[chan_id] = "CLOSED" + self.on_channels_updated() + if "transaction already in block chain" in msg: + return + assert suc, msg