Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8025.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug where state (e.g. power levels) would reset incorrectly when receiving an event from a remote server.
145 changes: 4 additions & 141 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2242,16 +2242,14 @@ async def do_auth(
at the event's position in the DAG, though occasionally (eg if the
event is an outlier), may be the auth events claimed by the remote
server.

Also NB that this function adds entries to it.
Returns:
updated context object
"""
room_version = await self.store.get_room_version_id(event.room_id)
room_version_obj = KNOWN_ROOM_VERSIONS[room_version]

try:
context = await self._update_auth_events_and_context_for_auth(
context = await self._fetch_missing_auth_events(
origin, event, context, auth_events
)
except Exception:
Expand All @@ -2273,7 +2271,7 @@ async def do_auth(

return context

async def _update_auth_events_and_context_for_auth(
async def _fetch_missing_auth_events(
self,
origin: str,
event: EventBase,
Expand All @@ -2282,14 +2280,8 @@ async def _update_auth_events_and_context_for_auth(
) -> EventContext:
"""Helper for do_auth. See there for docs.

Checks whether a given event has the expected auth events. If it
doesn't then we talk to the remote server to compare state to see if
we can come to a consensus (e.g. if one server missed some valid
state).

This attempts to resolve any potential divergence of state between
servers, but is not essential and so failures should not block further
processing of the event.
Checks and fetches if there are any auth events that we don't have,
and if so fetch them.

Args:
origin:
Expand All @@ -2304,8 +2296,6 @@ async def _update_auth_events_and_context_for_auth(
event is an outlier), may be the auth events claimed by the remote
server.

Also NB that this function adds entries to it.

Returns:
updated context
"""
Expand Down Expand Up @@ -2363,141 +2353,14 @@ async def _update_auth_events_and_context_for_auth(
"do_auth %s missing_auth: %s", event.event_id, e.event_id
)
await self._handle_new_event(origin, e, auth_events=auth)

if e.event_id in event_auth_events:
auth_events[(e.type, e.state_key)] = e
except AuthError:
pass

except Exception:
logger.exception("Failed to get auth chain")

if event.internal_metadata.is_outlier():
# XXX: given that, for an outlier, we'll be working with the
# event's *claimed* auth events rather than those we calculated:
# (a) is there any point in this test, since different_auth below will
# obviously be empty
# (b) alternatively, why don't we do it earlier?
logger.info("Skipping auth_event fetch for outlier")
return context

different_auth = event_auth_events.difference(
e.event_id for e in auth_events.values()
)

if not different_auth:
return context

logger.info(
"auth_events refers to events which are not in our calculated auth "
"chain: %s",
different_auth,
)

# XXX: currently this checks for redactions but I'm not convinced that is
# necessary?
different_events = await self.store.get_events_as_list(different_auth)

for d in different_events:
if d.room_id != event.room_id:
logger.warning(
"Event %s refers to auth_event %s which is in a different room",
event.event_id,
d.event_id,
)

# don't attempt to resolve the claimed auth events against our own
# in this case: just use our own auth events.
#
# XXX: should we reject the event in this case? It feels like we should,
# but then shouldn't we also do so if we've failed to fetch any of the
# auth events?
return context

# now we state-resolve between our own idea of the auth events, and the remote's
# idea of them.

local_state = auth_events.values()
remote_auth_events = dict(auth_events)
remote_auth_events.update({(d.type, d.state_key): d for d in different_events})
remote_state = remote_auth_events.values()

room_version = await self.store.get_room_version_id(event.room_id)
new_state = await self.state_handler.resolve_events(
room_version, (local_state, remote_state), event
)

logger.info(
"After state res: updating auth_events with new state %s",
{
(d.type, d.state_key): d.event_id
for d in new_state.values()
if auth_events.get((d.type, d.state_key)) != d
},
)

auth_events.update(new_state)

context = await self._update_context_for_auth_events(
event, context, auth_events
)

return context

async def _update_context_for_auth_events(
self, event: EventBase, context: EventContext, auth_events: StateMap[EventBase]
) -> EventContext:
"""Update the state_ids in an event context after auth event resolution,
storing the changes as a new state group.

Args:
event: The event we're handling the context for

context: initial event context

auth_events: Events to update in the event context.

Returns:
new event context
"""
# exclude the state key of the new event from the current_state in the context.
if event.is_state():
event_key = (event.type, event.state_key) # type: Optional[Tuple[str, str]]
else:
event_key = None
state_updates = {
k: a.event_id for k, a in auth_events.items() if k != event_key
}

current_state_ids = await context.get_current_state_ids()
current_state_ids = dict(current_state_ids) # type: ignore

current_state_ids.update(state_updates)

prev_state_ids = await context.get_prev_state_ids()
prev_state_ids = dict(prev_state_ids)

prev_state_ids.update({k: a.event_id for k, a in auth_events.items()})

# create a new state group as a delta from the existing one.
prev_group = context.state_group
state_group = await self.state_store.store_state_group(
event.event_id,
event.room_id,
prev_group=prev_group,
delta_ids=state_updates,
current_state_ids=current_state_ids,
)

return EventContext.with_state(
state_group=state_group,
state_group_before_event=context.state_group_before_event,
current_state_ids=current_state_ids,
prev_state_ids=prev_state_ids,
prev_group=prev_group,
delta_ids=state_updates,
)

async def construct_auth_difference(
self, local_auth: Iterable[EventBase], remote_auth: Iterable[EventBase]
) -> Dict:
Expand Down