Closes HNT-244. The following PR implements Space,Channel soft deletion using on-chain `disabled` flag scope to space, channel respectively. On message sync, dendrite will now gate disabled rooms by performing a leave on the user attempting to sync unless the user is the owner (more on this later). To re-join, given rooms (spaces,channels) are created by default using `invite` membership state, the owner will need to undo the on-chain `disabled` flag, setting it false then re-invite users that left the room as a side effect of it becoming disabled previously. The owner does not leave the space, channel because if they did then there would be no one left to invite users let alone themselves back in if the action is ever undone. What is not implemented in this PR: 1. **Transitive leaves on channels in a space** - If a space is disabled, users will leave the space but not the channels within the space. To allow for fully disabling a space and all its' channels, the client can offer a view to the owner that iterates over the channels and space to disable all on-chain. Furthermore, we could implement a batch on-chain method that fully disables all channels within a space (plus the space) in one on-chain call to save the owner gas. 2. **Data deletion** - No data is remove from the DAGs or on-chain. Therefore deletion is soft and reversible. 3. **New hook to check if a room is disabled** - the client can leverage existing on-chain public read only methods `getSpaceInfoBySpaceId`, `getChannelInfoByChannelId` to read the state of each in order to remove spaces, channels from a member's view that are disabled. |
||
|---|---|---|
| .. | ||
| consumers | ||
| internal | ||
| notifier | ||
| producers | ||
| routing | ||
| storage | ||
| streams | ||
| sync | ||
| types | ||
| README.md | ||
| syncapi.go | ||
| syncapi_test.go | ||
Sync API Server
This server is responsible for servicing /sync requests. It gets its data from the room server output log. Currently, the sync server will:
- Return a valid
/syncresponse for the user represented by the providedaccess_token. - Return a "complete sync" if no
sincevalue is provided, and return a validnext_batchtoken. This contains all rooms the user has been invited to or has joined. For joined rooms, this includes the complete current room state and the most recent 20 (hard-coded) events in the timeline. - For "incremental syncs" (a
sincevalue is provided), as you get invited to, join, or leave rooms they will be reflected correctly in the/syncresponse. - For very large state deltas, the
statesection of a room is correctly populated with the state of the room at the start of the timeline. - When you join a room, the
/syncwhich transitions your client to be "joined" will include the complete current room state as per the specification. - Only wake up user streams it needs to wake up.
- Honours the
timeoutquery parameter value.
Internals
When the server gets a /sync request, it needs to:
- Work out which rooms to return to the client.
- For each room, work out which events to return to the client.
The logic for working out which rooms is based on Synapse:
- Get the CURRENT joined room list for this user.
- Get membership list changes for this user between the provided stream position and now.
- For each room which has membership list changes:
- Check if the room is 'newly joined' (insufficient to just check for a join event because we allow dupe joins). If it is, then we need to send the full room state down (and 'limited' is always true).
- Check if user is still CURRENTLY invited to the room. If so, add room to 'invited' block.
- Check if the user is CURRENTLY left/banned. If so, add room to 'archived' block.
- Add joined rooms (joined room list)
For each room, the /sync response returns the most recent timeline events and the state of the room at the start of the timeline.
The logic for working out which events is not based entirely on Synapse code, as it is known broken with respect to working out
room state. In order to know which events to return, the server needs to calculate room state at various points in the history of
the room. For example, imagine a room with the following 15 events (letters are state events (updated via '), numbers are timeline events):
index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (1-based indexing as StreamPosition(0) represents no event)
timeline [A, B, C, D, 1, 2, 3, D', 4, D'', 5, B', D''', D'''', 6]
The current state of this room is: [A, B', C, D''''].
If this room was requested with ?since=14&limit=5 then 1 timeline event would be returned, the most recent one:
15
[ 6 ]
If this room was requested with ?since=9&limit=5 then 5 timeline events would be returned, the most recent ones:
11 12 13 14 15
[5, B', D''', D'''', 6]
The state of the room at the START of the timeline can be represented in 2 ways:
- The
full_statefrom index 0 :[A, B, C, D''](aka the state between 0-11 exclusive) - A partial state from index 9 :
[D''](aka the state between 9-11 exclusive)
Servers advance state events (e.g from D' to D'') based on the state conflict resolution algorithm.
You might think that you could advance the current state by just updating the entry for the (event type, state_key) tuple
for each state event, but this state can diverge from the state calculated using the state conflict resolution algorithm.
For example, if there are two "simultaneous" updates to the same state key, that is two updates at the same depth in the
event graph, then the final result of the state conflict resolution algorithm might not match the order the events appear
in the timeline.
The correct advancement for state events is represented by the AddsStateEventIDs and RemovesStateEventIDs that
are in OutputRoomEvents from the room server.
This version of the sync server uses very simple indexing to calculate room state at various points.
This is inefficient when a very old since value is provided, or the full_state is requested, as the state delta becomes
very large. This is mitigated slightly with indexes, but better data structures could be used in the future.
Known Issues
m.room.history_visibilityis not honoured: it is always treated as "shared".- All ephemeral events are not implemented (presence, typing, receipts).
- Account data (both user and room) is not implemented.
to_devicemessages are not implemented.- Back-pagination via
prev_batchis not implemented. - The
limitedflag can lie. - Filters are not honoured or implemented. The
limitfor each room is hard-coded to 20. - The
full_statequery parameter is not implemented. - The
set_presencequery parameter is not implemented. - "Ignored" users are not ignored.
- Redacted events are still sent to clients.
- Invites over federation (if it existed) won't work as they aren't "real" events and so won't be in the right tables.
invite_stateis not implemented (for similar reasons to the above point).- The current implementation scales badly when a very old
sincetoken is provided. - The entire current room state can be re-sent to the client if they send a duplicate "join" event which should be a no-op.