mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-27 15:31:31 +00:00
Add extra state to distinguish shutdown negotiation from post-
negotiation, where channel should not be reestablished. See #6182
This commit is contained in:
parent
680502cfb8
commit
2adbbee5fe
3 changed files with 49 additions and 30 deletions
|
@ -77,10 +77,11 @@ class ChannelState(IntEnum):
|
||||||
# - Non-funding node: has sent the funding_signed message.
|
# - Non-funding node: has sent the funding_signed message.
|
||||||
FUNDED = 2 # Funding tx was mined (requires min_depth and tx verification)
|
FUNDED = 2 # Funding tx was mined (requires min_depth and tx verification)
|
||||||
OPEN = 3 # both parties have sent funding_locked
|
OPEN = 3 # both parties have sent funding_locked
|
||||||
CLOSING = 4 # shutdown has been sent, and closing tx is unconfirmed.
|
SHUTDOWN = 4 # shutdown has been sent.
|
||||||
FORCE_CLOSING = 5 # we force-closed, and closing tx is unconfirmed. (otherwise we remain OPEN)
|
CLOSING = 5 # closing negotiation done. we have a fully signed tx.
|
||||||
CLOSED = 6 # closing tx has been mined
|
FORCE_CLOSING = 6 # we force-closed, and closing tx is unconfirmed. (otherwise we remain OPEN)
|
||||||
REDEEMED = 7 # we can stop watching
|
CLOSED = 7 # closing tx has been mined
|
||||||
|
REDEEMED = 8 # we can stop watching
|
||||||
|
|
||||||
|
|
||||||
class PeerState(IntEnum):
|
class PeerState(IntEnum):
|
||||||
|
@ -95,18 +96,25 @@ state_transitions = [
|
||||||
(cs.PREOPENING, cs.OPENING),
|
(cs.PREOPENING, cs.OPENING),
|
||||||
(cs.OPENING, cs.FUNDED),
|
(cs.OPENING, cs.FUNDED),
|
||||||
(cs.FUNDED, cs.OPEN),
|
(cs.FUNDED, cs.OPEN),
|
||||||
(cs.OPENING, cs.CLOSING),
|
(cs.OPENING, cs.SHUTDOWN),
|
||||||
(cs.FUNDED, cs.CLOSING),
|
(cs.FUNDED, cs.SHUTDOWN),
|
||||||
(cs.OPEN, cs.CLOSING),
|
(cs.OPEN, cs.SHUTDOWN),
|
||||||
(cs.OPENING, cs.FORCE_CLOSING),
|
(cs.SHUTDOWN, cs.SHUTDOWN), # if we reestablish
|
||||||
(cs.FUNDED, cs.FORCE_CLOSING),
|
(cs.SHUTDOWN, cs.CLOSING),
|
||||||
(cs.OPEN, cs.FORCE_CLOSING),
|
(cs.CLOSING, cs.CLOSING),
|
||||||
(cs.CLOSING, cs.FORCE_CLOSING),
|
# we can force close almost any time
|
||||||
(cs.OPENING, cs.CLOSED),
|
(cs.OPENING, cs.FORCE_CLOSING),
|
||||||
(cs.FUNDED, cs.CLOSED),
|
(cs.FUNDED, cs.FORCE_CLOSING),
|
||||||
(cs.OPEN, cs.CLOSED),
|
(cs.OPEN, cs.FORCE_CLOSING),
|
||||||
(cs.CLOSING, cs.CLOSING), # if we reestablish
|
(cs.SHUTDOWN, cs.FORCE_CLOSING),
|
||||||
(cs.CLOSING, cs.CLOSED),
|
(cs.CLOSING, cs.FORCE_CLOSING),
|
||||||
|
# we can get force closed almost any time
|
||||||
|
(cs.OPENING, cs.CLOSED),
|
||||||
|
(cs.FUNDED, cs.CLOSED),
|
||||||
|
(cs.OPEN, cs.CLOSED),
|
||||||
|
(cs.SHUTDOWN, cs.CLOSED),
|
||||||
|
(cs.CLOSING, cs.CLOSED),
|
||||||
|
#
|
||||||
(cs.FORCE_CLOSING, cs.FORCE_CLOSING), # allow multiple attempts
|
(cs.FORCE_CLOSING, cs.FORCE_CLOSING), # allow multiple attempts
|
||||||
(cs.FORCE_CLOSING, cs.CLOSED),
|
(cs.FORCE_CLOSING, cs.CLOSED),
|
||||||
(cs.FORCE_CLOSING, cs.REDEEMED),
|
(cs.FORCE_CLOSING, cs.REDEEMED),
|
||||||
|
@ -174,11 +182,11 @@ class AbstractChannel(Logger, ABC):
|
||||||
return self.get_state() == ChannelState.OPEN
|
return self.get_state() == ChannelState.OPEN
|
||||||
|
|
||||||
def is_closing(self):
|
def is_closing(self):
|
||||||
return self.get_state() in [ChannelState.CLOSING, ChannelState.FORCE_CLOSING]
|
return ChannelState.SHUTDOWN <= self.get_state() <= ChannelState.FORCE_CLOSING
|
||||||
|
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
# the closing txid has been saved
|
# the closing txid has been saved
|
||||||
return self.get_state() >= ChannelState.CLOSED
|
return self.get_state() >= ChannelState.CLOSING
|
||||||
|
|
||||||
def is_redeemed(self):
|
def is_redeemed(self):
|
||||||
return self.get_state() == ChannelState.REDEEMED
|
return self.get_state() == ChannelState.REDEEMED
|
||||||
|
@ -707,8 +715,6 @@ class Channel(AbstractChannel):
|
||||||
# and the constraints are the ones imposed by their config
|
# and the constraints are the ones imposed by their config
|
||||||
ctn = self.get_next_ctn(htlc_receiver)
|
ctn = self.get_next_ctn(htlc_receiver)
|
||||||
chan_config = self.config[htlc_receiver]
|
chan_config = self.config[htlc_receiver]
|
||||||
if self.is_closed():
|
|
||||||
raise PaymentFailure('Channel closed')
|
|
||||||
if self.get_state() != ChannelState.OPEN:
|
if self.get_state() != ChannelState.OPEN:
|
||||||
raise PaymentFailure('Channel not open', self.get_state())
|
raise PaymentFailure('Channel not open', self.get_state())
|
||||||
if htlc_proposer == LOCAL:
|
if htlc_proposer == LOCAL:
|
||||||
|
@ -777,7 +783,7 @@ class Channel(AbstractChannel):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def should_try_to_reestablish_peer(self) -> bool:
|
def should_try_to_reestablish_peer(self) -> bool:
|
||||||
return ChannelState.PREOPENING < self._state < ChannelState.FORCE_CLOSING and self.peer_state == PeerState.DISCONNECTED
|
return ChannelState.PREOPENING < self._state < ChannelState.CLOSING and self.peer_state == PeerState.DISCONNECTED
|
||||||
|
|
||||||
def get_funding_address(self):
|
def get_funding_address(self):
|
||||||
script = funding_output_script(self.config[LOCAL], self.config[REMOTE])
|
script = funding_output_script(self.config[LOCAL], self.config[REMOTE])
|
||||||
|
|
|
@ -87,7 +87,7 @@ class Peer(Logger):
|
||||||
self.temp_id_to_id = {} # to forward error messages
|
self.temp_id_to_id = {} # to forward error messages
|
||||||
self.funding_created_sent = set() # for channels in PREOPENING
|
self.funding_created_sent = set() # for channels in PREOPENING
|
||||||
self.funding_signed_sent = set() # for channels in PREOPENING
|
self.funding_signed_sent = set() # for channels in PREOPENING
|
||||||
self.shutdown_received = {}
|
self.shutdown_received = {} # chan_id -> asyncio.Future()
|
||||||
self.announcement_signatures = defaultdict(asyncio.Queue)
|
self.announcement_signatures = defaultdict(asyncio.Queue)
|
||||||
self.orphan_channel_updates = OrderedDict()
|
self.orphan_channel_updates = OrderedDict()
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
|
@ -933,7 +933,8 @@ class Peer(Logger):
|
||||||
if chan.is_funded() and chan.config[LOCAL].funding_locked_received:
|
if chan.is_funded() and chan.config[LOCAL].funding_locked_received:
|
||||||
self.mark_open(chan)
|
self.mark_open(chan)
|
||||||
util.trigger_callback('channel', chan)
|
util.trigger_callback('channel', chan)
|
||||||
if chan.get_state() == ChannelState.CLOSING:
|
# if we have sent a previous shutdown, it must be retransmitted (Bolt2)
|
||||||
|
if chan.get_state() == ChannelState.SHUTDOWN:
|
||||||
await self.send_shutdown(chan)
|
await self.send_shutdown(chan)
|
||||||
|
|
||||||
def send_funding_locked(self, chan: Channel):
|
def send_funding_locked(self, chan: Channel):
|
||||||
|
@ -1429,7 +1430,7 @@ class Peer(Logger):
|
||||||
while chan.has_pending_changes(REMOTE):
|
while chan.has_pending_changes(REMOTE):
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
self.send_message('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey)
|
self.send_message('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey)
|
||||||
chan.set_state(ChannelState.CLOSING)
|
chan.set_state(ChannelState.SHUTDOWN)
|
||||||
# can fullfill or fail htlcs. cannot add htlcs, because of CLOSING state
|
# can fullfill or fail htlcs. cannot add htlcs, because of CLOSING state
|
||||||
chan.set_can_send_ctx_updates(True)
|
chan.set_can_send_ctx_updates(True)
|
||||||
|
|
||||||
|
@ -1492,12 +1493,17 @@ class Peer(Logger):
|
||||||
if not chan.constraints.is_initiator:
|
if not chan.constraints.is_initiator:
|
||||||
send_closing_signed()
|
send_closing_signed()
|
||||||
# add signatures
|
# add signatures
|
||||||
closing_tx.add_signature_to_txin(txin_idx=0,
|
closing_tx.add_signature_to_txin(
|
||||||
signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(),
|
txin_idx=0,
|
||||||
sig=bh2u(der_sig_from_sig_string(our_sig) + b'\x01'))
|
signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(),
|
||||||
closing_tx.add_signature_to_txin(txin_idx=0,
|
sig=bh2u(der_sig_from_sig_string(our_sig) + b'\x01'))
|
||||||
signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(),
|
closing_tx.add_signature_to_txin(
|
||||||
sig=bh2u(der_sig_from_sig_string(their_sig) + b'\x01'))
|
txin_idx=0,
|
||||||
|
signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(),
|
||||||
|
sig=bh2u(der_sig_from_sig_string(their_sig) + b'\x01'))
|
||||||
|
# save local transaction and set state
|
||||||
|
self.lnworker.wallet.add_transaction(closing_tx)
|
||||||
|
chan.set_state(ChannelState.CLOSING)
|
||||||
# broadcast
|
# broadcast
|
||||||
await self.network.try_broadcasting(closing_tx, 'closing')
|
await self.network.try_broadcasting(closing_tx, 'closing')
|
||||||
return closing_tx.txid()
|
return closing_tx.txid()
|
||||||
|
|
|
@ -90,13 +90,20 @@ class MockBlockchain:
|
||||||
|
|
||||||
|
|
||||||
class MockWallet:
|
class MockWallet:
|
||||||
|
|
||||||
def set_label(self, x, y):
|
def set_label(self, x, y):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def save_db(self):
|
def save_db(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def add_transaction(self, tx):
|
||||||
|
pass
|
||||||
|
|
||||||
def is_lightning_backup(self):
|
def is_lightning_backup(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
|
class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
|
||||||
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue):
|
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue):
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
|
|
Loading…
Add table
Reference in a new issue