mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-01 17:55:20 +00:00
ln: close channels
This commit is contained in:
parent
83c60441cf
commit
7f206d6e4c
2 changed files with 53 additions and 17 deletions
|
@ -278,8 +278,8 @@ ChannelConfig = namedtuple("ChannelConfig", [
|
||||||
"payment_basepoint", "multisig_key", "htlc_basepoint", "delayed_basepoint", "revocation_basepoint",
|
"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"])
|
"to_self_delay", "dust_limit_sat", "max_htlc_value_in_flight_msat", "max_accepted_htlcs"])
|
||||||
OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"])
|
OnlyPubkeyKeypair = namedtuple("OnlyPubkeyKeypair", ["pubkey"])
|
||||||
RemoteState = namedtuple("RemoteState", ["ctn", "next_per_commitment_point", "amount_msat", "revocation_store", "last_per_commitment_point", "next_htlc_id"])
|
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"])
|
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"])
|
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"])
|
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
|
synced = server_height != 0 and network.is_up_to_date() and local_height >= server_height
|
||||||
return synced
|
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):
|
class Peer(PrintError):
|
||||||
|
|
||||||
def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False):
|
def __init__(self, lnworker, host, port, pubkey, request_initial_sync=False):
|
||||||
|
@ -787,7 +791,7 @@ class Peer(PrintError):
|
||||||
self.process_message(msg)
|
self.process_message(msg)
|
||||||
self.initialized.set_result(True)
|
self.initialized.set_result(True)
|
||||||
# reestablish channels
|
# 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
|
# loop
|
||||||
while True:
|
while True:
|
||||||
self.ping_if_required()
|
self.ping_if_required()
|
||||||
|
@ -871,8 +875,8 @@ class Peer(PrintError):
|
||||||
self.print_error('remote delay', remote_config.to_self_delay)
|
self.print_error('remote delay', remote_config.to_self_delay)
|
||||||
self.print_error('funding_txn_minimum_depth', funding_txn_minimum_depth)
|
self.print_error('funding_txn_minimum_depth', funding_txn_minimum_depth)
|
||||||
# create funding tx
|
# create funding tx
|
||||||
pubkeys = sorted([bh2u(local_config.multisig_key.pubkey), bh2u(remote_config.multisig_key.pubkey)])
|
redeem_script = funding_output_script(local_config, remote_config)
|
||||||
redeem_script = transaction.multisig_script(pubkeys, 2)
|
print("REDEEM SCRIPT", redeem_script)
|
||||||
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
|
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
|
||||||
funding_output = (bitcoin.TYPE_ADDRESS, funding_address, funding_sat)
|
funding_output = (bitcoin.TYPE_ADDRESS, funding_address, funding_sat)
|
||||||
funding_tx = wallet.mktx([funding_output], password, config, 1000)
|
funding_tx = wallet.mktx([funding_output], password, config, 1000)
|
||||||
|
@ -894,7 +898,7 @@ class Peer(PrintError):
|
||||||
remote_state=RemoteState(
|
remote_state=RemoteState(
|
||||||
ctn = -1,
|
ctn = -1,
|
||||||
next_per_commitment_point=remote_per_commitment_point,
|
next_per_commitment_point=remote_per_commitment_point,
|
||||||
last_per_commitment_point=None,
|
current_per_commitment_point=None,
|
||||||
amount_msat=remote_amount,
|
amount_msat=remote_amount,
|
||||||
revocation_store=their_revocation_store,
|
revocation_store=their_revocation_store,
|
||||||
next_htlc_id = 0
|
next_htlc_id = 0
|
||||||
|
@ -905,7 +909,8 @@ class Peer(PrintError):
|
||||||
amount_msat=local_amount,
|
amount_msat=local_amount,
|
||||||
next_htlc_id = 0,
|
next_htlc_id = 0,
|
||||||
funding_locked_received = False,
|
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)
|
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
|
# broadcast funding tx
|
||||||
success, _txid = self.network.broadcast_transaction(funding_tx)
|
success, _txid = self.network.broadcast_transaction(funding_tx)
|
||||||
assert success, success
|
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):
|
def reestablish_channel(self, chan):
|
||||||
self.channel_state[chan.channel_id] = 'REESTABLISHING'
|
self.channel_state[chan.channel_id] = 'REESTABLISHING'
|
||||||
|
@ -949,7 +954,7 @@ class Peer(PrintError):
|
||||||
if local_ctn != chan.local_state.ctn:
|
if local_ctn != chan.local_state.ctn:
|
||||||
raise Exception("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn))
|
raise Exception("expected local ctn {}, got {}".format(chan.local_state.ctn, local_ctn))
|
||||||
their = channel_reestablish_msg["my_current_per_commitment_point"]
|
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:
|
if our is None:
|
||||||
our = chan.remote_state.next_per_commitment_point
|
our = chan.remote_state.next_per_commitment_point
|
||||||
if our != their:
|
if our != their:
|
||||||
|
@ -976,7 +981,7 @@ class Peer(PrintError):
|
||||||
if not chan.local_state.funding_locked_received:
|
if not chan.local_state.funding_locked_received:
|
||||||
our_next_point = chan.remote_state.next_per_commitment_point
|
our_next_point = chan.remote_state.next_per_commitment_point
|
||||||
their_next_point = payload["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)
|
new_local_state = chan.local_state._replace(funding_locked_received = True)
|
||||||
chan = chan._replace(remote_state=new_remote_state, local_state=new_local_state)
|
chan = chan._replace(remote_state=new_remote_state, local_state=new_local_state)
|
||||||
self.lnworker.save_channel(chan)
|
self.lnworker.save_channel(chan)
|
||||||
|
@ -1257,6 +1262,8 @@ class Peer(PrintError):
|
||||||
def on_commitment_signed(self, payload):
|
def on_commitment_signed(self, payload):
|
||||||
self.print_error("commitment_signed", payload)
|
self.print_error("commitment_signed", payload)
|
||||||
channel_id = payload['channel_id']
|
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)
|
self.commitment_signed[channel_id].put_nowait(payload)
|
||||||
|
|
||||||
def on_update_fulfill_htlc(self, payload):
|
def on_update_fulfill_htlc(self, payload):
|
||||||
|
|
|
@ -7,18 +7,19 @@ import threading
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from . import constants
|
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 .util import bh2u, bfh, PrintError
|
||||||
from .constants import set_testnet, set_simnet
|
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 .lightning_payencode.lnaddr import lnencode, LnAddr, lndecode
|
||||||
from . import lnrouter
|
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")
|
is_key = lambda k: k.endswith("_basepoint") or k.endswith("_key")
|
||||||
|
|
||||||
def maybeDecode(k, v):
|
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 binascii.unhexlify(v)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
@ -110,7 +111,15 @@ class LNWorker(PrintError):
|
||||||
|
|
||||||
def add_peer(self, host, port, pubkey):
|
def add_peer(self, host, port, pubkey):
|
||||||
node_id = bfh(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))
|
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.network.futures.append(asyncio.run_coroutine_threadsafe(peer.main_loop(), asyncio.get_event_loop()))
|
||||||
self.peers[node_id] = peer
|
self.peers[node_id] = peer
|
||||||
|
@ -122,8 +131,8 @@ class LNWorker(PrintError):
|
||||||
self.channels[openchannel.channel_id] = openchannel
|
self.channels[openchannel.channel_id] = openchannel
|
||||||
for node_id, peer in self.peers.items():
|
for node_id, peer in self.peers.items():
|
||||||
peer.channels = self.channels_for_peer(node_id)
|
peer.channels = self.channels_for_peer(node_id)
|
||||||
if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.last_per_commitment_point:
|
if openchannel.remote_state.next_per_commitment_point == openchannel.remote_state.current_per_commitment_point:
|
||||||
raise Exception("Tried to save channel with next_point == last_point, this should not happen")
|
raise Exception("Tried to save channel with next_point == current_point, this should not happen")
|
||||||
dumped = serialize_channels(self.channels)
|
dumped = serialize_channels(self.channels)
|
||||||
self.wallet.storage.put("channels", dumped)
|
self.wallet.storage.put("channels", dumped)
|
||||||
self.wallet.storage.write()
|
self.wallet.storage.write()
|
||||||
|
@ -213,3 +222,23 @@ class LNWorker(PrintError):
|
||||||
|
|
||||||
def list_channels(self):
|
def list_channels(self):
|
||||||
return serialize_channels(self.channels)
|
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
|
||||||
|
|
Loading…
Add table
Reference in a new issue