|
| 1 | +# MSC4222: Adding `state_after` to sync v2 |
| 2 | + |
| 3 | +The current sync v2 API does not differentiate between state events in the timeline and updates to state, and so can |
| 4 | +cause the client's view of the current state of the room to diverge from the actual state of the room. This is |
| 5 | +particularly problematic for use-cases that rely on state being consistent between different clients. |
| 6 | + |
| 7 | +This behavior stems from the fact that the clients update their view of the current state with state events that appear |
| 8 | +in the timeline. To handle gappy syncs, the `state` section includes state events that are from *before* the start of |
| 9 | +the timeline, and so are replaced by any matching state events in the timeline. This provides little opportunity for the |
| 10 | +server to ensure that the clients come to the correct conclusion about the current state of the room. |
| 11 | + |
| 12 | +In [MSC4186 - Simplified Sliding Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) this problem is |
| 13 | +solved by the equivalent `required_state` section including all state changes between the previous sync and the end of |
| 14 | +the current sync, and clients do not update their view of state based on entries in the timeline. |
| 15 | + |
| 16 | + |
| 17 | +## Proposal |
| 18 | + |
| 19 | +This change is gated behind the client adding a `?use_state_after=true` (the unstable name is |
| 20 | +`org.matrix.use_state_after`) query param. |
| 21 | + |
| 22 | +When enabled, the Homeserver will **omit** the `state` section in the room response sections. This is replaced by |
| 23 | +`state_after` (the unstable field name is `org.matrix.state_after`), which will include all state changes between the |
| 24 | +previous sync and the *end* of the timeline section of the current sync. This is in contrast to the old `state` section |
| 25 | +that only included state changes between the previous sync and the *start* of the timeline section. Note that this does |
| 26 | +mean that a new state event will (likely) appear in both the timeline and state sections of the response. |
| 27 | + |
| 28 | +This is basically the same as how state is returned in [MSC4186 - Simplified Sliding |
| 29 | +Sync](https://github.com/matrix-org/matrix-spec-proposals/pull/4186). |
| 30 | + |
| 31 | +State events that appear in the timeline section **MUST NOT** update the current state. The current state **MUST** only be |
| 32 | +updated with the contents of `state_after`. |
| 33 | + |
| 34 | +Clients can tell if the server supports this change by whether it returns a `state` or `state_after` section in the |
| 35 | +response. |
| 36 | + |
| 37 | +### Examples |
| 38 | + |
| 39 | +#### Example 1 \- Common case |
| 40 | + |
| 41 | +Let’s take a look at the common case of a state event getting sent down an incremental sync, which is non-gappy. |
| 42 | + |
| 43 | +<table> |
| 44 | +<tr><th>Previously</th><th>Proposed</th></tr> |
| 45 | +<tr> |
| 46 | +<td> |
| 47 | + |
| 48 | +```json |
| 49 | +{ |
| 50 | + "timeline": { |
| 51 | + "events": [ { |
| 52 | + "type": "org.matrix.example", |
| 53 | + "state_key": "" |
| 54 | + } ], |
| 55 | + "limited": false, |
| 56 | + }, |
| 57 | + "state": { |
| 58 | + "events": [] |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +</td> |
| 64 | +<td> |
| 65 | + |
| 66 | +```json |
| 67 | +{ |
| 68 | + "timeline": { |
| 69 | + "events": [ { |
| 70 | + "type": "org.matrix.example", |
| 71 | + "state_key": "" |
| 72 | + } ], |
| 73 | + "limited": false, |
| 74 | + }, |
| 75 | + "state_after": { |
| 76 | + "events": [ { |
| 77 | + "type": "org.matrix.example", |
| 78 | + "state_key": "" |
| 79 | + } ] |
| 80 | + } |
| 81 | +``` |
| 82 | + |
| 83 | +</td> |
| 84 | +</tr> |
| 85 | +</table> |
| 86 | + |
| 87 | +> [!NOTE] |
| 88 | +> In the proposed API the state event comes down both in the timeline section *and* the state section. |
| 89 | + |
| 90 | + |
| 91 | +#### Example 2 - Receiving “outdated” state |
| 92 | + |
| 93 | +Next, let’s look at what would happen if we receive a state event that does not take effect, i.e. that shouldn’t cause the client to update its state. |
| 94 | + |
| 95 | +<table> |
| 96 | +<tr><th>Previously</th><th>Proposed</th></tr> |
| 97 | +<tr> |
| 98 | +<td> |
| 99 | + |
| 100 | +```json |
| 101 | +{ |
| 102 | + "timeline": { |
| 103 | + "events": [ { |
| 104 | + "type": "org.matrix.example", |
| 105 | + "state_key": "" |
| 106 | + } ], |
| 107 | + "limited": false, |
| 108 | + }, |
| 109 | + "state": { |
| 110 | + "events": [] |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +</td> |
| 116 | +<td> |
| 117 | + |
| 118 | +```json |
| 119 | +{ |
| 120 | + "timeline": { |
| 121 | + "events": [ { |
| 122 | + "type": "org.matrix.example", |
| 123 | + "state_key": "" |
| 124 | + } ], |
| 125 | + "limited": false, |
| 126 | + }, |
| 127 | + "state_after": { |
| 128 | + "events": [] |
| 129 | + } |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +</td> |
| 134 | +</tr> |
| 135 | +</table> |
| 136 | + |
| 137 | +> [!IMPORTANT] |
| 138 | +> Both responses are the same, but the client **MUST NOT** update its state with the event. |
| 139 | + |
| 140 | + |
| 141 | +## Potential issues |
| 142 | + |
| 143 | +With the proposed API the common case for receiving a state update will cause the event to come down in both the |
| 144 | +`timeline` and `state` sections, potentially increasing bandwidth usage. However, it is common for the HTTP responses to |
| 145 | +be compressed, heavily reducing the impact of having duplicated data. |
| 146 | + |
| 147 | +Clients will not be able to tell when a state change happened within the timeline. This was used by some clients to |
| 148 | +render e.g. display names of users at the time they sent the message (rather than their current display name), though |
| 149 | +e.g. Element clients have moved away from this UX. This behavior can be replicated in the same way that clients dealt |
| 150 | +with messages received via pagination (i.e. calling `/messages`), by walking the timeline backwards and inspecting the |
| 151 | +`unsigned.prev_state` field. While this can lead to incorrect results, this is no worse than the previous situation. |
| 152 | + |
| 153 | + |
| 154 | +## Alternatives |
| 155 | + |
| 156 | +There are a number of ways of encoding the same information in different ways, for example the response could include |
| 157 | +both the `state` and a `state_delta` section, where `state_delta` would be any changes that needed to be applied to the |
| 158 | +client calculated state to correct it. However, since |
| 159 | +[MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) is likely to replace the sync v2 API, we may as |
| 160 | +well use the same mechanism. This also has the benefit of showing that the proposed API shape can be successfully |
| 161 | +implemented by clients. |
| 162 | + |
| 163 | +We could also do nothing, and instead wait for [MSC4186](https://github.com/matrix-org/matrix-spec-proposals/pull/4186) |
| 164 | +(or equivalent) to land and for clients to update to it. |
| 165 | + |
| 166 | + |
| 167 | +## Security considerations |
| 168 | + |
| 169 | +There are no security concerns with this proposal, as it simply encodes the same information sent do clients in a |
| 170 | +different way |
| 171 | + |
| 172 | +## Unstable prefix |
| 173 | + |
| 174 | +| Name | Stable prefix | Unstable prefix | |
| 175 | +| - | - | - | |
| 176 | +| Query param | `use_state_after` | `org.matrix.use_state_after` | |
| 177 | +| Room response field | `state_after` | `org.matrix.state_after` | |
| 178 | + |
| 179 | +## Dependencies |
| 180 | + |
| 181 | +None |
0 commit comments