MSC4242: State DAGs - #4242
Conversation
|
(for clarity: this is using the process defined by matrix-org/matrix-spec#2157) |
There was a problem hiding this comment.
Implementation requirements:
- Server (unstable room version)
- Complement tests (for unstable room version)
|
This MSC now has content. |
| - while the queue is not empty and the limit has not been reached: | ||
| * dequeue an event ID and find its `prev_state_events` PSE. | ||
| * Remove IDs from PSE if they are in 'seen'. | ||
| * add the remaining event IDs from PSE to the result list and the end of the queue. |
There was a problem hiding this comment.
I'm assuming the list of PSE should be sorted before put onto the end of the queue?
(needs clarification)
There was a problem hiding this comment.
Doesn't matter, as it's still deterministic if we use the wire-format ordering.
There was a problem hiding this comment.
I was thinking this was necessary in order to "walk these events in lexicographical order" 🤔
If we don't do this then part-way down the forks, we could wander down in whatever path.
There was a problem hiding this comment.
Aha, you're right, thanks. Will fix.
There was a problem hiding this comment.
Reopened this because this ordering is harder to implement efficiently than other orderings. In particular, when working on the CTE SQL for this, expressing "process one frontier element at a time, query its predecessors, sort them lexicographically then append them to results" results in a subtle ordering which depends on the processing order of parents. The CTE SQL works on a hop-based system, finding all events 1 hop away, sorting them, rinse and repeat. This loses the distinction of sorting based on parent order, causing some unit tests to fail.
As an example:
A
|
B
/ \
D C
| |
E F <-- latest={E,F}, earliest={}, limit=2
The old order would sort latest=[E,F], then pop E, process it to produce D, then pop F and process it to process C, resulting in [D, C] as the output.
The proposed ordering would sort latest=[E,F] then simultaneously lookup all prevs to process [D, C] (everything 1 hop away) then sort them, resulting in [C, D] as the output.
The main important thing here to have a deterministic output, I don't particularly care which ordering we use, and given the CTE one is faster, that one should probably be the one we do.
This implements [MSC4242: State DAGs](matrix-org/matrix-spec-proposals#4242), without support for federation. A general overview: - It adds a new room version and new event type. - It adds a new field `calculated_auth_event_ids` to internal metadata. - It stores the state DAG via new state DAG edges / forward extremities tables. - It adds new auth rules as per the MSC. - It uses the new `prev_state_events` field instead of `prev_event_ids()` when doing state resolution. Complement tests: matrix-org/complement#841 ### Pull Request Checklist <!-- Please read https://element-hq.github.io/synapse/latest/development/contributing_guide.html before submitting your pull request --> * [x] Pull request is based on the develop branch * [x] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). The entry should: - Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - Use markdown where necessary, mostly for `code blocks`. - End with either a period (.) or an exclamation mark (!). - Start with a capital letter. - Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry. * [x] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: Eric Eastwood <erice@element.io>
This implements [MSC4242: State DAGs](matrix-org/matrix-spec-proposals#4242), without support for federation. A general overview: - It adds a new room version and new event type. - It adds a new field `calculated_auth_event_ids` to internal metadata. - It stores the state DAG via new state DAG edges / forward extremities tables. - It adds new auth rules as per the MSC. - It uses the new `prev_state_events` field instead of `prev_event_ids()` when doing state resolution. Complement tests: matrix-org/complement#841 ### Pull Request Checklist <!-- Please read https://element-hq.github.io/synapse/latest/development/contributing_guide.html before submitting your pull request --> * [x] Pull request is based on the develop branch * [x] Pull request includes a [changelog file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog). The entry should: - Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from `EventStore` to `EventWorkerStore`.". - Use markdown where necessary, mostly for `code blocks`. - End with either a period (.) or an exclamation mark (!). - Start with a capital letter. - Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry. * [x] [Code style](https://element-hq.github.io/synapse/latest/code_style.html) is correct (run the [linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters)) --------- Co-authored-by: Eric Eastwood <erice@element.io>
| case of garbage in, garbage out. Specifically, the State DAG proposal of this MSC: | ||
| - _calculates_ the `auth_events` key which is today sender-specified in every event, | ||
| - _mandates_ that all servers fill in the entire state DAG on receipt of new events with unknown | ||
| `prev_state_events` by walking back up the DAG to the create event via the `/get_missing_events` endpoint. |
There was a problem hiding this comment.
This seems insufficient for guaranteeing fully up-to-date reconciliations. While walking prev_state_events guarantees full causal history for an event, it fails to uncover concurrent branches of state events (hidden forward extremities generated during network partitions). Relying on the "forward-push" mechanism (new events being sent) to merge branches means partitions will lead to semi-permanent divergence in inactive rooms. Active partition-healing and discovery mechanisms are essential to the eventual consistency this MSC aims for... relegating them to "follow-up" MSCs feels inadequate. I'm sure there has been some work/investigation here, but I have not seen or come across those proposals. I have some ideas here regarding /room_digest endpoints and gossip mechanisms.
If state DAGs are to be their own fully contiguous, self-sufficient sub-graphs, they definitely deserve a dedicated /_matrix/federation/v2/get_missing_state_events endpoint rather than overloading /get_missing_events with a boolean param. Because this MSC introduces completely different traversal logic when walking the state DAG, overloading the legacy API endpoint is surely an anti-pattern. We need a clear, dedicated endpoint for this specific BFS walk.
The proposed exponential limit markers may result in network bottlenecks when resolving deep partitions. Having recently implemented binary-lifted jump pointers (shorteventid_statejumppointers) to alleviate database thrashing under the O(N) hop-by-hop walk of large test cases, I feel a pressing need for that same O(log N) efficiency over the network wire.
On the network side, I caution against leaning too heavily on Bloom Filters. While convenient at shallow depths, their footprint scales linearly to keep a low false-positive rate. A hybrid approach seems better. Similar to how C++ switches from quicksort to heapsort to bound worst-case overhead, federation syncs should enforce an algorithmic "switch" above a critical divergence depth... explicitly shifting to the O(log N) techniques already mentioned (Merkle search trees, Merkle-Patricia tries, Range-based reconciliation) to maintain a healthy and efficient network.
There was a problem hiding this comment.
I don't disagree with most of what you have written, but you don't seem to understand that we have a working protocol and large protocol changes are difficult to land.
I explicitly call out that we don't have an anti-entropy mechanism today and consciously defer it. You may disagree, that's fine. It is worth noting that in practice the lack of such a mechanism hasn't materially impacted the protocol because in practice relying on forward-push is enough. Anti-entropy does have its own costs (constant background chatter gossiping latest forward extremities) so it's not as clear cut as you're portraying.
It's a taste issue if you want a new endpoint or not. Both operations walk a DAG and have the same inputs. If you have any non-taste issues which may lean to one or the other then let me know.
Yes, it can take a while to reconcile with a server which has diverged significantly from you. I'm very open to suggestions to improve this, though you need to bear in mind we are working in a Byzantine environment. Most optimisations assume servers follow the protocol and degrade to a DAG walk (or worse, break entirely). If you have a way to ensure the jump pointers can't be outright malicious, please comment with a suggestion.
We're not using bloom filters in this proposal.
There was a problem hiding this comment.
-
I certainly see the value in not attaching too many subordinate MSCs, and how that could slow things down more. However, I insist on proper separations of concerns. If providing "eventual consistency" (preferably: rapid consistency) falls under the purview of a different card or MSC, that should be clearly admitted, explained, and documented. This can be especially misleading given Synapse's tendency not to re-evaluate anything it already processed. Advertising State DAGs intrinsically as a sufficient standalone solution causes some confusion to the serious reader. My MSC4521 aims optimistically to reconcile differences of 100,000 elements over populations of 10 million in about 5-10 seconds. Smaller reconciliations (like under 1000 differences) are nearly instant, under 200 ms—theoretically fast enough to keep all partitions unified in real-time (except for deliberate Byzantine defectors).
-
/get_missing_state_eventswill cache dedicated ETags and consume a homogeneous JSON structure... also allows homeserver admins to route state-DAG syncs to dedicated worker pools or apply custom reverse proxy rules and rate limits. Better to keep it separate. -
Merkle proofs can attest to the credibility of a path or series of binary hops. While state resolution must still be run (i.e., the corresponding event chain must be fully fetched and validated), a valid Merkle path is instantly more credible, worthy, and promising as a lead than an opaque, ostensibly Byzantine input.
A "graceful" fallback to linear walking
O(N)could just as easily be Byzantine. I could self-sign 10 million PDUs and stream them to you one after another, all from the same domain. Linear walks should probably be a worst-case fallback if all else fails.To avoid enforcing the full download back to
m.room.create, we can leverage the finality concepts you recently outlined in your PaPoC '26 ERA paper. By utilizing "epoch events" or anm.room.finalityseal issued by the finality arbiter (e.g., the immutable Creator role), we can certify or commit a given snapshot, allowing nodes to act as "thin servers" and trust the cryptographic seal.This perfectly aligns with the ERA paper's note that finalized events can be compacted. We could establish a precise cadence (e.g., checkpointing every 5000 state events and adjacent merge points), preemptively compress the deterministic JSON with
xzip, and seal the bundle cryptographically to maintain high availability indefinitely.For room versions or customizations not opting into a finality clause, any rebuilds triggered later by backdated admin events remain the server's responsibility to process from the conflict out to the frontier (existing implementations and runtime characteristics can be substantially improved, to the point of allowing arguably even the largest servers to rebuild huge rooms leisurely).
| - `org.matrix.msc3401.call.member` events. MSC3401 uses per-user room state to synchronise call info. | ||
| This _does_ scale with the number of users in the room and would be negatively affected by this | ||
| proposal, as all historical calls will need to be downloaded prior to joining the room. | ||
| - `m.space.child` events. Space rooms will be more expensive to join, though this proposal would |
There was a problem hiding this comment.
It may also be worth analyzing or mentioning m.space.parent. This introduces some cross-room validation complexity, per the spec:
To avoid situations where a room falsely claims it is part of a given space,
m.space.parentevents should be ignored unless one of the following is true:
- A corresponding
m.space.childevent can be found in the supposed parent space.- The sender of the
m.space.parentevent has sufficient power level in the supposed parent space to sendm.space.childstate events (there doesn't need to be a matching child event).https://spec.matrix.org/v1.2/client-server-api/#mspaceparent-relationships
As a detail, m.room.tombstone seems to have very low overhead (not having any forward extremities or being referenced). But is worth including for completeness.
Lastly, I'd like to bring up m.policy.rule.* and m.room.policy events. These events are very common in some rooms, and I'm curious if they have the potential to slow things down at all. Seems these events would carry the cost of standard membership events. Rooms today like the KDE help lounge (with over 500K membership churn due to their timeout/rejoin model) can be almost impossible to join and require significant disk/CPU usage if ever successfully joined.
Tested on NetBSD 10 amd64 with 2026Q2 environment. # Synapse 1.156.0 (2026-07-07) ## Features - Expose [MSC4354 Sticky Events](matrix-org/matrix-spec-proposals#4354) over [MSC4186 (Simplified) Sliding Sync](matrix-org/matrix-spec-proposals#4186). ([\#19591](element-hq/synapse#19591)) - Stabilize support for sending ephemeral events to application services, as per [MSC2409](matrix-org/matrix-spec-proposals#2409). Contributed by @jason-famedly @ Famedly. ([\#19758](element-hq/synapse#19758)) - Include `allowed_room_ids` in the `/summary` client-server API response for rooms with restricted join rules, as required by Matrix 1.15. Contributed by @FrenchGithubUser @famedly. ([\#19762](element-hq/synapse#19762)) - [MSC4140: Cancellable delayed events](matrix-org/matrix-spec-proposals#4140): Allow authentication on delayed event management endpoints (such as `/restart`) to bypass ratelimits for unauthenticated requests based on the client IP address. ([\#19794](element-hq/synapse#19794)) - Add new metric `synapse_non_deactivated_user_count` which tracks the number of non-deactivated users in the database, split by `app_service`. ([\#19848](element-hq/synapse#19848)) - The `GET /_matrix/client/unstable/org.matrix.msc1763/retention/configuration` endpoint is now provided when retention is enabled and `experimental_features.msc1763_enabled` is enabled, based on [MSC1763](matrix-org/matrix-spec-proposals#1763). ([\#19853](element-hq/synapse#19853)) - Add experimental support for [MSC4491: Invite reasons in room creation](matrix-org/matrix-spec-proposals#4491). ([\#19874](element-hq/synapse#19874)) # Synapse 1.155.0 (2026-06-16) # Synapse 1.154.0 (2026-06-04) ## Features - Add support for [MSC4452: Preview URL capabilities API](matrix-org/matrix-spec-proposals#4452) which exposes a `io.element.msc4452.preview_url` capability. If `experimental_features.msc4452_enabled` is `true`, the `/_matrix/(client/v1/media|media/v3)/preview_url` endpoint now responds with a 403 status code when the capability is disabled. ([\#19715](element-hq/synapse#19715)) # Synapse 1.153.0 (2026-05-19) ## Features - Make ACLs apply to EDUs per [MSC4163](matrix-org/matrix-spec-proposals#4163). ([\#18475](element-hq/synapse#18475)) - Stabilize [MSC3266: Room summary API](matrix-org/matrix-spec-proposals#3266), removing the experimental config flag `msc3266_enabled`. Contributed by @dasha-uwu. ([\#19720](element-hq/synapse#19720)) - Partial [MSC4311](matrix-org/matrix-spec-proposals#4311) implementation: `m.room.create` is now a required part of stripped `invite_state`/`knock_state` . Contributed by @FrenchGithubUser @famedly. ([\#19722](element-hq/synapse#19722)) - Expose `tombstoned` and `replacement_room` in room details on admin API endpoint `GET /_synapse/admin/v1/rooms/<room_id>`. Contributed by Noah Markert. ([\#19737](element-hq/synapse#19737)) # Synapse 1.152.1 (2026-05-07) # Synapse 1.152.0 (2026-04-28) ## Features - Add a ["Listing quarantined media changes" Admin API](https://element-hq.github.io/synapse/latest/admin_api/media_admin_api.html#listing-quarantined-media-changes) for retrieving a paginated record of when media became (un)quarantined. ([\#19558](element-hq/synapse#19558), [\#19677](element-hq/synapse#19677), [\#19694](element-hq/synapse#19694)) - Advertise [MSC4445](matrix-org/matrix-spec-proposals#4445) sync timeline order in `unstable_features`. ([\#19642](element-hq/synapse#19642)) - Report the Rust compiler version used in the Prometheus metrics. Contributed by Noah Markert. ([\#19643](element-hq/synapse#19643)) - Passthrough 'article' and 'profile' OpenGraph metadata on URL preview requests. ([\#19659](element-hq/synapse#19659)) - Add a way to re-sign local events with a new signing key. ([\#19668](element-hq/synapse#19668)) - Support [MSC4450: Identity Provider selection for User-Interactive Authentication with Legacy Single Sign-On](matrix-org/matrix-spec-proposals#4450). ([\#19693](element-hq/synapse#19693)) - Add experimental support for [MSC4242](matrix-org/matrix-spec-proposals#4242): State DAGs. Excludes federation support. ([\#19424](element-hq/synapse#19424)) - Adds [Admin API](https://element-hq.github.io/synapse/latest/usage/administration/admin_api/index.html) endpoints to list, fetch and delete user reports. ([\#19657](element-hq/synapse#19657)) - Reduce database disk space usage by pruning old rows from `device_lists_changes_in_room`. ([\#19473](element-hq/synapse#19473), [\#19709](element-hq/synapse#19709)) # Synapse 1.151.0 (2026-04-07) ## Features - Add stable support for [MSC4284](matrix-org/matrix-spec-proposals#4284) Policy Servers. ([\#19503](element-hq/synapse#19503)) - Update and stabilize support for [MSC2666](matrix-org/matrix-spec-proposals#2666): Get rooms in common with another user. Contributed by @tulir @ Beeper. ([\#19511](element-hq/synapse#19511)) - Updated experimental support for [MSC4388: Secure out-of-band channel for sign in with QR](matrix-org/matrix-spec-proposals#4388). ([\#19573](element-hq/synapse#19573)) - Stabilize `room_version` and `encryption` fields in the space/room `/hierarchy` API (part of [MSC3266](matrix-org/matrix-spec-proposals#3266)). ([\#19576](element-hq/synapse#19576)) - Introduce a [configuration option](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_authentication_service) to allow using HTTP/2 over plaintext when Synapse connects to Matrix Authentication Service. ([\#19586](element-hq/synapse#19586)) ## Deprecations and Removals - Remove support for [MSC3852: Expose user agent information on Device](matrix-org/matrix-spec-proposals#3852) as the MSC was closed. ([\#19430](element-hq/synapse#19430)) # Synapse 1.150.0 (2026-03-24) ## Features - Add experimental support for the [MSC4370](matrix-org/matrix-spec-proposals#4370) Federation API `GET /extremities` endpoint. ([\#19314](element-hq/synapse#19314)) - [MSC4140: Cancellable delayed events](matrix-org/matrix-spec-proposals#4140): When persisting a delayed event to the timeline, include its `delay_id` in the event's `unsigned` section in `/sync` responses to the event sender. ([\#19479](element-hq/synapse#19479)) - Expose [MSC4354 Sticky Events](matrix-org/matrix-spec-proposals#4354) over the legacy (v3) /sync API. ([\#19487](element-hq/synapse#19487)) - When Matrix Authentication Service (MAS) integration is enabled, allow MAS to set the user locked status in Synapse. ([\#19554](element-hq/synapse#19554))
|
|
||
| > 1) Is a valid event, otherwise it is dropped. For an event to be valid, it must contain a `room_id`, and it must comply with the event format of that room version. | ||
| > 2) Passes signature checks, otherwise it is dropped. | ||
| > 3) Passes hash checks, otherwise it is redacted before being processed further. |
There was a problem hiding this comment.
Hi, I am doing a deep dive on redactions in my state res library lately.
Since a new redaction algo was not specified or updated, and auth_events were gutted out in favor of prev_state_events, this would mean redactions strip and break a lot of things. Am I missing something?
We would need to clarify protected fields and hashing semantics.
Technically just overloading the old auth_events field name would be an easier refactor, at the expense of confusion/semantic poisoning (and possible resolution failures if the create event and room version can't be fetched)... for a year or two, mere presence of the prev_state_events will be a distinguishing factor (e.g., maps numerically to v13+ and state res v2.2+, where there is no v14 nor v2.3 yet).
I think some edge cases about how redacted state events with content: {} can influence fork resolution are worth adding, either as manual notes or formally as unit, or rather I guess Complement tests? I did a little work on an internal PR (against my own Complement fork's master/main). I'll invite you there, we can maybe discuss some ideas at leisure. I may be able to put a small demo together.
I have a feeling some of the unintuitive logic around the same-event-ID despite redaction and the hashes: { ... } logic/conundrums can be addressed and simplified by choosing the right Merkle tree construction and combining header leaves correctly, so content can be stripped without mutating event_root. Or content_hash can be checked if it matches the "empty commitment." This sounds like a big lift, but if redactions require a lot of work anyway we may want to do things a more "intuitive" way and refactor while we are at it (I'm not saying I'm married to the idea of Merkle trees/paths/proofs here, but it's certainly worth considering over the coming months so we can get more understanding of the benefits and any possible limits).
Starting a separate review for this, since I have a hunch redactions may deserve a dedicated conversation thread.
Rendered
Synapse impl:
Complement tests: matrix-org/complement#841
No client changes required as it's not client visible.