Repository: matrix-org/synapse

Issue:
Failed to backfill from <server> because UnboundLocalError("local variable 'result' referenced before assignment")
### Description

Python error when backfilling fails.

### Steps to reproduce

The final stacktrace is:
```
UnboundLocalError: local variable 'result' referenced before assignment
  File "synapse/handlers/federation.py", line 1194, in try_backfill
    await self.backfill(
  File "synapse/handlers/federation.py", line 959, in backfill
    state = await self._get_state_for_room(
  File "synapse/handlers/federation.py", line 590, in _get_state_for_room
    await self._get_events_and_persist(
  File "synapse/handlers/federation.py", line 1357, in _get_events_and_persist
    await self._auth_and_persist_events(
  File "synapse/handlers/federation.py", line 2104, in _auth_and_persist_events
    await self.persist_events_and_notify(
  File "synapse/handlers/federation.py", line 3066, in persist_events_and_notify
    return result["max_stream_id"]
```

I've seen different errors further along though:
* `SynapseError: 401: Failed to find any key to satisfy VerifyJsonRequest(server=sugar-free-jazz.com, key_ids=['ed25519:a_OHOQ'], min_valid=1622756012022)`
* `SynapseError: 401: Invalid signature for server pc.koesters.xyz:59003 with key ed25519:key1: Unable to verify signature for pc.koesters.xyz:59003: <class 'nacl.exceptions.BadSignatureError'> Signature was forged or corrupt`

Possibly only happens in worker setups?

### Version information

- **Homeserver**: fosdem.org (& others, all running with workers)
- **Version**: 1.35.0 & 1.35.1


Discussion:
https://sentry.matrix.org/sentry/synapse-modular/issues/222690/events/ & https://sentry.matrix.org/sentry/synapse-modular/issues/222595/events/ FWIW

Resolving patch:
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -22,6 +22,7 @@
 from http import HTTPStatus
 from typing import (
     TYPE_CHECKING,
+    Collection,
     Dict,
     Iterable,
     List,
@@ -1364,11 +1365,12 @@ async def get_event(event_id: str):
 
             event_infos.append(_NewEventInfo(event, None, auth))
 
-        await self._auth_and_persist_events(
-            destination,
-            room_id,
-            event_infos,
-        )
+        if event_infos:
+            await self._auth_and_persist_events(
+                destination,
+                room_id,
+                event_infos,
+            )
 
     def _sanity_check_event(self, ev: EventBase) -> None:
         """
@@ -2077,7 +2079,7 @@ async def _auth_and_persist_events(
         self,
         origin: str,
         room_id: str,
-        event_infos: Iterable[_NewEventInfo],
+        event_infos: Collection[_NewEventInfo],
         backfilled: bool = False,
     ) -> None:
         """Creates the appropriate contexts and persists events. The events
@@ -2088,6 +2090,9 @@ async def _auth_and_persist_events(
         Notifies about the events where appropriate.
         """
 
+        if not event_infos:
+            return
+
         async def prep(ev_info: _NewEventInfo):
             event = ev_info.event
             with nested_logging_context(suffix=event.event_id):
@@ -2216,13 +2221,14 @@ async def _persist_auth_tree(
                     raise
                 events_to_context[e.event_id].rejected = RejectedReason.AUTH_ERROR
 
-        await self.persist_events_and_notify(
-            room_id,
-            [
-                (e, events_to_context[e.event_id])
-                for e in itertools.chain(auth_events, state)
-            ],
-        )
+        if auth_events or state:
+            await self.persist_events_and_notify(
+                room_id,
+                [
+                    (e, events_to_context[e.event_id])
+                    for e in itertools.chain(auth_events, state)
+                ],
+            )
 
         new_event_context = await self.state_handler.compute_event_context(
             event, old_state=state
@@ -3061,7 +3067,13 @@ async def persist_events_and_notify(
                 the same room.
             backfilled: Whether these events are a result of
                 backfilling or not
+
+        Returns:
+            The stream ID after which all events have been persisted.
         """
+        if not event_and_contexts:
+            return self.store.get_current_events_token()
+
         instance = self.config.worker.events_shard_config.get_instance(room_id)
         if instance != self._instance_name:
             # Limit the number of events sent over replication. We choose 200


Test patch: