Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
d7febaf
Add is_state_dag_connected to check the events returned in /send_join
kegsay Sep 4, 2026
67fc751
Calculate auth events on events and auth based on them
kegsay Sep 4, 2026
2a58c73
Auth and persist state DAG outliers
kegsay Sep 4, 2026
0f8cfcb
Support joining MSC4242 rooms
kegsay Sep 7, 2026
b47fe53
changelog
kegsay Sep 11, 2026
6cd5439
Apply batched suggestions from code review
kegsay Sep 11, 2026
e7c915e
Review comments
kegsay Sep 11, 2026
f92d4bf
Review comments
kegsay Sep 11, 2026
378ba03
Linting
kegsay Sep 11, 2026
b6b1a58
Review comments
kegsay Sep 14, 2026
4b3a72b
Review comments
kegsay Sep 16, 2026
78dd966
Update synapse/handlers/federation_event.py
kegsay Sep 21, 2026
87e68d1
Update synapse/handlers/federation_event.py
kegsay Sep 21, 2026
f99f047
Review comments
kegsay Sep 21, 2026
a835dac
Linting
kegsay Sep 21, 2026
c108796
Factor out _get_events_from_remote from _get_events_and_persist
kegsay Sep 9, 2026
6585235
Fetch missing state DAG events via /get_missing_events
kegsay Sep 9, 2026
aa3cca5
Compute event context for pulled state DAG events
kegsay Sep 9, 2026
6908536
Fetch missing prev_state events via the state DAG walk
kegsay Sep 9, 2026
a6c7796
Soft-fail pulled state DAG events against DAG extremities
kegsay Sep 9, 2026
2d0d65e
Test receiving MSC4242 state DAG events over federation
kegsay Sep 9, 2026
b32e9a3
Cap prev_state_events and the state DAG walk
kegsay Sep 9, 2026
8ae810b
Adjust test comments and make created rooms more accurate
kegsay Sep 11, 2026
02eeef1
Changelog
kegsay Sep 14, 2026
326f040
Add is_state_dag_connected to check the events returned in /send_join
kegsay Sep 4, 2026
0e579e2
Calculate auth events on events and auth based on them
kegsay Sep 4, 2026
113b8fa
Auth and persist state DAG outliers
kegsay Sep 4, 2026
d4f5f81
Support joining MSC4242 rooms
kegsay Sep 7, 2026
8106eab
changelog
kegsay Sep 11, 2026
752f11f
Apply batched suggestions from code review
kegsay Sep 11, 2026
1e7cecc
Review comments
kegsay Sep 11, 2026
0e962a1
Review comments
kegsay Sep 11, 2026
cc56001
Linting
kegsay Sep 11, 2026
e98c653
Review comments
kegsay Sep 14, 2026
bf174a9
Review comments
kegsay Sep 16, 2026
e32d828
Update synapse/handlers/federation_event.py
kegsay Sep 21, 2026
c3db7cf
Update synapse/handlers/federation_event.py
kegsay Sep 21, 2026
0cbd358
Review comments
kegsay Sep 21, 2026
ae5bff7
Linting
kegsay Sep 21, 2026
40ef6c5
Update synapse/handlers/federation_event.py
kegsay Sep 22, 2026
fbf97a6
Review comments
kegsay Sep 22, 2026
947b910
Various bug fixes and enable Complement tests for MSC4242
kegsay Sep 24, 2026
f3dce9b
Linting
kegsay Sep 24, 2026
dc0d0dc
Stricter check to ensure missing await would be caught
kegsay Sep 24, 2026
d73bc42
Add missing RejectionsStore for workers
kegsay Sep 28, 2026
2b40cd0
bugfix: rejoining a room with rejected events incorrectly authorised …
kegsay Sep 30, 2026
b65f0b7
Review comments
kegsay Sep 30, 2026
32876bb
Linting
kegsay Sep 30, 2026
0d1ded9
bugfix: fix an issue where a rejoin would calculate state incorrectly…
kegsay Sep 30, 2026
55ad431
Linting
kegsay Oct 2, 2026
fd9a6a4
Block other federation endpoints for MSC4242 rooms; ensure the join e…
kegsay Oct 2, 2026
ef22e48
Ensure we sanity check events in the /send_join response
kegsay Oct 2, 2026
5cb407f
Only scan for rejected event IDs in state DAG rooms to avoid needless…
kegsay Oct 2, 2026
982efb7
Assert there can be no missing auth event IDs since we already ensure…
kegsay Oct 2, 2026
82d52c9
Swap 5xx for 4xx else MSC4242 rooms can trigger retry backoffs server…
kegsay Oct 2, 2026
f086c20
Merge branch 'kegan/4242-inbound' into kegan/4242-inbound-events
kegsay Oct 2, 2026
0aea320
Linting
kegsay Oct 2, 2026
ce6a0c5
Remove unused patch file
kegsay Oct 2, 2026
6fa25a1
Update test to reflect 4xx behaviour in /get_missing_events; more docs
kegsay Oct 7, 2026
fecc9f4
Ensure we build valid MSC4242 events
kegsay Oct 7, 2026
1d2ee64
Even more review comments
kegsay Oct 13, 2026
a919eed
Remove comment as it was moved elsewhere
kegsay Oct 13, 2026
bfb98ad
Rework where the max check lives to ensure we don't drop forward extr…
kegsay Oct 13, 2026
091c140
Merge remote-tracking branch 'origin/develop' into kegan/4242-inbound
kegsay Oct 13, 2026
c81dedf
Merge remote-tracking branch 'origin/kegan/4242-inbound' into kegan/4…
kegsay Oct 13, 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/20194.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for joining [MSC4242](https://github.com/matrix-org/matrix-spec-proposals/pull/4242) rooms over federation.
1 change: 1 addition & 0 deletions changelog.d/20221.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for receiving events in [MSC4242](https://github.com/matrix-org/matrix-spec-proposals/pull/4242) rooms over federation.
1 change: 1 addition & 0 deletions docker/complement/conf/workers-shared-extra.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ experimental_features:
msc4306_enabled: true
# Sticky Events
msc4354_enabled: true
msc4242_enabled: true

server_notices:
system_mxid_localpart: _server
Expand Down
1 change: 1 addition & 0 deletions scripts-dev/complement.sh
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ main() {
./tests/msc4306
./tests/msc4222
./tests/msc4429
./tests/msc4242
)

# Export the list of test packages as a space-separated environment variable, so other
Expand Down
4 changes: 4 additions & 0 deletions synapse/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,7 @@ class StateDag:
Arbitrary, servers usually never get this high so it's mostly to bound the max size of
a single /get_missing_events response.
"""
MAX_PREV_STATE_EVENTS: Final = 20
"""
Max number of prev_state_events allowed on an MSC4242 event as per the MSC.
"""
2 changes: 2 additions & 0 deletions synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
from synapse.storage.databases.main.pusher import PusherWorkerStore
from synapse.storage.databases.main.receipts import ReceiptsWorkerStore
from synapse.storage.databases.main.registration import RegistrationWorkerStore
from synapse.storage.databases.main.rejections import RejectionsStore
from synapse.storage.databases.main.relations import RelationsWorkerStore
from synapse.storage.databases.main.room import RoomWorkerStore
from synapse.storage.databases.main.roommember import RoomMemberWorkerStore
Expand Down Expand Up @@ -153,6 +154,7 @@ class GenericWorkerStore(
EventFederationWorkerStore,
EventPushActionsWorkerStore,
PurgeEventsStore,
RejectionsStore,
StateGroupWorkerStore,
SignatureWorkerStore,
UserErasureWorkerStore,
Expand Down
13 changes: 11 additions & 2 deletions synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ async def check_state_independent_auth_rules(
store: _EventSourceStore,
event: "EventBase",
batched_auth_events: Mapping[str, "EventBase"] | None = None,
batched_rejections: Mapping[str, str] | None = None,
) -> None:
"""Check that an event complies with auth rules that are independent of room state

Expand All @@ -175,6 +176,11 @@ async def check_state_independent_auth_rules(
event: the event being checked.
batched_auth_events: if the event being authed is part of a batch, any events
from the same batch that may be necessary to auth the current event
batched_rejections: event ID to rejection reason for any events in `batched_auth_events`
which have been rejected. Events in the batch have not been persisted
yet, so their rejection is recorded neither on the event nor in the database,
and the caller has to tell us. Needed for MSC4242 State DAG rooms, where
rejection cascades through `prev_state_events`.

Raises:
AuthError if the checks fail
Expand Down Expand Up @@ -244,11 +250,14 @@ async def check_state_independent_auth_rules(
)
# 2.3 If there are entries which were themselves rejected under the checks performed on
# receipt of a PDU, reject.
if prev_state_event.rejected_reason is not None:
rejected_reason = prev_state_event.rejected_reason
if rejected_reason is None and batched_rejections is not None:
rejected_reason = batched_rejections.get(prev_state_event.event_id)
if rejected_reason is not None:
raise AuthError(
403,
f"During auth for event {event.event_id} in room {event.room_id}, event has a "
+ f"prev_state_event which is rejected ({prev_state_event.rejected_reason}): "
+ f"prev_state_event which is rejected ({rejected_reason}): "
+ f"{prev_state_event.event_id}",
)

Expand Down
12 changes: 11 additions & 1 deletion synapse/events/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
import attr
from signedjson.types import SigningKey

from synapse.api.constants import MAX_DEPTH, EventTypes, StickyEvent, StickyEventField
from synapse.api.constants import (
MAX_DEPTH,
EventTypes,
StateDag,
StickyEvent,
StickyEventField,
)
from synapse.api.room_versions import (
KNOWN_EVENT_FORMAT_VERSIONS,
EventFormatVersions,
Expand Down Expand Up @@ -157,6 +163,10 @@ async def build(
assert self.room_version.msc4242_state_dags
if self.room_version.msc4242_state_dags:
assert prev_state_events is not None
# FIXME: choose more intelligently
prev_state_events = sorted(prev_state_events)[
: StateDag.MAX_PREV_STATE_EVENTS
]
if self.room_id:
state_ids = await self._state.compute_state_after_events(
self.room_id,
Expand Down
32 changes: 32 additions & 0 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,23 @@ async def _handle_old_staged_events(self) -> None:
# We pause a bit so that we don't start handling all rooms at once.
await self._clock.sleep(Duration(seconds=random.uniform(0, 0.1)))

async def _assert_not_state_dag_room(self, room_id: str, endpoint: str) -> None:
room_version = await self.store.get_room_version(room_id)
if room_version.msc4242_state_dags:
# Not 5xx else the server will back off from us
raise SynapseError(
400,
f"{endpoint} is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNRECOGNIZED,
)

async def on_backfill_request(
self, origin: str, room_id: str, versions: list[str], limit: int
) -> tuple[int, dict[str, Any]]:
async with self._server_linearizer.queue((origin, room_id)):
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/backfill")

pdus = await self.handler.on_backfill_request(
origin, room_id, versions, limit
Expand Down Expand Up @@ -275,6 +286,14 @@ async def on_timestamp_to_event_request(
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)

if (await self.store.get_room_version(room_id)).msc4242_state_dags:
# TODO: we currently don't support /timestamp_to_event in MSC4242 rooms.
raise SynapseError(
404,
"This endpoint is not yet implemented in MSC4242 rooms",
errcode=Codes.NOT_FOUND,
)

# We only try to fetch data from the local database
event_id = await self.store.get_event_id_for_timestamp(
room_id, timestamp, direction
Expand Down Expand Up @@ -654,6 +673,7 @@ async def on_room_state_request(
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/state")

# we grab the linearizer to protect ourselves from servers which hammer
# us. In theory we might already have the response to this query
Expand Down Expand Up @@ -694,6 +714,7 @@ async def on_state_ids_request(
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/state_ids")

resp = await self._state_ids_resp_cache.wrap(
(room_id, event_id),
Expand Down Expand Up @@ -1154,6 +1175,14 @@ async def _on_send_membership_event(
# Note that get_room_version throws if the room does not exist here.
room_version = await self.store.get_room_version(room_id)

if room_version.msc4242_state_dags and membership_type != Membership.JOIN:
# 4xx rather than 5xx, so the caller does not back off from us
raise SynapseError(
400,
f"/send_{membership_type} is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNRECOGNIZED,
)

if await self.store.is_partial_state_room(room_id):
# If our server is still only partially joined, we can't give a complete
# response to /send_join, /send_knock or /send_leave.
Expand Down Expand Up @@ -1247,6 +1276,7 @@ async def on_event_auth(
await self._event_auth_handler.assert_host_in_room(room_id, origin)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, room_id)
await self._assert_not_state_dag_room(room_id, "/event_auth")

time_now = self._clock.time_msec()
auth_pdus = await self.handler.on_event_auth(event_id, room_id)
Expand Down Expand Up @@ -1332,6 +1362,8 @@ async def on_get_missing_events(
origin, room_id, earliest_events, latest_events, limit
)
else:
# TODO: currently we only handle /send_join and connected events in /send for state DAG rooms
await self._assert_not_state_dag_room(room_id, "/get_missing_events")
missing_events = await self.handler.on_get_missing_events(
origin, room_id, earliest_events, latest_events, limit
)
Expand Down
24 changes: 17 additions & 7 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
PartialStateConflictError,
RequestSendFailed,
SynapseError,
UnsupportedRoomVersionError,
)
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.crypto.event_signing import compute_event_signature
Expand Down Expand Up @@ -255,6 +254,10 @@ async def maybe_backfill(
Returns:
True if we actually tried to backfill something, otherwise False.
"""
if (await self.store.get_room_version(room_id)).msc4242_state_dags:
# TODO: we currently don't support backfilling in MSC4242 rooms.
return False

# Starting the processing time here so we can include the room backfill
# linearizer lock queue in the timing
processing_start_time = self.clock.time_msec() if record_time else 0
Expand Down Expand Up @@ -712,12 +715,6 @@ async def do_invite_join(
room_id
)

# See related restriction in /createRoom requests in handlers/room.py
if room_version_obj.msc4242_state_dags:
raise UnsupportedRoomVersionError(
"Homeserver does not support this room version over federation"
)

ret = await self.federation_client.send_join(
host_list,
event,
Expand All @@ -741,6 +738,11 @@ async def do_invite_join(
auth_chain = ret.auth_chain
auth_chain.sort(key=lambda e: e.depth)

if ret.state_dag is not None:
# MSC4242 State DAG rooms have no separate state and auth chain: the
# state DAG is both, and everything is derived from it.
state = ret.state_dag

logger.debug("do_invite_join auth_chain: %s", auth_chain)
logger.debug("do_invite_join state: %s", state)

Expand Down Expand Up @@ -1461,6 +1463,14 @@ async def on_invite_request(
if event.state_key is None:
raise SynapseError(400, "The invite event did not have a state key")

if room_version.msc4242_state_dags:
# 4xx rather than 5xx, so the caller does not back off from us
raise SynapseError(
400,
"/invite is not implemented for MSC4242 state DAG rooms",
errcode=Codes.UNRECOGNIZED,
)

is_blocked = await self.store.is_room_blocked(event.room_id)
if is_blocked:
raise SynapseError(403, "This room has been blocked on this server")
Expand Down
Loading
Loading