Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
bdfeb90
Client side API's should still use stripped events with MSC4311
MadLittleMods Apr 23, 2026
43a11f5
Add changelog
MadLittleMods Apr 23, 2026
7f25120
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods May 1, 2026
6026aaa
Non-working: Use full PDU's for `invite_room_state` in federation
MadLittleMods May 2, 2026
e0eb224
Iteration that uses current state
MadLittleMods May 2, 2026
3464ec8
Use `get_stripped_room_state_ids_from_event_context`
MadLittleMods May 2, 2026
f96c008
Invite event should be stripped when included in `invite_state`
MadLittleMods May 4, 2026
a088aa8
Sanitize `invite_room_state` received over federation
MadLittleMods May 4, 2026
336b686
Fix test lints
MadLittleMods May 4, 2026
22f4f20
Updated comment
MadLittleMods May 4, 2026
cce5dc7
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods May 21, 2026
7e379e7
Make the TODO more obvious
MadLittleMods May 21, 2026
92d0d8b
Placeholder arg docstring
MadLittleMods May 21, 2026
76b4905
Better logic/reasoning
MadLittleMods May 22, 2026
5103f1b
Better comment flow
MadLittleMods May 22, 2026
5c1f4ca
Use `FIXME(MSC4311)` prefix
MadLittleMods May 22, 2026
374c4c5
Mark down create event after we verify it's valid
MadLittleMods May 22, 2026
ffe5c4b
Small comment to explain (we also explain above)
MadLittleMods May 22, 2026
2b900b4
Better `invite_room_state` scrutiny
MadLittleMods May 22, 2026
5e12cb8
Fix `events` typo
MadLittleMods May 22, 2026
ff533df
Event ID context
MadLittleMods May 22, 2026
de78d9e
Simplify logic to just use `hs.config.api.room_prejoin_state` instead…
MadLittleMods May 22, 2026
4598a43
`_parse_stripped_room_state`
MadLittleMods May 23, 2026
fbff685
`_minimal_parse_stripped_room_state`
MadLittleMods May 23, 2026
75a53ef
Strip the knock itself we add down `/sync`
MadLittleMods May 23, 2026
178dd89
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods May 26, 2026
1926534
Account for previous Matrix versions where `invite_room_state` is opt…
MadLittleMods May 26, 2026
17c3763
Better `msc4311_stripped_state` description
MadLittleMods May 26, 2026
75d1935
The PDU's over federation can be full PDU's now
MadLittleMods May 26, 2026
3888387
Raise 500 when federation returns 400 `M_MISSING_PARAM`
MadLittleMods May 26, 2026
259f151
Add note about future Complement tests
MadLittleMods May 26, 2026
c289e93
Kick CI after Sytest fixes
MadLittleMods May 27, 2026
ec572c6
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods May 27, 2026
4e7f3fc
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods Jul 9, 2026
bf9ab2f
Refactor to include `inviter_user_id` as part of stripped state
MadLittleMods Jul 9, 2026
c54c93b
Just pass in the event
MadLittleMods Jul 9, 2026
17a4ef6
Remove `_calculate_stripped_state_filter`
MadLittleMods Jul 9, 2026
1cf8282
Update date that Synapse is compliant
MadLittleMods Jul 9, 2026
ce17c93
Cross-link #19943
MadLittleMods Jul 9, 2026
5cc8753
Fix `compatibility` typo
MadLittleMods Jul 9, 2026
53a68d7
Use correct `knock_room_state` reference from response
MadLittleMods Jul 9, 2026
7488a24
Use full PDU's for v1 of federation invite API
MadLittleMods Jul 9, 2026
3870034
Fix typo causing extra nesting
MadLittleMods Jul 10, 2026
40d1316
Kick CI after deleting sytest branch with same name
MadLittleMods Jul 10, 2026
3fc94ba
Fix arg ordering typo with `SynapseError`
MadLittleMods Jul 10, 2026
26bf739
Cross-link MSC4319
MadLittleMods Jul 22, 2026
2822f3c
Move parse/serialize as methods of `StrippedStateEvent`
MadLittleMods Jul 28, 2026
3ce6900
Rename `parse` to `from_json_dict`
MadLittleMods Jul 28, 2026
19d8a7a
Rename `serialize` to `as_json_dict`
MadLittleMods Jul 28, 2026
ecab104
Fix `builtins.TypeError: unsupported operand type(s) for |: 'str' and…
MadLittleMods Jul 28, 2026
eed3071
MSC4319 is now merged
MadLittleMods Jul 28, 2026
f751b5f
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods Jul 28, 2026
e06b311
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods Jul 29, 2026
e301cdd
Start error off more plainly: `The other user's server did not unders…
MadLittleMods Jul 29, 2026
bc73bb0
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods Aug 3, 2026
0baa67b
Merge branch 'develop' into madlittlemods/remove-flawed-msc4311-parti…
MadLittleMods Aug 3, 2026
4f70688
Iterate on error
MadLittleMods Aug 3, 2026
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/19723.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove flawed [MSC4311](https://github.com/matrix-org/matrix-spec-proposals/pull/4311) partial implementation: Client-side API's like `/sync` should still use stripped events.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Complement tests are expected to fail ❌ as we removed the flawed partial implementation in this PR.

The Complement tests have been updated in matrix-org/complement#796 and pass locally. We will merge both PRs at the same time.

Comment thread
MadLittleMods marked this conversation as resolved.
9 changes: 0 additions & 9 deletions synapse/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,15 +1019,6 @@ def strip_event(event: EventBase) -> JsonDict:
Stripped state events can only have the `sender`, `type`, `state_key` and `content`
properties present.
"""
# MSC4311: Ensure the create event is available on invites and knocks.
# TODO: Implement the rest of MSC4311
if (
event.room_version.msc4291_room_ids_as_hashes
and event.type == EventTypes.Create
and event.get_state_key() == ""
):
return event.get_pdu_json()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the flawed MSC4311 partial implementation

return {
"type": event.type,
"state_key": event.state_key,
Expand Down
53 changes: 48 additions & 5 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
RoomVersions,
)
from synapse.events import EventBase, builder, make_event_from_dict
from synapse.events.snapshot import EventContext
from synapse.events.utils import parse_stripped_state_event
from synapse.federation.federation_base import (
FederationBase,
InvalidEventSignatureError,
Expand All @@ -71,7 +73,13 @@
from synapse.http.types import QueryParams
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, tag_args, trace
from synapse.metrics import SERVER_NAME_LABEL
from synapse.types import JsonDict, StrCollection, UserID, get_domain_from_id
from synapse.types import (
JsonDict,
StrCollection,
UserID,
get_domain_from_id,
)
from synapse.types.state import StateFilter
from synapse.util.async_helpers import concurrently_execute
from synapse.util.caches.expiringcache import ExpiringCache
from synapse.util.duration import Duration
Expand Down Expand Up @@ -1309,12 +1317,12 @@ async def send_invite(
self,
destination: str,
room_id: str,
event_id: str,
pdu: EventBase,
context: EventContext,
) -> EventBase:
room_version = await self.store.get_room_version(room_id)

content = await self._do_send_invite(destination, pdu, room_version)
content = await self._do_send_invite(destination, pdu, context, room_version)

pdu_dict = content["event"]

Expand All @@ -1335,7 +1343,11 @@ async def send_invite(
return pdu

async def _do_send_invite(
self, destination: str, pdu: EventBase, room_version: RoomVersion
self,
destination: str,
pdu: EventBase,
context: EventContext,
room_version: RoomVersion,
) -> JsonDict:
"""Actually sends the invite, first trying v2 API and falling back to
v1 API if necessary.
Expand All @@ -1350,6 +1362,34 @@ async def _do_send_invite(
"""
time_now = self._clock.time_msec()

# MSC4311: For the federation API, format events in `invite_room_state` as full
# PDU's
#
# First get all of the expected stripped state events that should be included.
# We will derive these from the `unsigned` part of the PDU but this doesn't
# include any event ID information so we need to look it up based on the state
# at the time of the invite.
stripped_state_types = []
for raw_stripped_event in pdu.unsigned.get("invite_room_state", []):
stripped_state_event = parse_stripped_state_event(raw_stripped_event)
# Since this is our own invite, it should always be well-formed
assert stripped_state_event is not None, (
"Unable to parse one of the evnts from the `invite_room_state` as a stripped state event"
)
stripped_state_types.append(
(stripped_state_event.type, stripped_state_event.state_key)
)

# Find the full events based on the state at the time of the invite
state_filter = StateFilter.from_types(stripped_state_types)
state_ids = await self.store.get_stripped_room_state_ids_from_event_context(
context, state_filter
)
state_events = await self.store.get_events(state_ids)
assert set(state_ids) == set(state_events.keys()), (
"We should have all events available that were set as stripped state."
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, this is technically a behaviour change where we are recalculating the stripped state here rather than using the stripped state we calculated at event creation.

Generally, I'm wondering if we should change the format of what we store as invite_room_state in the event json in the DB, rather than having this somewhat weird thing going where we have to recalculate it. Not really sure how tractable it is, but you can imagine a world where we store in a separate table the stripped state event IDs for locally generated events.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 Future plans


try:
return await self.transport_layer.send_invite_v2(
destination=destination,
Expand All @@ -1358,7 +1398,10 @@ async def _do_send_invite(
content={
"event": pdu.get_pdu_json(time_now),
"room_version": room_version.identifier,
"invite_room_state": pdu.unsigned.get("invite_room_state", []),
"invite_room_state": [
state_event.get_pdu_json(time_now)
for state_event in state_events.values()
],
},
)
except HttpResponseException as e:
Expand Down
1 change: 1 addition & 0 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ async def on_send_knock_request(
# server. This will allow the remote server's clients to display information
# related to the room while the knock request is pending.
stripped_room_state = (
# TODO: Implement MSC4311 and use full PDUs here
await self.store.get_stripped_room_state_from_event_context(
context, self._room_prejoin_state_types
)
Expand Down
6 changes: 4 additions & 2 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,9 @@ async def try_backfill(domains: StrCollection) -> bool:

return False

async def send_invite(self, target_host: str, event: EventBase) -> EventBase:
async def send_invite(
self, target_host: str, event: EventBase, context: EventContext
) -> EventBase:
"""Sends the invite to the remote server for signing.

Invites must be signed by the invitee's server before distribution.
Expand All @@ -563,8 +565,8 @@ async def send_invite(self, target_host: str, event: EventBase) -> EventBase:
pdu = await self.federation_client.send_invite(
destination=target_host,
room_id=event.room_id,
event_id=event.event_id,
pdu=event,
context=context,
)
except RequestSendFailed:
raise SynapseError(502, f"Can't connect to server {target_host}")
Expand Down
2 changes: 1 addition & 1 deletion synapse/handlers/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,7 @@ async def persist_and_notify_client_events(
# to get them to sign the event.

returned_invite = await federation_handler.send_invite(
invitee.domain, event
invitee.domain, event, context
)
event.unsigned.pop("room_state", None)

Expand Down
41 changes: 34 additions & 7 deletions synapse/storage/databases/main/events_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1137,19 +1137,46 @@ async def get_stripped_room_state_from_event_context(
filter = StateFilter.from_types(types)
else:
filter = state_keys_to_include
selected_state_ids = await context.get_current_state_ids(filter)

selected_state_ids = await self.get_stripped_room_state_ids_from_event_context(
context, filter
)

state_to_include = await self.get_events(selected_state_ids)

return [strip_event(e) for e in state_to_include.values()]

async def get_stripped_room_state_ids_from_event_context(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split some logic out of get_stripped_room_state_from_event_context(...) into get_stripped_room_state_ids_from_event_context(...)

This way we can use the same event selection logic but fetch/format them however we see fit.

self,
context: EventContext,
state_keys_to_include: StateFilter,
) -> list[str]:
"""
Retrieve the stripped state IDs for an event, given an event context to retrieve state
from as well as the state types to include. Optionally, include the membership
events from a specific user.

"Stripped" state means that only the `type`, `state_key`, `content` and `sender` keys
are included from each state event.
Comment on lines +1166 to +1167

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really relevant to this function which is only about getting the state event IDs.

Suggested change
"Stripped" state means that only the `type`, `state_key`, `content` and `sender` keys
are included from each state event.


Args:
context: The event context to retrieve state of the room from.
state_keys_to_include: The state events to include, for each event type.

Returns:
A list of event_ids, each representing the stripped state event to include for this event
"""
selected_state_ids = await context.get_current_state_ids(state_keys_to_include)

# We know this event is not an outlier, so this must be
# non-None.
assert selected_state_ids is not None

# Confusingly, get_current_state_events may return events that are discarded by
# the filter, if they're in context._state_delta_due_to_event. Strip these away.
selected_state_ids = filter.filter_state(selected_state_ids)

state_to_include = await self.get_events(selected_state_ids.values())
# Confusingly, `get_current_state_ids` may return events that are discarded by
# the filter, if they're in `context._state_delta_due_to_event`. Strip these away.
selected_state_ids = state_keys_to_include.filter_state(selected_state_ids)

return [strip_event(e) for e in state_to_include.values()]
return list(selected_state_ids.values())

def _maybe_start_fetch_thread(self) -> None:
"""Starts an event fetch thread if we are not yet at the maximum number."""
Expand Down
Loading