Merge pull request #1773 from matrix-org/travis/spec/rooms
Add a room version specification
This commit is contained in:
commit
6c7eea555a
9 changed files with 447 additions and 245 deletions
|
@ -457,7 +457,7 @@ def main(targets, dest_dir, keep_intermediates, substitutions):
|
||||||
|
|
||||||
rst_file = os.path.join(tmp_dir, "spec_%s.rst" % (target_name,))
|
rst_file = os.path.join(tmp_dir, "spec_%s.rst" % (target_name,))
|
||||||
if version_label:
|
if version_label:
|
||||||
d = os.path.join(dest_dir, target_name)
|
d = os.path.join(dest_dir, target_name.split('@')[0])
|
||||||
if not os.path.exists(d):
|
if not os.path.exists(d):
|
||||||
os.mkdir(d)
|
os.mkdir(d)
|
||||||
html_file = os.path.join(d, "%s.html" % version_label)
|
html_file = os.path.join(d, "%s.html" % version_label)
|
||||||
|
|
|
@ -17,8 +17,11 @@ from batesian.sections import Sections
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class MatrixSections(Sections):
|
class MatrixSections(Sections):
|
||||||
|
|
||||||
# pass through git ver so it'll be dropped in the input file
|
# pass through git ver so it'll be dropped in the input file
|
||||||
|
@ -28,26 +31,19 @@ class MatrixSections(Sections):
|
||||||
def render_git_rev(self):
|
def render_git_rev(self):
|
||||||
return self.units.get("git_version")["revision"]
|
return self.units.get("git_version")["revision"]
|
||||||
|
|
||||||
def render_client_server_changelog(self):
|
def render_changelogs(self):
|
||||||
|
rendered = {}
|
||||||
changelogs = self.units.get("changelogs")
|
changelogs = self.units.get("changelogs")
|
||||||
return changelogs["client_server"]
|
for spec, versioned in changelogs.items():
|
||||||
|
spec_var = "%s_changelog" % spec
|
||||||
# TODO: We should make this a generic variable instead of having to add functions all the time.
|
logger.info("Rendering changelog for spec: %s" % spec)
|
||||||
def render_push_gateway_changelog(self):
|
for version, changelog in versioned.items():
|
||||||
changelogs = self.units.get("changelogs")
|
version_var = "%s_%s" % (spec_var, version)
|
||||||
return changelogs["push_gateway"]
|
logger.info("Rendering changelog for %s" % version_var)
|
||||||
|
rendered[version_var] = changelog
|
||||||
def render_identity_service_changelog(self):
|
if version == "unstable":
|
||||||
changelogs = self.units.get("changelogs")
|
rendered[spec_var] = changelog
|
||||||
return changelogs["identity_service"]
|
return rendered
|
||||||
|
|
||||||
def render_server_server_changelog(self):
|
|
||||||
changelogs = self.units.get("changelogs")
|
|
||||||
return changelogs["server_server"]
|
|
||||||
|
|
||||||
def render_application_service_changelog(self):
|
|
||||||
changelogs = self.units.get("changelogs")
|
|
||||||
return changelogs["application_service"]
|
|
||||||
|
|
||||||
def _render_events(self, filterFn, sortFn):
|
def _render_events(self, filterFn, sortFn):
|
||||||
template = self.env.get_template("events.tmpl")
|
template = self.env.get_template("events.tmpl")
|
||||||
|
|
|
@ -906,11 +906,26 @@ class MatrixUnits(Units):
|
||||||
def load_changelogs(self):
|
def load_changelogs(self):
|
||||||
changelogs = {}
|
changelogs = {}
|
||||||
|
|
||||||
|
# Changelog generation is a bit complicated. We rely on towncrier to
|
||||||
|
# generate the unstable/current changelog, but otherwise use the RST
|
||||||
|
# edition to record historical changelogs. This is done by prepending
|
||||||
|
# the towncrier output to the RST in memory, then parsing the RST by
|
||||||
|
# hand. We parse the entire changelog to create a changelog for each
|
||||||
|
# version which may be of use in some APIs.
|
||||||
|
|
||||||
|
# Map specific headers to specific keys that'll be used eventually
|
||||||
|
# in variables. Things not listed here will get lowercased and formatted
|
||||||
|
# such that characters not [a-z0-9] will be replaced with an underscore.
|
||||||
|
keyword_versions = {
|
||||||
|
"Unreleased Changes": "unstable"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only generate changelogs for things that have an RST document
|
||||||
for f in os.listdir(CHANGELOG_DIR):
|
for f in os.listdir(CHANGELOG_DIR):
|
||||||
if not f.endswith(".rst"):
|
if not f.endswith(".rst"):
|
||||||
continue
|
continue
|
||||||
path = os.path.join(CHANGELOG_DIR, f)
|
path = os.path.join(CHANGELOG_DIR, f)
|
||||||
name = f[:-4]
|
name = f[:-4] # take off ".rst"
|
||||||
|
|
||||||
# If there's a directory with the same name, we'll try to generate
|
# If there's a directory with the same name, we'll try to generate
|
||||||
# a towncrier changelog and prepend it to the general changelog.
|
# a towncrier changelog and prepend it to the general changelog.
|
||||||
|
@ -959,15 +974,39 @@ class MatrixUnits(Units):
|
||||||
prev_line = line
|
prev_line = line
|
||||||
else: # have title, get body (stop on next title or EOF)
|
else: # have title, get body (stop on next title or EOF)
|
||||||
if re.match("^[=]{3,}$", line.strip()):
|
if re.match("^[=]{3,}$", line.strip()):
|
||||||
# we added the title in the previous iteration, pop it
|
# we hit another title, so pop the last line of
|
||||||
# then bail out.
|
# the changelog and record the changelog
|
||||||
changelog_lines.pop()
|
new_title = changelog_lines.pop()
|
||||||
break
|
if name not in changelogs:
|
||||||
|
changelogs[name] = {}
|
||||||
|
if title_part in keyword_versions:
|
||||||
|
title_part = keyword_versions[title_part]
|
||||||
|
title_part = title_part.strip().replace("^[a-zA-Z0-9]", "_").lower()
|
||||||
|
changelog = "".join(changelog_lines)
|
||||||
|
changelogs[name][title_part] = changelog
|
||||||
|
|
||||||
|
# reset for the next version
|
||||||
|
changelog_lines = []
|
||||||
|
title_part = new_title.strip()
|
||||||
|
continue
|
||||||
# Don't generate subheadings (we'll keep the title though)
|
# Don't generate subheadings (we'll keep the title though)
|
||||||
if re.match("^[-]{3,}$", line.strip()):
|
if re.match("^[-]{3,}$", line.strip()):
|
||||||
continue
|
continue
|
||||||
|
if line.strip().startswith(".. version: "):
|
||||||
|
# The changelog is directing us to use a different title
|
||||||
|
# for the changelog.
|
||||||
|
title_part = line.strip()[len(".. version: "):]
|
||||||
|
continue
|
||||||
|
if line.strip().startswith(".. "):
|
||||||
|
continue # skip comments
|
||||||
changelog_lines.append(" " + line + '\n')
|
changelog_lines.append(" " + line + '\n')
|
||||||
changelogs[name] = "".join(changelog_lines)
|
if len(changelog_lines) > 0 and title_part is not None:
|
||||||
|
if name not in changelogs:
|
||||||
|
changelogs[name] = {}
|
||||||
|
if title_part in keyword_versions:
|
||||||
|
title_part = keyword_versions[title_part]
|
||||||
|
changelog = "".join(changelog_lines)
|
||||||
|
changelogs[name][title_part.replace("^[a-zA-Z0-9]", "_").lower()] = changelog
|
||||||
|
|
||||||
return changelogs
|
return changelogs
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
Identifier Grammar
|
Identifier Grammar
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
Some identifiers are specific to given room versions, please refer to the
|
||||||
|
`room versions specification`_ for more information.
|
||||||
|
|
||||||
|
.. _`room versions specification`: ../index.html#room-versions
|
||||||
|
|
||||||
|
|
||||||
Server Name
|
Server Name
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -78,38 +84,6 @@ Some recommendations for a choice of server name follow:
|
||||||
* The length of the complete server name should not exceed 230 characters.
|
* The length of the complete server name should not exceed 230 characters.
|
||||||
* Server names should not use upper-case characters.
|
* Server names should not use upper-case characters.
|
||||||
|
|
||||||
|
|
||||||
Room Versions
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Room versions are used to change properties of rooms that may not be compatible
|
|
||||||
with other servers. For example, changing the rules for event authorization would
|
|
||||||
cause older servers to potentially end up in a split-brain situation due to them
|
|
||||||
not understanding the new rules.
|
|
||||||
|
|
||||||
A room version is defined as a string of characters which MUST NOT exceed 32
|
|
||||||
codepoints in length. Room versions MUST NOT be empty and SHOULD contain only
|
|
||||||
the characters ``a-z``, ``0-9``, ``.``, and ``-``.
|
|
||||||
|
|
||||||
Room versions are not intended to be parsed and should be treated as opaque
|
|
||||||
identifiers. Room versions consisting only of the characters ``0-9`` and ``.``
|
|
||||||
are reserved for future versions of the Matrix protocol.
|
|
||||||
|
|
||||||
The complete grammar for a legal room version is::
|
|
||||||
|
|
||||||
room_version = 1*room_version_char
|
|
||||||
room_version_char = DIGIT
|
|
||||||
/ %x61-7A ; a-z
|
|
||||||
/ "-" / "."
|
|
||||||
|
|
||||||
Examples of valid room versions are:
|
|
||||||
|
|
||||||
* ``1`` (would be reserved by the Matrix protocol)
|
|
||||||
* ``1.2`` (would be reserved by the Matrix protocol)
|
|
||||||
* ``1.2-beta``
|
|
||||||
* ``com.example.version``
|
|
||||||
|
|
||||||
|
|
||||||
Common Identifier Format
|
Common Identifier Format
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -418,6 +418,73 @@ dedicated API. The API is symmetrical to managing Profile data.
|
||||||
Would it really be overengineered to use the same API for both profile &
|
Would it really be overengineered to use the same API for both profile &
|
||||||
private user data, but with different ACLs?
|
private user data, but with different ACLs?
|
||||||
|
|
||||||
|
|
||||||
|
Room Versions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Rooms are central to how Matrix operates, and have strict rules for what
|
||||||
|
is allowed to be contained within them. Rooms can also have various
|
||||||
|
algorithms that handle different tasks, such as what to do when two or
|
||||||
|
more events collide in the underlying DAG. To allow rooms to be improved
|
||||||
|
upon through new algorithms or rules, "room versions" are employed to
|
||||||
|
manage a set of expectations for each room. New room versions are assigned
|
||||||
|
as needed.
|
||||||
|
|
||||||
|
There is no implicit ordering or hierarchy to room versions, and their principles
|
||||||
|
are immutable once placed in the specification. Although there is a recommended
|
||||||
|
set of versions, some rooms may benefit from features introduced by other versions.
|
||||||
|
Rooms move between different versions by "upgrading" to the desired version. Due
|
||||||
|
to versions not being ordered or hierarchical, this means a room can "upgrade"
|
||||||
|
from version 2 to version 1, if it is so desired.
|
||||||
|
|
||||||
|
Room version grammar
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Room versions are used to change properties of rooms that may not be compatible
|
||||||
|
with other servers. For example, changing the rules for event authorization would
|
||||||
|
cause older servers to potentially end up in a split-brain situation due to not
|
||||||
|
understanding the new rules.
|
||||||
|
|
||||||
|
A room version is defined as a string of characters which MUST NOT exceed 32
|
||||||
|
codepoints in length. Room versions MUST NOT be empty and SHOULD contain only
|
||||||
|
the characters ``a-z``, ``0-9``, ``.``, and ``-``.
|
||||||
|
|
||||||
|
Room versions are not intended to be parsed and should be treated as opaque
|
||||||
|
identifiers. Room versions consisting only of the characters ``0-9`` and ``.``
|
||||||
|
are reserved for future versions of the Matrix protocol.
|
||||||
|
|
||||||
|
The complete grammar for a legal room version is::
|
||||||
|
|
||||||
|
room_version = 1*room_version_char
|
||||||
|
room_version_char = DIGIT
|
||||||
|
/ %x61-7A ; a-z
|
||||||
|
/ "-" / "."
|
||||||
|
|
||||||
|
Examples of valid room versions are:
|
||||||
|
|
||||||
|
* ``1`` (would be reserved by the Matrix protocol)
|
||||||
|
* ``1.2`` (would be reserved by the Matrix protocol)
|
||||||
|
* ``1.2-beta``
|
||||||
|
* ``com.example.version``
|
||||||
|
|
||||||
|
Complete list of room versions
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Room versions are divided into two distinct groups: stable and unstable. Stable
|
||||||
|
room versions may be used by rooms safely. Unstable room versions are everything
|
||||||
|
else which is either not listed in the specification or flagged as unstable for
|
||||||
|
some other reason. Versions can switch between stable and unstable periodically
|
||||||
|
for a variety of reasons, including discovered security vulnerabilites and age.
|
||||||
|
|
||||||
|
Clients should not ask room administrators to upgrade their rooms if the room is
|
||||||
|
running a stable version. Servers SHOULD use room version 1 as the default room
|
||||||
|
version when creating new rooms.
|
||||||
|
|
||||||
|
The available room versions are:
|
||||||
|
|
||||||
|
* `Version 1 <rooms/v1.html>`_ - **Stable**. The current version of most rooms.
|
||||||
|
* `Version 2 <rooms/v2.html>`_ - **Stable**. Implements State Resolution Version 2.
|
||||||
|
|
||||||
Specification Versions
|
Specification Versions
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
98
specification/rooms/v1.rst
Normal file
98
specification/rooms/v1.rst
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
.. Copyright 2017,2019 New Vector Ltd
|
||||||
|
..
|
||||||
|
.. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
.. you may not use this file except in compliance with the License.
|
||||||
|
.. You may obtain a copy of the License at
|
||||||
|
..
|
||||||
|
.. http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
..
|
||||||
|
.. Unless required by applicable law or agreed to in writing, software
|
||||||
|
.. distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
.. See the License for the specific language governing permissions and
|
||||||
|
.. limitations under the License.
|
||||||
|
|
||||||
|
Room Version 1
|
||||||
|
==============
|
||||||
|
|
||||||
|
This room version is the first ever version for rooms, and contains the building
|
||||||
|
blocks for other room versions.
|
||||||
|
|
||||||
|
Server implementation components
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
The information contained in this section is strictly for server implementors.
|
||||||
|
Applications which use the Client-Server API are generally unaffected by the
|
||||||
|
details contained here, and can safely ignore their presence.
|
||||||
|
|
||||||
|
|
||||||
|
The algorithms defined here should only apply to version 1 rooms. Other algorithms
|
||||||
|
may be used by other room versions, and as such servers should be aware of which
|
||||||
|
version room they are dealing with prior to executing a given algorithm.
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
Although room version 1 is the most popular room version, it is known to have
|
||||||
|
undesirable effects. Servers implementing support for room version 1 should be
|
||||||
|
aware that restrictions should be generally relaxed and that inconsistencies
|
||||||
|
may occur until room version 2 (or later) is ready and adopted.
|
||||||
|
|
||||||
|
State resolution
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
This section documents the state resolution algorithm as implemented by
|
||||||
|
Synapse as of December 2017 (and therefore the de-facto Matrix protocol).
|
||||||
|
However, this algorithm is known to have some problems.
|
||||||
|
|
||||||
|
The room state :math:`S'(E)` after an event :math:`E` is defined in terms of
|
||||||
|
the room state :math:`S(E)` before :math:`E`, and depends on whether
|
||||||
|
:math:`E` is a state event or a message event:
|
||||||
|
|
||||||
|
* If :math:`E` is a message event, then :math:`S'(E) = S(E)`.
|
||||||
|
|
||||||
|
* If :math:`E` is a state event, then :math:`S'(E)` is :math:`S(E)`, except
|
||||||
|
that its entry corresponding to :math:`E`'s ``event_type`` and ``state_key``
|
||||||
|
is replaced by :math:`E`'s ``event_id``.
|
||||||
|
|
||||||
|
The room state :math:`S(E)` before :math:`E` is the *resolution* of the set of
|
||||||
|
states :math:`\{ S'(E'), S'(E''), … \}` consisting of the states after each of
|
||||||
|
:math:`E`'s ``prev_event``\s :math:`\{ E', E'', … \}`.
|
||||||
|
|
||||||
|
The *resolution* of a set of states is defined as follows. The resolved state
|
||||||
|
is built up in a number of passes; here we use :math:`R` to refer to the
|
||||||
|
results of the resolution so far.
|
||||||
|
|
||||||
|
* Start by setting :math:`R` to the union of the states to be resolved,
|
||||||
|
excluding any *conflicting* events.
|
||||||
|
|
||||||
|
* First we resolve conflicts between ``m.room.power_levels`` events. If there
|
||||||
|
is no conflict, this step is skipped, otherwise:
|
||||||
|
|
||||||
|
* Assemble all the ``m.room.power_levels`` events from the states to
|
||||||
|
be resolved into a list.
|
||||||
|
|
||||||
|
* Sort the list by ascending ``depth`` then descending ``sha1(event_id)``.
|
||||||
|
|
||||||
|
* Add the first event in the list to :math:`R`.
|
||||||
|
|
||||||
|
* For each subsequent event in the list, check that the event would be
|
||||||
|
allowed by the `authorization rules`_ for a room in state :math:`R`. If the
|
||||||
|
event would be allowed, then update :math:`R` with the event and continue
|
||||||
|
with the next event in the list. If it would not be allowed, stop and
|
||||||
|
continue below with ``m.room.join_rules`` events.
|
||||||
|
|
||||||
|
* Repeat the above process for conflicts between ``m.room.join_rules`` events.
|
||||||
|
|
||||||
|
* Repeat the above process for conflicts between ``m.room.member`` events.
|
||||||
|
|
||||||
|
* No other events affect the authorization rules, so for all other conflicts,
|
||||||
|
just pick the event with the highest depth and lowest ``sha1(event_id)`` that
|
||||||
|
passes authentication in :math:`R` and add it to :math:`R`.
|
||||||
|
|
||||||
|
A *conflict* occurs between states where those states have different
|
||||||
|
``event_ids`` for the same ``(state_type, state_key)``. The events thus
|
||||||
|
affected are said to be *conflicting* events.
|
||||||
|
|
||||||
|
|
||||||
|
.. _`authorization rules`: ../server_server/unstable.html#authorization-rules
|
202
specification/rooms/v2.rst
Normal file
202
specification/rooms/v2.rst
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
.. Copyright 2018-2019 New Vector Ltd
|
||||||
|
..
|
||||||
|
.. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
.. you may not use this file except in compliance with the License.
|
||||||
|
.. You may obtain a copy of the License at
|
||||||
|
..
|
||||||
|
.. http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
..
|
||||||
|
.. Unless required by applicable law or agreed to in writing, software
|
||||||
|
.. distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
.. See the License for the specific language governing permissions and
|
||||||
|
.. limitations under the License.
|
||||||
|
|
||||||
|
Room Version 2
|
||||||
|
==============
|
||||||
|
|
||||||
|
This room version builds off of `version 1 <v1.html>`_ with an improved state
|
||||||
|
resolution algorithm.
|
||||||
|
|
||||||
|
Server implementation components
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. WARNING::
|
||||||
|
The information contained in this section is strictly for server implementors.
|
||||||
|
Applications which use the Client-Server API are generally unaffected by the
|
||||||
|
details contained here, and can safely ignore their presence.
|
||||||
|
|
||||||
|
|
||||||
|
The algorithms defined here should only apply to version 2 rooms. Other algorithms
|
||||||
|
may be used by other room versions, and as such servers should be aware of which
|
||||||
|
version room they are dealing with prior to executing a given algorithm.
|
||||||
|
|
||||||
|
|
||||||
|
State resolution
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The room state :math:`S'(E)` after an event :math:`E` is defined in terms of
|
||||||
|
the room state :math:`S(E)` before :math:`E`, and depends on whether
|
||||||
|
:math:`E` is a state event or a message event:
|
||||||
|
|
||||||
|
* If :math:`E` is a message event, then :math:`S'(E) = S(E)`.
|
||||||
|
|
||||||
|
* If :math:`E` is a state event, then :math:`S'(E)` is :math:`S(E)`, except
|
||||||
|
that its entry corresponding to :math:`E`'s ``event_type`` and ``state_key``
|
||||||
|
is replaced by :math:`E`'s ``event_id``.
|
||||||
|
|
||||||
|
The room state :math:`S(E)` before :math:`E` is the *resolution* of the set of
|
||||||
|
states :math:`\{ S'(E_1), S'(E_2), … \}` consisting of the states after each of
|
||||||
|
:math:`E`'s ``prev_event``\s :math:`\{ E_1, E_2, … \}`, where the resolution of
|
||||||
|
a set of states is given in the algorithm below.
|
||||||
|
|
||||||
|
Definitions
|
||||||
|
+++++++++++
|
||||||
|
|
||||||
|
The state resolution algorithm for version 2 rooms uses the following
|
||||||
|
definitions, given the set of room states :math:`\{ S_1, S_2, \ldots \}`:
|
||||||
|
|
||||||
|
Power events
|
||||||
|
A *power event* is a state event with type ``m.room.power_levels`` or
|
||||||
|
``m.room.join_rules``, or a state event with type ``m.room.member`` where the
|
||||||
|
``membership`` is ``leave`` or ``ban`` and the ``sender`` does not match the
|
||||||
|
``state_key``. The idea behind this is that power events are events that might
|
||||||
|
remove someone's ability to do something in the room.
|
||||||
|
|
||||||
|
Unconflicted state map and conflicted state set
|
||||||
|
The *unconflicted state map* is the state where the value of each key exists
|
||||||
|
and is the same in each state :math:`S_i`. The *conflicted state set* is the
|
||||||
|
set of all other state events. Note that the unconflicted state map only has
|
||||||
|
one event per ``(event_type, state_key)``, whereas the conflicted state set
|
||||||
|
may have multiple events.
|
||||||
|
|
||||||
|
Auth difference
|
||||||
|
The *auth difference* is calculated by first calculating the full auth chain
|
||||||
|
for each state :math:`S_i`, that is the union of the auth chains for each
|
||||||
|
event in :math:`S_i`, and then taking every event that doesn't appear in
|
||||||
|
every auth chain. If :math:`C_i` is the full auth chain of :math:`S_i`, then
|
||||||
|
the auth difference is :math:`\cup C_i - \cap C_i`.
|
||||||
|
|
||||||
|
Full conflicted set
|
||||||
|
The *full conflicted set* is the union of the conflicted state set and the
|
||||||
|
auth difference.
|
||||||
|
|
||||||
|
Reverse topological power ordering
|
||||||
|
The *reverse topological power ordering* of a set of events is the
|
||||||
|
lexicographically smallest topological ordering based on the DAG formed by
|
||||||
|
auth events. The reverse topological power ordering is ordered from earliest
|
||||||
|
event to latest. For comparing two topological orderings to determine which
|
||||||
|
is the lexicographically smallest, the following comparison relation on
|
||||||
|
events is used: for events :math:`x` and :math:`y`, :math:`x<y` if
|
||||||
|
|
||||||
|
1. :math:`x`'s sender has *greater* power level than :math:`y`'s sender,
|
||||||
|
when looking at their respective ``auth_event``\s; or
|
||||||
|
2. the senders have the same power level, but :math:`x`'s
|
||||||
|
``origin_server_ts`` is *less* than :math:`y`'s ``origin_server_ts``; or
|
||||||
|
3. the senders have the same power level and the events have the same
|
||||||
|
``origin_server_ts``, but :math:`x`'s ``event_id`` is *less* than
|
||||||
|
:math:`y`'s ``event_id``.
|
||||||
|
|
||||||
|
The reverse topological power ordering can be found by sorting the events
|
||||||
|
using Kahn's algorithm for topological sorting, and at each step selecting,
|
||||||
|
among all the candidate vertices, the smallest vertex using the above
|
||||||
|
comparison relation.
|
||||||
|
|
||||||
|
Mainline ordering
|
||||||
|
Given an ``m.room.power_levels`` event :math:`P`, the *mainline of* :math:`P`
|
||||||
|
is the list of events generated by starting with :math:`P` and recursively
|
||||||
|
taking the ``m.room.power_levels`` events from the ``auth_events``, ordered
|
||||||
|
such that :math:`P` is last. Given another event :math:`e`, the *closest
|
||||||
|
mainline event to* :math:`e` is the first event encountered in the mainline
|
||||||
|
when iteratively descending through the ``m.room.power_levels`` events in the
|
||||||
|
``auth_events`` starting at :math:`e`. If no mainline event is encountered
|
||||||
|
when iteratively descending through the ``m.room.power_levels`` events, then
|
||||||
|
the closest mainline event to :math:`e` can be considered to be a dummy event
|
||||||
|
that is before any other event in the mainline of :math:`P` for the purposes
|
||||||
|
of condition 1 below.
|
||||||
|
|
||||||
|
The *mainline ordering based on* :math:`P` of a set of events is the
|
||||||
|
ordering, from smallest to largest, using the following comparision relation
|
||||||
|
on events: for events :math:`x` and :math:`y`, :math:`x<y` if
|
||||||
|
|
||||||
|
1. the closest mainline event to :math:`x` appears *before* the closest
|
||||||
|
mainline event to :math:`y`; or
|
||||||
|
2. the closest mainline events are the same, but :math:`x`\'s
|
||||||
|
``origin_server_ts`` is *less* than :math:`y`\'s ``origin_server_ts``; or
|
||||||
|
3. the closest mainline events are the same and the events have the same
|
||||||
|
``origin_server_ts``, but :math:`x`\'s ``event_id`` is *less* than
|
||||||
|
:math:`y`\'s ``event_id``.
|
||||||
|
|
||||||
|
Iterative auth checks
|
||||||
|
The *iterative auth checks algorithm* takes as input an initial room state
|
||||||
|
and a sorted list of state events, and constructs a new room state by
|
||||||
|
iterating through the event list and applying the state event to the room
|
||||||
|
state if the state event is allowed by the `authorization rules`_. If the
|
||||||
|
state event is not allowed by the authorization rules, then the event is
|
||||||
|
ignored. If a ``(event_type, state_key)`` key that is required for checking
|
||||||
|
the authorization rules is not present in the state, then the appropriate
|
||||||
|
state event from the event's ``auth_events`` is used if the auth event is
|
||||||
|
not rejected.
|
||||||
|
|
||||||
|
Algorithm
|
||||||
|
+++++++++
|
||||||
|
|
||||||
|
The *resolution* of a set of states is obtained as follows:
|
||||||
|
|
||||||
|
1. Take all *power events* and any events in their auth chains, recursively,
|
||||||
|
that appear in the *full conflicted set* and order them by the *reverse
|
||||||
|
topological power ordering*.
|
||||||
|
2. Apply the *iterative auth checks algorithm* on the *unconflicted state map*
|
||||||
|
and the list of events from the previous step to get a partially resolved
|
||||||
|
state.
|
||||||
|
3. Take all remaining events that weren't picked in step 1 and order them by
|
||||||
|
the mainline ordering based on the power level in the partially resolved
|
||||||
|
state obtained in step 2.
|
||||||
|
4. Apply the *iterative auth checks algorithm* on the partial resolved
|
||||||
|
state and the list of events from the previous step.
|
||||||
|
5. Update the result by replacing any event with the event with the same key
|
||||||
|
from the *unconflicted state map*, if such an event exists, to get the final
|
||||||
|
resolved state.
|
||||||
|
|
||||||
|
|
||||||
|
.. _`authorization rules`: ../server_server/unstable.html#authorization-rules
|
||||||
|
|
||||||
|
Rejected events
|
||||||
|
+++++++++++++++
|
||||||
|
|
||||||
|
Events that have been rejected due to failing auth based on the state at the
|
||||||
|
event (rather than based on their auth chain) are handled as usual by the
|
||||||
|
algorithm, unless otherwise specified.
|
||||||
|
|
||||||
|
Note that no events rejected due to failure to auth against their auth chain
|
||||||
|
should appear in the process, as they should not appear in state (the algorithm
|
||||||
|
only uses events that appear in either the state sets or in the auth chain of
|
||||||
|
the events in the state sets).
|
||||||
|
|
||||||
|
.. admonition:: Rationale
|
||||||
|
|
||||||
|
This helps ensure that different servers' view of state is more likely to
|
||||||
|
converge, since rejection state of an event may be different. This can happen if
|
||||||
|
a third server gives an incorrect version of the state when a server joins a
|
||||||
|
room via it (either due to being faulty or malicious). Convergence of state is a
|
||||||
|
desirable property as it ensures that all users in the room have a (mostly)
|
||||||
|
consistent view of the state of the room. If the view of the state on different
|
||||||
|
servers diverges it can lead to bifurcation of the room due to e.g. servers
|
||||||
|
disagreeing on who is in the room.
|
||||||
|
|
||||||
|
Intuitively, using rejected events feels dangerous, however:
|
||||||
|
|
||||||
|
1. Servers cannot arbitrarily make up state, since they still need to pass the
|
||||||
|
auth checks based on the event's auth chain (e.g. they can't grant themselves
|
||||||
|
power levels if they didn't have them before).
|
||||||
|
2. For a previously rejected event to pass auth there must be a set of state
|
||||||
|
that allows said event. A malicious server could therefore produce a
|
||||||
|
fork where it claims the state is that particular set of state, duplicate the
|
||||||
|
rejected event to point to that fork, and send the event. The
|
||||||
|
duplicated event would then pass the auth checks. Ignoring rejected events
|
||||||
|
would therefore not eliminate any potential attack vectors.
|
||||||
|
|
||||||
|
|
||||||
|
Rejected auth events are deliberately excluded from use in the iterative auth
|
||||||
|
checks, as auth events aren't re-authed (although non-auth events are) during
|
||||||
|
the iterative auth checks.
|
|
@ -752,191 +752,8 @@ is at the top)::
|
||||||
Suppose E3 and E4 are both ``m.room.name`` events which set the name of the
|
Suppose E3 and E4 are both ``m.room.name`` events which set the name of the
|
||||||
room. What should the name of the room be at E5?
|
room. What should the name of the room be at E5?
|
||||||
|
|
||||||
Servers should follow one of the following recursively-defined algorithms,
|
The algorithm to be used for state resolution depends on the room version. For
|
||||||
depending on the room version, to determine the room state at a given point on
|
a description of each room version's algorithm, please see the `room version specification`_.
|
||||||
the DAG.
|
|
||||||
|
|
||||||
State resolution algorithm for version 2 rooms
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The room state :math:`S'(E)` after an event :math:`E` is defined in terms of
|
|
||||||
the room state :math:`S(E)` before :math:`E`, and depends on whether
|
|
||||||
:math:`E` is a state event or a message event:
|
|
||||||
|
|
||||||
* If :math:`E` is a message event, then :math:`S'(E) = S(E)`.
|
|
||||||
|
|
||||||
* If :math:`E` is a state event, then :math:`S'(E)` is :math:`S(E)`, except
|
|
||||||
that its entry corresponding to :math:`E`'s ``event_type`` and ``state_key``
|
|
||||||
is replaced by :math:`E`'s ``event_id``.
|
|
||||||
|
|
||||||
The room state :math:`S(E)` before :math:`E` is the *resolution* of the set of
|
|
||||||
states :math:`\{ S'(E_1), S'(E_2), … \}` consisting of the states after each of
|
|
||||||
:math:`E`'s ``prev_event``\s :math:`\{ E_1, E_2, … \}`, where the resolution of
|
|
||||||
a set of states is given in the algorithm below.
|
|
||||||
|
|
||||||
Definitions
|
|
||||||
+++++++++++
|
|
||||||
|
|
||||||
The state resolution algorithm for version 2 rooms uses the following
|
|
||||||
definitions, given the set of room states :math:`\{ S_1, S_2, \ldots \}`:
|
|
||||||
|
|
||||||
Power events
|
|
||||||
A *power event* is a state event with type ``m.room.power_levels`` or
|
|
||||||
``m.room.join_rules``, or a state event with type ``m.room.member`` where the
|
|
||||||
``membership`` is ``leave`` or ``ban`` and the ``sender`` does not match the
|
|
||||||
``state_key``. The idea behind this is that power events are events that have
|
|
||||||
may remove someone's ability to do something in the room.
|
|
||||||
|
|
||||||
Unconflicted state map and conflicted state set
|
|
||||||
The *unconflicted state map* is the state where the value of each key exists
|
|
||||||
and is the same in each state :math:`S_i`. The *conflicted state set* is the
|
|
||||||
set of all other state events. Note that the unconflicted state map only has
|
|
||||||
one event per ``(event_type, state_key)``, whereas the conflicted state set
|
|
||||||
may have multiple events.
|
|
||||||
|
|
||||||
Auth difference
|
|
||||||
The *auth difference* is calculated by first calculating the full auth chain
|
|
||||||
for each state :math:`S_i`, that is the union of the auth chains for each
|
|
||||||
event in :math:`S_i`, and then taking every event that doesn't appear in
|
|
||||||
every auth chain. If :math:`C_i` is the full auth chain of :math:`S_i`, then
|
|
||||||
the auth difference is :math:`\cup C_i - \cap C_i`.
|
|
||||||
|
|
||||||
Full conflicted set
|
|
||||||
The *full conflicted set* is the union of the conflicted state set and the
|
|
||||||
auth difference.
|
|
||||||
|
|
||||||
Reverse topological power ordering
|
|
||||||
The *reverse topological power ordering* of a set of events is the
|
|
||||||
lexicographically smallest topological ordering based on the DAG formed by
|
|
||||||
auth events. The reverse topological power ordering is ordered from earliest
|
|
||||||
event to latest. For comparing two topological orderings to determine which
|
|
||||||
is the lexicographically smallest, the following comparison relation on
|
|
||||||
events is used: for events :math:`x` and :math:`y`, :math:`x<y` if
|
|
||||||
|
|
||||||
1. :math:`x`'s sender has *greater* power level than :math:`y`'s sender,
|
|
||||||
when looking at their respective ``auth_event``\s; or
|
|
||||||
2. the senders have the same power level, but :math:`x`'s
|
|
||||||
``origin_server_ts`` is *less* than :math:`y`'s ``origin_server_ts``; or
|
|
||||||
3. the senders have the same power level and the events have the same
|
|
||||||
``origin_server_ts``, but :math:`x`'s ``event_id`` is *less* than
|
|
||||||
:math:`y`'s ``event_id``.
|
|
||||||
|
|
||||||
The reverse topological power ordering can be found by sorting the events
|
|
||||||
using Kahn's algorithm for topological sorting, and at each step selecting,
|
|
||||||
among all the candidate vertices, the smallest vertex using the above
|
|
||||||
comparison relation.
|
|
||||||
|
|
||||||
Mainline ordering
|
|
||||||
Given an ``m.room.power_levels`` event :math:`P`, the *mainline of* :math:`P`
|
|
||||||
is the list of events generated by starting with :math:`P` and recursively
|
|
||||||
taking the ``m.room.power_levels`` events from the ``auth_events``, ordered
|
|
||||||
such that :math:`P` is last. Given another event :math:`e`, the *closest
|
|
||||||
mainline event to* :math:`e` is the first event encountered in the mainline
|
|
||||||
when iteratively descending through the ``m.room.power_levels`` events in the
|
|
||||||
``auth_events`` starting at :math:`e`. If no mainline event is encountered
|
|
||||||
when iteratively descending through the ``m.room.power_levels`` events, then
|
|
||||||
the closest mainline event to :math:`e` can be considered to be a dummy event
|
|
||||||
that is before any other event in the mainline of :math:`P` for the purposes
|
|
||||||
of condition 1 below.
|
|
||||||
|
|
||||||
The *mainline ordering based on* :math:`P` of a set of events is the
|
|
||||||
ordering, from smallest to largest, using the following comparision relation
|
|
||||||
on events: for events :math:`x` and :math:`y`, :math:`x<y` if
|
|
||||||
|
|
||||||
1. the closest mainline event to :math:`x` appears *before* the closest
|
|
||||||
mainline event to :math:`y`; or
|
|
||||||
2. the closest mainline events are the same, but :math:`x`\'s
|
|
||||||
``origin_server_ts`` is *less* than :math:`y`\'s ``origin_server_ts``; or
|
|
||||||
3. the closest mainline events are the same and the events have the same
|
|
||||||
``origin_server_ts``, but :math:`x`\'s ``event_id`` is *less* than
|
|
||||||
:math:`y`\'s ``event_id``.
|
|
||||||
|
|
||||||
Iterative auth checks
|
|
||||||
The *iterative auth checks algorithm* takes as input an initial room state
|
|
||||||
and a sorted list of state events, and constructs a new room state by
|
|
||||||
iterating through the event list and applying the state event to the room
|
|
||||||
state if the state event is allowed by the `authorization rules`_. If the
|
|
||||||
state event is not allowed by the authorization rules, then the event is
|
|
||||||
ignored. If a ``(event_type, state_key)`` key that is required for checking
|
|
||||||
the authorization rules is not present in the state, then the appropriate
|
|
||||||
state event from the event's ``auth_events`` is used.
|
|
||||||
|
|
||||||
Algorithm
|
|
||||||
+++++++++
|
|
||||||
|
|
||||||
The *resolution* of a set of states is obtained as follows:
|
|
||||||
|
|
||||||
1. Take all *power events* and any events in their auth chains, recursively,
|
|
||||||
that appear in the *full conflicted set* and order them by the *reverse
|
|
||||||
topological power ordering*.
|
|
||||||
2. Apply the *iterative auth checks algorithm* on the *unconflicted state map*
|
|
||||||
and the list of events from the previous step to get a partially resolved
|
|
||||||
state.
|
|
||||||
3. Take all remaining events that weren't picked in step 1 and order them by
|
|
||||||
the mainline ordering based on the power level in the partially resolved
|
|
||||||
state obtained in step 2.
|
|
||||||
4. Apply the *iterative auth checks algorithm* on the partial resolved
|
|
||||||
state and the list of events from the previous step.
|
|
||||||
5. Update the result by replacing any event with the event with the same key
|
|
||||||
from the *unconflicted state map*, if such an event exists, to get the final
|
|
||||||
resolved state.
|
|
||||||
|
|
||||||
State resolution algorithm for version 1 rooms
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. WARNING::
|
|
||||||
This section documents the state resolution algorithm as implemented by
|
|
||||||
Synapse as of December 2017 (and therefore the de-facto Matrix protocol).
|
|
||||||
However, this algorithm is known to have some problems.
|
|
||||||
|
|
||||||
The room state :math:`S'(E)` after an event :math:`E` is defined in terms of
|
|
||||||
the room state :math:`S(E)` before :math:`E`, and depends on whether
|
|
||||||
:math:`E` is a state event or a message event:
|
|
||||||
|
|
||||||
* If :math:`E` is a message event, then :math:`S'(E) = S(E)`.
|
|
||||||
|
|
||||||
* If :math:`E` is a state event, then :math:`S'(E)` is :math:`S(E)`, except
|
|
||||||
that its entry corresponding to :math:`E`'s ``event_type`` and ``state_key``
|
|
||||||
is replaced by :math:`E`'s ``event_id``.
|
|
||||||
|
|
||||||
The room state :math:`S(E)` before :math:`E` is the *resolution* of the set of
|
|
||||||
states :math:`\{ S'(E'), S'(E''), … \}` consisting of the states after each of
|
|
||||||
:math:`E`'s ``prev_event``\s :math:`\{ E', E'', … \}`.
|
|
||||||
|
|
||||||
The *resolution* of a set of states is defined as follows. The resolved state
|
|
||||||
is built up in a number of passes; here we use :math:`R` to refer to the
|
|
||||||
results of the resolution so far.
|
|
||||||
|
|
||||||
* Start by setting :math:`R` to the union of the states to be resolved,
|
|
||||||
excluding any *conflicting* events.
|
|
||||||
|
|
||||||
* First we resolve conflicts between ``m.room.power_levels`` events. If there
|
|
||||||
is no conflict, this step is skipped, otherwise:
|
|
||||||
|
|
||||||
* Assemble all the ``m.room.power_levels`` events from the states to
|
|
||||||
be resolved into a list.
|
|
||||||
|
|
||||||
* Sort the list by ascending ``depth`` then descending ``sha1(event_id)``.
|
|
||||||
|
|
||||||
* Add the first event in the list to :math:`R`.
|
|
||||||
|
|
||||||
* For each subsequent event in the list, check that the event would be
|
|
||||||
allowed by the `authorization rules`_ for a room in state :math:`R`. If the
|
|
||||||
event would be allowed, then update :math:`R` with the event and continue
|
|
||||||
with the next event in the list. If it would not be allowed, stop and
|
|
||||||
continue below with ``m.room.join_rules`` events.
|
|
||||||
|
|
||||||
* Repeat the above process for conflicts between ``m.room.join_rules`` events.
|
|
||||||
|
|
||||||
* Repeat the above process for conflicts between ``m.room.member`` events.
|
|
||||||
|
|
||||||
* No other events affect the authorization rules, so for all other conflicts,
|
|
||||||
just pick the event with the highest depth and lowest ``sha1(event_id)`` that
|
|
||||||
passes authentication in :math:`R` and add it to :math:`R`.
|
|
||||||
|
|
||||||
A *conflict* occurs between states where those states have different
|
|
||||||
``event_ids`` for the same ``(state_type, state_key)``. The events thus
|
|
||||||
affected are said to be *conflicting* events.
|
|
||||||
|
|
||||||
|
|
||||||
Backfilling and retrieving missing events
|
Backfilling and retrieving missing events
|
||||||
|
@ -1500,3 +1317,4 @@ Example code
|
||||||
.. _`Checking for a signature`: ../appendices.html#checking-for-a-signature
|
.. _`Checking for a signature`: ../appendices.html#checking-for-a-signature
|
||||||
.. _`Device Management module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#device-management
|
.. _`Device Management module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#device-management
|
||||||
.. _`End-to-End Encryption module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#end-to-end-encryption
|
.. _`End-to-End Encryption module`: ../client_server/%CLIENT_RELEASE_LABEL%.html#end-to-end-encryption
|
||||||
|
.. _`room version specification`: ../index.html#room-versions
|
||||||
|
|
|
@ -26,6 +26,14 @@ targets:
|
||||||
files:
|
files:
|
||||||
- push_gateway.rst
|
- push_gateway.rst
|
||||||
version_label: "%PUSH_GATEWAY_RELEASE_LABEL%"
|
version_label: "%PUSH_GATEWAY_RELEASE_LABEL%"
|
||||||
|
rooms@v1: # this is translated to be rooms/v1.html
|
||||||
|
files:
|
||||||
|
- rooms/v1.rst
|
||||||
|
version_label: v1
|
||||||
|
rooms@v2: # this is translated to be rooms/v2.html
|
||||||
|
files:
|
||||||
|
- rooms/v2.rst
|
||||||
|
version_label: v2
|
||||||
appendices:
|
appendices:
|
||||||
files:
|
files:
|
||||||
- appendices.rst
|
- appendices.rst
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue