lnpeer: Use a single queue per channel for messages that are ordered.

Forward error messages with 'temporary_channel_id' to the correct channel_id
This commit is contained in:
ThomasV 2020-02-23 17:18:45 +01:00
parent 28c5825f41
commit ac884eb3c3

View file

@ -82,15 +82,11 @@ class Peer(Logger):
self.reply_channel_range = asyncio.Queue()
# gossip uses a single queue to preserve message order
self.gossip_queue = asyncio.Queue()
# channel messsage queues
self.ordered_messages = ['accept_channel', 'funding_signed', 'funding_created', 'accept_channel', 'channel_reestablish', 'closing_signed']
self.ordered_message_queues = defaultdict(asyncio.Queue) # for messsage that are ordered
self.temp_id_to_id = {} # to forward error messages
self.shutdown_received = defaultdict(asyncio.Future)
self.channel_accepted = defaultdict(asyncio.Queue)
self.channel_reestablished = defaultdict(asyncio.Queue)
self.funding_signed = defaultdict(asyncio.Queue)
self.funding_created = defaultdict(asyncio.Queue)
self.announcement_signatures = defaultdict(asyncio.Queue)
self.closing_signed = defaultdict(asyncio.Queue)
#
self.orphan_channel_updates = OrderedDict()
self._local_changed_events = defaultdict(asyncio.Event)
self._remote_changed_events = defaultdict(asyncio.Event)
@ -147,26 +143,28 @@ class Peer(Logger):
def process_message(self, message):
message_type, payload = decode_msg(message)
try:
f = getattr(self, 'on_' + message_type)
except AttributeError:
#self.logger.info("Received '%s'" % message_type.upper(), payload)
return
# raw message is needed to check signature
if message_type in ['node_announcement', 'channel_announcement', 'channel_update']:
payload['raw'] = message
execution_result = f(payload)
if asyncio.iscoroutinefunction(f):
asyncio.ensure_future(execution_result)
if message_type in self.ordered_messages:
chan_id = payload.get('channel_id') or payload["temporary_channel_id"]
self.ordered_message_queues[chan_id].put_nowait((message_type, payload))
else:
try:
f = getattr(self, 'on_' + message_type)
except AttributeError:
#self.logger.info("Received '%s'" % message_type.upper(), payload)
return
# raw message is needed to check signature
if message_type in ['node_announcement', 'channel_announcement', 'channel_update']:
payload['raw'] = message
execution_result = f(payload)
if asyncio.iscoroutinefunction(f):
asyncio.ensure_future(execution_result)
def on_error(self, payload):
self.logger.info(f"on_error: {payload['data'].decode('ascii')}")
chan_id = payload.get("channel_id")
for d in [ self.channel_accepted, self.funding_signed,
self.funding_created, self.channel_reestablished,
self.announcement_signatures, self.closing_signed ]:
if chan_id in d:
d[chan_id].put_nowait({'error':payload['data']})
if chan_id in self.temp_id_to_id:
chan_id = self.temp_id_to_id[chan_id]
self.ordered_message_queues[chan_id].put_nowait((None, {'error':payload['data']}))
def on_ping(self, payload):
l = int.from_bytes(payload['num_pong_bytes'], 'big')
@ -175,21 +173,14 @@ class Peer(Logger):
def on_pong(self, payload):
pass
def on_accept_channel(self, payload):
temp_chan_id = payload["temporary_channel_id"]
if temp_chan_id not in self.channel_accepted:
raise Exception("Got unknown accept_channel")
self.channel_accepted[temp_chan_id].put_nowait(payload)
def on_funding_signed(self, payload):
channel_id = payload['channel_id']
if channel_id not in self.funding_signed: raise Exception("Got unknown funding_signed")
self.funding_signed[channel_id].put_nowait(payload)
def on_funding_created(self, payload):
channel_id = payload['temporary_channel_id']
if channel_id not in self.funding_created: raise Exception("Got unknown funding_created")
self.funding_created[channel_id].put_nowait(payload)
async def wait_for_message(self, expected_name, channel_id):
q = self.ordered_message_queues[channel_id]
name, payload = await asyncio.wait_for(q.get(), LN_P2P_NETWORK_TIMEOUT)
if payload.get('error'):
raise Exception('Remote peer reported error: ' + repr(payload.get('error')))
if name != expected_name:
raise Exception(f"Received unexpected '{name}'")
return payload
def on_init(self, payload):
if self._received_init:
@ -533,9 +524,7 @@ class Peer(Logger):
channel_reserve_satoshis=local_config.reserve_sat,
htlc_minimum_msat=1,
)
payload = await asyncio.wait_for(self.channel_accepted[temp_channel_id].get(), LN_P2P_NETWORK_TIMEOUT)
if payload.get('error'):
raise Exception('Remote Lightning peer reported error: ' + repr(payload.get('error')))
payload = await self.wait_for_message('accept_channel', temp_channel_id)
remote_per_commitment_point = payload['first_per_commitment_point']
funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
if funding_txn_minimum_depth <= 0:
@ -601,12 +590,13 @@ class Peer(Logger):
lnworker=self.lnworker,
initial_feerate=feerate)
sig_64, _ = chan.sign_next_commitment()
self.temp_id_to_id[temp_channel_id] = channel_id
self.send_message("funding_created",
temporary_channel_id=temp_channel_id,
funding_txid=funding_txid_bytes,
funding_output_index=funding_index,
signature=sig_64)
payload = await asyncio.wait_for(self.funding_signed[channel_id].get(), LN_P2P_NETWORK_TIMEOUT)
payload = await self.wait_for_message('funding_signed', channel_id)
self.logger.info('received funding_signed')
remote_sig = payload['signature']
chan.receive_new_commitment(remote_sig, [])
@ -666,7 +656,7 @@ class Peer(Logger):
htlc_basepoint=local_config.htlc_basepoint.pubkey,
first_per_commitment_point=per_commitment_point_first,
)
funding_created = await self.funding_created[temp_chan_id].get()
funding_created = await self.wait_for_message('funding_created', temp_chan_id)
funding_idx = int.from_bytes(funding_created['funding_output_index'], 'big')
funding_txid = bh2u(funding_created['funding_txid'][::-1])
channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_idx)
@ -715,14 +705,6 @@ class Peer(Logger):
raise Exception(f'reserve too high: {remote_reserve_sat}, funding_sat: {funding_sat}')
return remote_reserve_sat
def on_channel_reestablish(self, payload):
chan_id = payload["channel_id"]
chan = self.channels.get(chan_id)
if not chan:
self.logger.info(f"Received unknown channel_reestablish {bh2u(chan_id)} {payload}")
raise Exception('Unknown channel_reestablish')
self.channel_reestablished[chan_id].put_nowait(payload)
@log_exceptions
async def reestablish_channel(self, chan: Channel):
await self.initialized
@ -765,8 +747,7 @@ class Peer(Logger):
self.logger.info(f'channel_reestablish: sent channel_reestablish with '
f'(next_local_ctn={next_local_ctn}, '
f'oldest_unrevoked_remote_ctn={oldest_unrevoked_remote_ctn})')
msg = await self.channel_reestablished[chan_id].get()
msg = await self.wait_for_message('channel_reestablish', chan_id)
their_next_local_ctn = int.from_bytes(msg["next_local_commitment_number"], 'big')
their_oldest_unrevoked_remote_ctn = int.from_bytes(msg["next_remote_revocation_number"], 'big')
their_local_pcp = msg.get("my_current_per_commitment_point")
@ -1356,11 +1337,6 @@ class Peer(Logger):
feerate_per_kw=feerate_per_kw)
await self.await_remote(chan, remote_ctn)
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)
@log_exceptions
async def close_channel(self, chan_id: bytes):
chan = self.channels[chan_id]
@ -1404,7 +1380,7 @@ class Peer(Logger):
our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee)
self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
# FIXME: the remote SHOULD send closing_signed, but some don't.
cs_payload = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), LN_P2P_NETWORK_TIMEOUT)
cs_payload = await self.wait_for_message('closing_signed', chan.channel_id)
their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big')
their_sig = cs_payload['signature']
if our_fee == their_fee: