Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
48e1e73
MSC4242: State DAGs (CSAPI)
kegsay Feb 3, 2026
dc3db60
Changelog
kegsay Feb 3, 2026
fc4975f
AssertEqual not plural because https://github.com/twisted/twisted/iss…
kegsay Feb 3, 2026
b69986d
Merge branch 'develop' into kegan/4242-csapi
kegsay Mar 9, 2026
c561c0d
Swap Optional for |: https://github.com/element-hq/synapse/pull/19424…
kegsay Mar 9, 2026
3dff3ac
Additional comments per code review
kegsay Mar 9, 2026
aa46122
Apply suggestions from code review
kegsay Mar 9, 2026
2f82a5b
Review comments in persist_events.py
kegsay Mar 9, 2026
606ce6b
Additional review comments: non-test files
kegsay Mar 9, 2026
7311f92
Review comments in events/builder.py
kegsay Mar 9, 2026
85fa59e
Update synapse/handlers/admin.py
kegsay Mar 9, 2026
332fd2d
Update synapse/handlers/message.py
kegsay Mar 9, 2026
4ea71e0
Update synapse/state/__init__.py
kegsay Mar 9, 2026
7a1be81
More code review (non-test files)
kegsay Mar 9, 2026
d96737b
Review comments: test code and a few extras
kegsay Mar 10, 2026
c9d8d9f
Update synapse/events/__init__.py
kegsay Mar 10, 2026
2f8fece
Update synapse/handlers/admin.py
kegsay Mar 10, 2026
bc91229
Update synapse/storage/controllers/persist_events.py
kegsay Mar 10, 2026
6ecf821
Update synapse/storage/controllers/persist_events.py
kegsay Mar 10, 2026
ecc9860
Update synapse/event_auth.py
kegsay Mar 10, 2026
a0711ef
Review comments
kegsay Mar 10, 2026
85fc75c
Use shortstr for consistency with other log messages
kegsay Mar 10, 2026
f790d3a
Linting
kegsay Mar 10, 2026
206c77a
Review comments
kegsay Mar 17, 2026
0b155b7
Review comments
kegsay Mar 17, 2026
cdf9054
Merge branch 'develop' into kegan/4242-csapi
kegsay Mar 17, 2026
3a51f74
Exclude MSC4242 room versions whilst this branch doesn't have fed su…
kegsay Mar 17, 2026
d59f31a
Linting
kegsay Mar 17, 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/19424.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add experimental support for MSC4242: State DAGs. Excludes federation support.
21 changes: 21 additions & 0 deletions rust/src/events/internal_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ enum EventInternalMetadataData {
TxnId(Box<str>),
TokenId(i64),
DeviceId(Box<str>),
CalculatedAuthEventIDs(Vec<String>),
}

impl EventInternalMetadataData {
Expand Down Expand Up @@ -123,6 +124,10 @@ impl EventInternalMetadataData {
pyo3::intern!(py, "device_id"),
o.into_pyobject(py).unwrap_infallible().into_any(),
),
EventInternalMetadataData::CalculatedAuthEventIDs(o) => (
pyo3::intern!(py, "calculated_auth_event_ids"),
o.into_pyobject(py).unwrap().into_any(),
),
}
}

Expand Down Expand Up @@ -190,6 +195,11 @@ impl EventInternalMetadataData {
.map(String::into_boxed_str)
.with_context(|| format!("'{key_str}' has invalid type"))?,
),
"calculated_auth_event_ids" => EventInternalMetadataData::CalculatedAuthEventIDs(
value
.extract()
.with_context(|| format!("'{key_str}' has invalid type"))?,
),
_ => return Ok(None),
};

Expand Down Expand Up @@ -472,6 +482,17 @@ impl EventInternalMetadata {
set_property!(self, TxnId, obj.into_boxed_str());
}

/// The calculated auth event IDs, if it was set when the event was created.
#[getter]
fn get_calculated_auth_event_ids(&self) -> PyResult<&Vec<String>> {
let s = get_property!(self, CalculatedAuthEventIDs)?;
Ok(s)
}
#[setter]
fn set_calculated_auth_event_ids(&mut self, obj: Vec<String>) {
set_property!(self, CalculatedAuthEventIDs, obj);
}

/// The access token ID of the user who sent this event, if any.
#[getter]
fn get_token_id(&self) -> PyResult<i64> {
Expand Down
48 changes: 46 additions & 2 deletions synapse/api/room_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ class EventFormatVersions:
ROOM_V3 = 2 # MSC1659-style $hash event id format: used for room v3
ROOM_V4_PLUS = 3 # MSC1884-style $hash format: introduced for room v4
ROOM_V11_HYDRA_PLUS = 4 # MSC4291 room IDs as hashes: introduced for room HydraV11
ROOM_VMSC4242 = 5


KNOWN_EVENT_FORMAT_VERSIONS = {
EventFormatVersions.ROOM_V1_V2,
EventFormatVersions.ROOM_V3,
EventFormatVersions.ROOM_V4_PLUS,
EventFormatVersions.ROOM_V11_HYDRA_PLUS,
EventFormatVersions.ROOM_VMSC4242,
}


Expand Down Expand Up @@ -112,6 +114,8 @@ class RoomVersion:
msc3931_push_features: tuple[str, ...] # values from PushRuleRoomFlag
# MSC3757: Restricting who can overwrite a state event
msc3757_enabled: bool
# MSC4242: Creates events with prev_state_events instead of auth_events and derives state from it.
msc4242_state_dags: bool
# MSC4289: Creator power enabled
msc4289_creator_power_enabled: bool
# MSC4291: Room IDs as hashes of the create event
Expand All @@ -138,6 +142,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -160,6 +165,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -182,6 +188,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -204,6 +211,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -226,6 +234,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -248,6 +257,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -270,6 +280,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -292,6 +303,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -314,6 +326,7 @@ class RoomVersions:
enforce_int_power_levels=False,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -336,6 +349,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -359,6 +373,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(PushRuleRoomFlag.EXTENSIBLE_EVENTS,),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -382,6 +397,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=True,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -404,6 +420,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -427,6 +444,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=True,
msc4242_state_dags=False,
msc4289_creator_power_enabled=False,
msc4291_room_ids_as_hashes=False,
)
Expand All @@ -439,8 +457,8 @@ class RoomVersions:
special_case_aliases_auth=False,
strict_canonicaljson=True,
limit_notifications_power_levels=True,
implicit_room_creator=True, # Used by MSC3820
updated_redaction_rules=True, # Used by MSC3820
implicit_room_creator=True,
updated_redaction_rules=True,
restricted_join_rule=True,
restricted_join_rule_fix=True,
knock_join_rule=True,
Expand All @@ -449,6 +467,7 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=True, # Changed from v11
msc4291_room_ids_as_hashes=True, # Changed from v11
)
Expand All @@ -471,9 +490,33 @@ class RoomVersions:
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=False,
msc4242_state_dags=False,
msc4289_creator_power_enabled=True, # Changed from v11
msc4291_room_ids_as_hashes=True, # Changed from v11
)
MSC4242v12 = RoomVersion(
"org.matrix.msc4242.12", # MSC4242 (State DAGs) based on room version "12"
RoomDisposition.UNSTABLE,
EventFormatVersions.ROOM_VMSC4242,
StateResolutionVersions.V2_1,
enforce_key_validity=True,
special_case_aliases_auth=False,
strict_canonicaljson=True,
limit_notifications_power_levels=True,
implicit_room_creator=True,
updated_redaction_rules=True,
restricted_join_rule=True,
restricted_join_rule_fix=True,
knock_join_rule=True,
msc3389_relation_redactions=False,
knock_restricted_join_rule=True,
enforce_int_power_levels=True,
msc3931_push_features=(),
msc3757_enabled=False,
msc4289_creator_power_enabled=True,
msc4291_room_ids_as_hashes=True,
msc4242_state_dags=True,
)


KNOWN_ROOM_VERSIONS: dict[str, RoomVersion] = {
Expand All @@ -493,6 +536,7 @@ class RoomVersions:
RoomVersions.V12,
RoomVersions.MSC3757v10,
RoomVersions.MSC3757v11,
RoomVersions.MSC4242v12,
RoomVersions.HydraV11,
)
}
Expand Down
73 changes: 72 additions & 1 deletion synapse/event_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
RoomVersion,
RoomVersions,
)
from synapse.events import is_creator
from synapse.events import FrozenEventVMSC4242, is_creator
from synapse.state import CREATE_KEY
from synapse.storage.databases.main.events_worker import EventRedactBehaviour
from synapse.types import (
Expand Down Expand Up @@ -186,6 +186,70 @@ async def check_state_independent_auth_rules(
# 1.5 Otherwise, allow
return

# State DAGs 2. Considering the event's prev_state_events:
if event.room_version.msc4242_state_dags:
prev_state_events_ids = set(cast(FrozenEventVMSC4242, event).prev_state_events)
needed_prev_state_event_ids = {
event_id
for event_id in prev_state_events_ids
if not batched_auth_events or event_id not in batched_auth_events
}
prev_state_events = (
{}
if not batched_auth_events
else {
e: batched_auth_events[e]
for e in prev_state_events_ids - needed_prev_state_event_ids
}
)
if len(needed_prev_state_event_ids) > 0:
needed_prev_state_events = await store.get_events(
needed_prev_state_event_ids,
redact_behaviour=EventRedactBehaviour.as_is,
allow_rejected=True,
)
prev_state_events.update(needed_prev_state_events)
if len(prev_state_events) != len(prev_state_events_ids):
# we should have all the prev state events by now, so if we do not, that suggests
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# we should have all the prev state events by now, so if we do not, that suggests
# we should have all the `prev_state_events` by now, so if we do not, that suggests

# a synapse programming error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# a synapse programming error
# a Synapse programming error

known_prev_state_event_ids = set(prev_state_events)
raise RuntimeError(
f"Event {event.event_id} has unknown prev state events "
+ f"{len(prev_state_events)} != {len(prev_state_events_ids)} "
Copy link
Copy Markdown
Contributor

@MadLittleMods MadLittleMods Mar 18, 2026

Choose a reason for hiding this comment

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

To avoid some of the jumble of output we can group this together better with a description.

Suggested change
+ f"{len(prev_state_events)} != {len(prev_state_events_ids)} "
+ f"({len(prev_state_events)}/{len(prev_state_events_ids)} known)"

+ f"{prev_state_events_ids - known_prev_state_event_ids} missing "
+ f"out of {prev_state_events_ids}"
)
for prev_state_event in prev_state_events.values():
# 2.1 If there are entries which do not belong in the same room, reject.
if prev_state_event.room_id != event.room_id:
raise AuthError(
403,
"During auth for event %s in room %s, found event %s in prev state events "
"which is in room %s"
% (
event.event_id,
event.room_id,
prev_state_event.event_id,
prev_state_event.room_id,
),
)
# 2.2 If there are entries which do not have a state_key, reject.
if not prev_state_event.is_state():
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 not state: {prev_state_event.event_id}",
)
# 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:
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.event_id}",
)

# 2. Reject if event has auth_events that: ...
auth_events: ChainMap[str, EventBase] = ChainMap()
if batched_auth_events:
Expand Down Expand Up @@ -471,6 +535,13 @@ def _check_create(event: "EventBase") -> None:
if event.prev_event_ids():
raise AuthError(403, "Create event has prev events")

# State DAGs 1.2 If it has any prev_state_events, reject.
if (
event.room_version.msc4242_state_dags
and cast(FrozenEventVMSC4242, event).prev_state_events
):
raise AuthError(403, "Create event has prev state events")

if event.room_version.msc4291_room_ids_as_hashes:
# 1.2 If the create event has a room_id, reject
if "room_id" in event:
Expand Down
Loading
Loading