summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-xsetup.py1
-rw-r--r--synapse/api/auth.py22
-rw-r--r--synapse/api/constants.py9
-rw-r--r--synapse/api/events/factory.py2
-rw-r--r--synapse/crypto/event_signing.py41
-rw-r--r--synapse/events/__init__.py136
-rw-r--r--synapse/events/builder.py69
-rw-r--r--synapse/events/snapshot.py63
-rw-r--r--synapse/events/utils.py82
-rw-r--r--synapse/events/validator.py58
-rw-r--r--synapse/federation/replication.py30
-rw-r--r--synapse/handlers/_base.py100
-rw-r--r--synapse/handlers/directory.py28
-rw-r--r--synapse/handlers/federation.py28
-rw-r--r--synapse/handlers/message.py25
-rw-r--r--synapse/handlers/presence.py38
-rw-r--r--synapse/handlers/profile.py33
-rw-r--r--synapse/handlers/room.py150
-rw-r--r--synapse/handlers/typing.py4
-rw-r--r--synapse/rest/admin.py2
-rw-r--r--synapse/rest/base.py2
-rw-r--r--synapse/rest/login.py4
-rw-r--r--synapse/rest/presence.py4
-rw-r--r--synapse/rest/room.py112
-rw-r--r--synapse/server.py19
-rw-r--r--synapse/state.py18
-rw-r--r--synapse/storage/__init__.py19
-rw-r--r--synapse/storage/_base.py77
-rw-r--r--synapse/storage/schema/im.sql10
-rw-r--r--synapse/storage/signatures.py19
-rw-r--r--synapse/types.py21
31 files changed, 865 insertions, 361 deletions
diff --git a/setup.py b/setup.py
index 9b38f790b9..d1b8f0680a 100755
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ setup(
         "pynacl",
         "daemonize",
         "py-bcrypt",
+        "frozendict>=0.4",
     ],
     dependency_links=[
         "https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2",
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 2b0475543d..5261c3e3bf 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -351,7 +351,7 @@ class Auth(object):
         return self.store.is_server_admin(user)
 
     @defer.inlineCallbacks
-    def add_auth_events(self, event):
+    def get_auth_events(self, event, current_state):
         if event.type == RoomCreateEvent.TYPE:
             event.auth_events = []
             return
@@ -359,19 +359,19 @@ class Auth(object):
         auth_events = []
 
         key = (RoomPowerLevelsEvent.TYPE, "", )
-        power_level_event = event.old_state_events.get(key)
+        power_level_event = current_state.get(key)
 
         if power_level_event:
             auth_events.append(power_level_event.event_id)
 
         key = (RoomJoinRulesEvent.TYPE, "", )
-        join_rule_event = event.old_state_events.get(key)
+        join_rule_event = current_state.get(key)
 
         key = (RoomMemberEvent.TYPE, event.user_id, )
-        member_event = event.old_state_events.get(key)
+        member_event = current_state.get(key)
 
         key = (RoomCreateEvent.TYPE, "", )
-        create_event = event.old_state_events.get(key)
+        create_event = current_state.get(key)
         if create_event:
             auth_events.append(create_event.event_id)
 
@@ -393,17 +393,11 @@ class Auth(object):
             if member_event.content["membership"] == Membership.JOIN:
                 auth_events.append(member_event.event_id)
 
-        hashes = yield self.store.get_event_reference_hashes(
+        auth_events = yield self.store.add_event_hashes(
             auth_events
         )
-        hashes = [
-            {
-                k: encode_base64(v) for k, v in h.items()
-                if k == "sha256"
-            }
-            for h in hashes
-        ]
-        event.auth_events = zip(auth_events, hashes)
+
+        defer.returnValue(auth_events)
 
     @log_function
     def _can_send_event(self, event, auth_events):
diff --git a/synapse/api/constants.py b/synapse/api/constants.py
index 3cafff0e32..acf50e42ab 100644
--- a/synapse/api/constants.py
+++ b/synapse/api/constants.py
@@ -59,3 +59,12 @@ class LoginType(object):
     EMAIL_URL = u"m.login.email.url"
     EMAIL_IDENTITY = u"m.login.email.identity"
     RECAPTCHA = u"m.login.recaptcha"
+
+
+class EventTypes(object):
+    Member = "m.room.member"
+    Create = "m.room.create"
+    JoinRules = "m.room.join_rules"
+    PowerLevels = "m.room.power_levels"
+    Aliases = "m.room.aliases"
+    Redaction = "m.room.redaction"
\ No newline at end of file
diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py
index a1ec708a81..1b84e2b445 100644
--- a/synapse/api/events/factory.py
+++ b/synapse/api/events/factory.py
@@ -58,7 +58,7 @@ class EventFactory(object):
 
         local_part = str(int(self.clock.time())) + i + random_string(5)
 
-        e_id = EventID.create_local(local_part, self.hs)
+        e_id = EventID.create(local_part, self.hs.hostname)
 
         return e_id.to_string()
 
diff --git a/synapse/crypto/event_signing.py b/synapse/crypto/event_signing.py
index a9d8953239..b189f0bb2b 100644
--- a/synapse/crypto/event_signing.py
+++ b/synapse/crypto/event_signing.py
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 
-from synapse.api.events.utils import prune_event
+from synapse.events.utils import prune_event
 from syutil.jsonutil import encode_canonical_json
 from syutil.base64util import encode_base64, decode_base64
 from syutil.crypto.jsonsign import sign_json
@@ -29,17 +29,17 @@ logger = logging.getLogger(__name__)
 
 def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
     """Check whether the hash for this PDU matches the contents"""
-    computed_hash = _compute_content_hash(event, hash_algorithm)
-    logger.debug("Expecting hash: %s", encode_base64(computed_hash.digest()))
-    if computed_hash.name not in event.hashes:
+    name, expected_hash = compute_content_hash(event, hash_algorithm)
+    logger.debug("Expecting hash: %s", encode_base64(expected_hash))
+    if name not in event.hashes:
         raise SynapseError(
             400,
             "Algorithm %s not in hashes %s" % (
-                computed_hash.name, list(event.hashes),
+                name, list(event.hashes),
             ),
             Codes.UNAUTHORIZED,
         )
-    message_hash_base64 = event.hashes[computed_hash.name]
+    message_hash_base64 = event.hashes[name.name]
     try:
         message_hash_bytes = decode_base64(message_hash_base64)
     except:
@@ -48,10 +48,10 @@ def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
             "Invalid base64: %s" % (message_hash_base64,),
             Codes.UNAUTHORIZED,
         )
-    return message_hash_bytes == computed_hash.digest()
+    return message_hash_bytes == expected_hash
 
 
-def _compute_content_hash(event, hash_algorithm):
+def compute_content_hash(event, hash_algorithm):
     event_json = event.get_pdu_json()
     event_json.pop("age_ts", None)
     event_json.pop("unsigned", None)
@@ -59,8 +59,11 @@ def _compute_content_hash(event, hash_algorithm):
     event_json.pop("hashes", None)
     event_json.pop("outlier", None)
     event_json.pop("destinations", None)
+
     event_json_bytes = encode_canonical_json(event_json)
-    return hash_algorithm(event_json_bytes)
+
+    hashed = hash_algorithm(event_json_bytes)
+    return (hashed.name, hashed.digest())
 
 
 def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
@@ -86,20 +89,20 @@ def compute_event_signature(event, signature_name, signing_key):
 
 def add_hashes_and_signatures(event, signature_name, signing_key,
                               hash_algorithm=hashlib.sha256):
-    if hasattr(event, "old_state_events"):
-        state_json_bytes = encode_canonical_json(
-            [e.event_id for e in event.old_state_events.values()]
-        )
-        hashed = hash_algorithm(state_json_bytes)
-        event.state_hash = {
-            hashed.name: encode_base64(hashed.digest())
-        }
+    # if hasattr(event, "old_state_events"):
+    #     state_json_bytes = encode_canonical_json(
+    #         [e.event_id for e in event.old_state_events.values()]
+    #     )
+    #     hashed = hash_algorithm(state_json_bytes)
+    #     event.state_hash = {
+    #         hashed.name: encode_base64(hashed.digest())
+    #     }
 
-    hashed = _compute_content_hash(event, hash_algorithm=hash_algorithm)
+    name, digest = compute_content_hash(event, hash_algorithm=hash_algorithm)
 
     if not hasattr(event, "hashes"):
         event.hashes = {}
-    event.hashes[hashed.name] = encode_base64(hashed.digest())
+    event.hashes[name] = encode_base64(digest)
 
     event.signatures = compute_event_signature(
         event,
diff --git a/synapse/events/__init__.py b/synapse/events/__init__.py
new file mode 100644
index 0000000000..58edf2bc8f
--- /dev/null
+++ b/synapse/events/__init__.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket 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.
+
+from frozendict import frozendict
+
+
+def _freeze(o):
+    if isinstance(o, dict):
+        return frozendict({k: _freeze(v) for k,v in o.items()})
+
+    if isinstance(o, basestring):
+        return o
+
+    try:
+        return tuple([_freeze(i) for i in o])
+    except TypeError:
+        pass
+
+    return o
+
+
+class _EventInternalMetadata(object):
+    def __init__(self, internal_metadata_dict):
+        self.__dict__ = internal_metadata_dict
+
+    def get_dict(self):
+        return dict(self.__dict__)
+
+
+def _event_dict_property(key):
+        def getter(self):
+            return self._event_dict[key]
+
+        def setter(self, v):
+            self._event_dict[key] = v
+
+        def delete(self):
+            del self._event_dict[key]
+
+        return property(
+            getter,
+            setter,
+            delete,
+        )
+
+
+class EventBase(object):
+    def __init__(self, event_dict, signatures={}, unsigned={},
+                 internal_metadata_dict={}):
+        self.signatures = signatures
+        self.unsigned = unsigned
+
+        self._event_dict = event_dict
+
+        self.internal_metadata = _EventInternalMetadata(
+            internal_metadata_dict
+        )
+
+    auth_events = _event_dict_property("auth_events")
+    content = _event_dict_property("content")
+    event_id = _event_dict_property("event_id")
+    hashes = _event_dict_property("hashes")
+    origin = _event_dict_property("origin")
+    prev_events = _event_dict_property("prev_events")
+    prev_state = _event_dict_property("prev_state")
+    room_id = _event_dict_property("room_id")
+    sender = _event_dict_property("sender")
+    state_key = _event_dict_property("state_key")
+    type = _event_dict_property("type")
+    user_id = _event_dict_property("sender")
+
+    def is_state(self):
+        return hasattr(self, "state_key")
+
+    def get_dict(self):
+        d = dict(self._event_dict)
+        d.update({
+            "signatures": self.signatures,
+            "unsigned": self.unsigned,
+        })
+
+        return d
+
+    def get_internal_metadata_dict(self):
+        return self.internal_metadata.get_dict()
+
+    def get_pdu_json(self, time_now=None):
+        pdu_json = self.get_dict()
+
+        if time_now is not None and "age_ts" in pdu_json["unsigned"]:
+            age = time_now - pdu_json["unsigned"]["age_ts"]
+            pdu_json.setdefault("unsigned", {})["age"] = int(age)
+            del pdu_json["unsigned"]["age_ts"]
+
+        return pdu_json
+
+    def __set__(self, instance, value):
+        raise AttributeError("Unrecognized attribute %s" % (instance,))
+
+
+class FrozenEvent(EventBase):
+    def __init__(self, event_dict, signatures={}, unsigned={}):
+        event_dict = dict(event_dict)
+
+        signatures.update(event_dict.pop("signatures", {}))
+        unsigned.update(event_dict.pop("unsigned", {}))
+
+        frozen_dict = _freeze(event_dict)
+
+        super(FrozenEvent, self).__init__(
+            frozen_dict,
+            signatures=signatures,
+            unsigned=unsigned
+        )
+
+    @staticmethod
+    def from_event(event):
+        e = FrozenEvent(
+            event.get_pdu_json()
+        )
+
+        e.internal_metadata = event.internal_metadata
+
+        return e
diff --git a/synapse/events/builder.py b/synapse/events/builder.py
new file mode 100644
index 0000000000..0b8caf9318
--- /dev/null
+++ b/synapse/events/builder.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket 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.
+
+from . import EventBase, FrozenEvent
+
+from synapse.types import EventID
+
+from synapse.util.stringutils import random_string
+
+
+class EventBuilder(EventBase):
+    def __init__(self, key_values={}):
+        super(EventBuilder, self).__init__(
+            key_values,
+        )
+
+    def update_event_key(self, key, value):
+        self._event_dict[key] = value
+
+    def update_event_keys(self, other_dict):
+        self._event_dict.update(other_dict)
+
+    def build(self):
+        return FrozenEvent.from_event(self)
+
+
+class EventBuilderFactory(object):
+    def __init__(self, clock, hostname):
+        self.clock = clock
+        self.hostname = hostname
+
+        self.event_id_count = 0
+
+    def create_event_id(self):
+        i = str(self.event_id_count)
+        self.event_id_count += 1
+
+        local_part = str(int(self.clock.time())) + i + random_string(5)
+
+        e_id = EventID.create(local_part, self.hostname)
+
+        return e_id.to_string()
+
+    def new(self, key_values={}):
+        if "event_id" not in key_values:
+            key_values["event_id"] = self.create_event_id()
+
+        time_now = self.clock.time_msec()
+
+        key_values.setdefault("origin", self.hostname)
+        key_values.setdefault("origin_server_ts", time_now)
+
+        if "unsigned" in key_values:
+            age = key_values["unsigned"].pop("age", 0)
+            key_values["unsigned"].setdefault("age_ts", time_now - age)
+
+        return EventBuilder(key_values=key_values,)
\ No newline at end of file
diff --git a/synapse/events/snapshot.py b/synapse/events/snapshot.py
new file mode 100644
index 0000000000..ca15ec09ae
--- /dev/null
+++ b/synapse/events/snapshot.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket 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.
+
+from twisted.internet import defer
+
+
+class EventSnapshot(object):
+    def __init__(self, prev_events, depth, current_state,
+                 current_state_group):
+        self._prev_events = prev_events
+        self._depth = depth
+        self._current_state = current_state
+        self._current_state_group = current_state_group
+
+
+class EventCache(object):
+    def __init__(self, store):
+        self._store = store
+
+        self._cache = {}
+
+    @defer.inlineCallbacks
+    def load_event(self, event_id):
+        event = self._cache.get(event_id, None)
+
+        if not event:
+            event = yield self._store.get_event(
+                event_id,
+                allow_none=True
+            )
+
+            if event:
+                self._cache[event_id] = event
+
+        defer.returnValue(event)
+
+    def load_event_from_cache(self, event_id):
+        return self._cache.get(event_id, None)
+
+    def add_to_cache(self, *events):
+        self._cache.update({
+            event.event_id: event
+            for event in events
+        })
+
+
+class EventContext(object):
+
+    def __init__(self, current_state, auth_events):
+        self.current_state = current_state
+        self.auth_events = auth_events
diff --git a/synapse/events/utils.py b/synapse/events/utils.py
new file mode 100644
index 0000000000..412f690f08
--- /dev/null
+++ b/synapse/events/utils.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket 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.
+
+from synapse.api.constants import EventTypes
+
+
+def prune_event(event):
+    """ Returns a pruned version of the given event, which removes all keys we
+    don't know about or think could potentially be dodgy.
+
+    This is used when we "redact" an event. We want to remove all fields that
+    the user has specified, but we do want to keep necessary information like
+    type, state_key etc.
+    """
+    event_type = event.type
+
+    allowed_keys = [
+        "event_id",
+        "sender",
+        "room_id",
+        "hashes",
+        "signatures",
+        "content",
+        "type",
+        "state_key",
+        "depth",
+        "prev_events",
+        "prev_state",
+        "auth_events",
+        "origin",
+        "origin_server_ts",
+    ]
+
+    new_content = {}
+
+    def add_fields(*fields):
+        for field in fields:
+            if field in event.content:
+                new_content[field] = event.content[field]
+
+    if event_type == EventTypes.Member:
+        add_fields("membership")
+    elif event_type == EventTypes.Create:
+        add_fields("creator")
+    elif event_type == EventTypes.JoinRules:
+        add_fields("join_rule")
+    elif event_type == EventTypes.PowerLevels:
+        add_fields(
+            "users",
+            "users_default",
+            "events",
+            "events_default",
+            "events_default",
+            "state_default",
+            "ban",
+            "kick",
+            "redact",
+        )
+    elif event_type == EventTypes.Aliases:
+        add_fields("aliases")
+
+    allowed_fields = {
+        k: v
+        for k, v in event.get_dict().items()
+        if k in allowed_keys
+    }
+
+    allowed_fields["content"] = new_content
+
+    return type(event)(allowed_fields)
diff --git a/synapse/events/validator.py b/synapse/events/validator.py
new file mode 100644
index 0000000000..7dc9506ec4
--- /dev/null
+++ b/synapse/events/validator.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Copyright 2014 OpenMarket 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.
+
+from synapse.types import EventID, RoomID, UserID
+from synapse.api.errors import SynapseError
+
+
+class EventValidator(object):
+
+    def validate(self, event):
+        EventID.from_string(event.event_id)
+        RoomID.from_string(event.room_id)
+
+        hasattr(event, "auth_events")
+        hasattr(event, "content")
+        hasattr(event, "hashes")
+        hasattr(event, "origin")
+        hasattr(event, "prev_events")
+        hasattr(event, "prev_events")
+        hasattr(event, "sender")
+        hasattr(event, "type")
+
+        # Check that the following keys have string values
+        strings = [
+            "origin",
+            "sender",
+            "type",
+        ]
+
+        if hasattr(event, "state_key"):
+            strings.append("state_key")
+
+        for s in strings:
+            if not isinstance(getattr(event, s), basestring):
+                raise SynapseError(400, "Not '%s' a string type" % (s,))
+
+        # Check that the following keys have dictionary values
+        # TODO
+
+        # Check that the following keys have the correct format for DAGs
+        # TODO
+
+    def validate_new(self, event):
+        self.validate(event)
+
+        UserID.from_string(event.sender)
\ No newline at end of file
diff --git a/synapse/federation/replication.py b/synapse/federation/replication.py
index 01f87fe423..b11df9e5c6 100644
--- a/synapse/federation/replication.py
+++ b/synapse/federation/replication.py
@@ -74,6 +74,7 @@ class ReplicationLayer(object):
         self._clock = hs.get_clock()
 
         self.event_factory = hs.get_event_factory()
+        self.event_builder_factory = hs.get_event_builder_factory()
 
     def set_handler(self, handler):
         """Sets the handler that the replication layer will use to communicate
@@ -112,7 +113,7 @@ class ReplicationLayer(object):
         self.query_handlers[query_type] = handler
 
     @log_function
-    def send_pdu(self, pdu):
+    def send_pdu(self, pdu, destinations):
         """Informs the replication layer about a new PDU generated within the
         home server that should be transmitted to others.
 
@@ -131,7 +132,7 @@ class ReplicationLayer(object):
         logger.debug("[%s] transaction_layer.enqueue_pdu... ", pdu.event_id)
 
         # TODO, add errback, etc.
-        self._transaction_queue.enqueue_pdu(pdu, order)
+        self._transaction_queue.enqueue_pdu(pdu, destinations, order)
 
         logger.debug(
             "[%s] transaction_layer.enqueue_pdu... done",
@@ -658,19 +659,14 @@ class ReplicationLayer(object):
         return "<ReplicationLayer(%s)>" % self.server_name
 
     def event_from_pdu_json(self, pdu_json, outlier=False):
-        #TODO: Check we have all the PDU keys here
-        pdu_json.setdefault("hashes", {})
-        pdu_json.setdefault("signatures", {})
-        sender = pdu_json.pop("sender", None)
-        if sender is not None:
-            pdu_json["user_id"] = sender
-        state_hash = pdu_json.get("unsigned", {}).pop("state_hash", None)
-        if state_hash is not None:
-            pdu_json["state_hash"] = state_hash
-        return self.event_factory.create_event(
-            pdu_json["type"], outlier=outlier, **pdu_json
+        builder = self.event_builder_factory.new(
+            pdu_json
         )
 
+        builder.internal_metadata = outlier
+
+        return builder.build()
+
 
 class _TransactionQueue(object):
     """This class makes sure we only have one transaction in flight at
@@ -705,15 +701,13 @@ class _TransactionQueue(object):
 
     @defer.inlineCallbacks
     @log_function
-    def enqueue_pdu(self, pdu, order):
+    def enqueue_pdu(self, pdu, destinations, order):
         # We loop through all destinations to see whether we already have
         # a transaction in progress. If we do, stick it in the pending_pdus
         # table and we'll get back to it later.
 
-        destinations = set([
-            d for d in pdu.destinations
-            if d != self.server_name
-        ])
+        destinations = set(destinations)
+        destinations.remove(self.server_name)
 
         logger.debug("Sending to: %s", str(destinations))
 
diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py
index 15adc9dc2c..4052d0e1e7 100644
--- a/synapse/handlers/_base.py
+++ b/synapse/handlers/_base.py
@@ -15,11 +15,13 @@
 
 from twisted.internet import defer
 
-from synapse.api.errors import LimitExceededError
+from synapse.api.errors import LimitExceededError, SynapseError
 from synapse.util.async import run_on_reactor
 from synapse.crypto.event_signing import add_hashes_and_signatures
 from synapse.api.events.room import RoomMemberEvent
-from synapse.api.constants import Membership
+from synapse.api.constants import Membership, EventTypes
+
+from synapse.events.snapshot import EventSnapshot, EventContext
 
 import logging
 
@@ -44,6 +46,8 @@ class BaseHandler(object):
         self.signing_key = hs.config.signing_key[0]
         self.server_name = hs.hostname
 
+        self.event_builder_factory = hs.get_event_builder_factory()
+
     def ratelimit(self, user_id):
         time_now = self.clock.time()
         allowed, time_allowed = self.ratelimiter.send_message(
@@ -57,6 +61,98 @@ class BaseHandler(object):
             )
 
     @defer.inlineCallbacks
+    def _create_new_client_event(self, builder):
+        latest_ret = yield self.store.get_latest_events_in_room(
+            builder.room_id,
+        )
+
+        depth = max([d for _, _, d in latest_ret])
+        prev_events = [(e, h) for e, h, _ in latest_ret]
+
+        state_handler = self.state_handler
+        if builder.is_state():
+            ret = yield state_handler.resolve_state_groups(
+                [e for e, _ in prev_events],
+                event_type=builder.event_type,
+                state_key=builder.state_key,
+            )
+
+            group, curr_state, prev_state = ret
+
+            prev_state = yield self.store.add_event_hashes(
+                prev_state
+            )
+
+            builder.prev_state = prev_state
+        else:
+            group, curr_state, _ = yield state_handler.resolve_state_groups(
+                [e for e, _ in prev_events],
+            )
+
+        builder.internal_metadata.state_group = group
+
+        builder.prev_events = prev_events
+        builder.depth = depth
+
+        auth_events = yield self.auth.get_auth_events(builder, curr_state)
+
+        builder.update_event_key("auth_events", auth_events)
+
+        add_hashes_and_signatures(
+            builder, self.server_name, self.signing_key
+        )
+
+        event = builder.build()
+
+        auth_ids = zip(*auth_events)[0]
+        curr_auth_events = {
+            k: v
+            for k, v in curr_state.items()
+            if v.event_id in auth_ids
+        }
+
+        context = EventContext(
+            current_state=curr_state,
+            auth_events=curr_auth_events,
+        )
+
+        defer.returnValue(
+            (event, context,)
+        )
+
+    @defer.inlineCallbacks
+    def handle_new_client_event(self, event, context, extra_destinations=[],
+                                extra_users=[], suppress_auth=False):
+        # We now need to go and hit out to wherever we need to hit out to.
+
+        if not suppress_auth:
+            self.auth.check(event, auth_events=context.auth_events)
+
+        yield self.store.persist_event(event)
+
+        destinations = set(extra_destinations)
+        for k, s in context.current_state.items():
+            try:
+                if k[0] == EventTypes.Member:
+                    if s.content["membership"] == Membership.JOIN:
+                        destinations.add(
+                            self.hs.parse_userid(s.state_key).domain
+                        )
+            except SynapseError:
+                logger.warn(
+                    "Failed to get destination from event %s", s.event_id
+                )
+
+        yield self.notifier.on_new_room_event(event, extra_users=extra_users)
+
+        federation_handler = self.hs.get_handlers().federation_handler
+        yield federation_handler.handle_new_event(
+            event,
+            None,
+            destinations=destinations,
+        )
+
+    @defer.inlineCallbacks
     def _on_new_room_event(self, event, snapshot, extra_destinations=[],
                            extra_users=[], suppress_auth=False,
                            do_invite_host=None):
diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py
index 3b37e49e6f..76fb897f20 100644
--- a/synapse/handlers/directory.py
+++ b/synapse/handlers/directory.py
@@ -40,7 +40,7 @@ class DirectoryHandler(BaseHandler):
 
         # TODO(erikj): Do auth.
 
-        if not room_alias.is_mine:
+        if not self.hs.is_mine(room_alias):
             raise SynapseError(400, "Room alias must be local")
             # TODO(erikj): Change this.
 
@@ -64,7 +64,7 @@ class DirectoryHandler(BaseHandler):
     def delete_association(self, user_id, room_alias):
         # TODO Check if server admin
 
-        if not room_alias.is_mine:
+        if not self.hs.is_mine(room_alias):
             raise SynapseError(400, "Room alias must be local")
 
         room_id = yield self.store.delete_room_alias(room_alias)
@@ -75,7 +75,7 @@ class DirectoryHandler(BaseHandler):
     @defer.inlineCallbacks
     def get_association(self, room_alias):
         room_id = None
-        if room_alias.is_mine:
+        if self.hs.is_mine(room_alias):
             result = yield self.store.get_association_from_room_alias(
                 room_alias
             )
@@ -123,7 +123,7 @@ class DirectoryHandler(BaseHandler):
     @defer.inlineCallbacks
     def on_directory_query(self, args):
         room_alias = self.hs.parse_roomalias(args["room_alias"])
-        if not room_alias.is_mine:
+        if not self.hs.is_mine(room_alias):
             raise SynapseError(
                 400, "Room Alias is not hosted on this Home Server"
             )
@@ -148,16 +148,12 @@ class DirectoryHandler(BaseHandler):
     def send_room_alias_update_event(self, user_id, room_id):
         aliases = yield self.store.get_aliases_for_room(room_id)
 
-        event = self.event_factory.create_event(
-            etype=RoomAliasesEvent.TYPE,
-            state_key=self.hs.hostname,
-            room_id=room_id,
-            user_id=user_id,
-            content={"aliases": aliases},
-        )
-
-        snapshot = yield self.store.snapshot_room(event)
+        msg_handler = self.hs.get_handlers().message_handler
+        yield msg_handler.handle_event({
+            "type": RoomAliasesEvent.TYPE,
+            "state_key": self.hs.hostname,
+            "room_id": room_id,
+            "sender": user_id,
+            "content": {"aliases": aliases},
+        })
 
-        yield self._on_new_room_event(
-            event, snapshot, extra_users=[user_id], suppress_auth=True
-        )
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 925eb5376e..b4a28ea3cb 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -76,7 +76,7 @@ class FederationHandler(BaseHandler):
 
     @log_function
     @defer.inlineCallbacks
-    def handle_new_event(self, event, snapshot):
+    def handle_new_event(self, event, snapshot, destinations):
         """ Takes in an event from the client to server side, that has already
         been authed and handled by the state module, and sends it to any
         remote home servers that may be interested.
@@ -92,12 +92,7 @@ class FederationHandler(BaseHandler):
 
         yield run_on_reactor()
 
-        pdu = event
-
-        if not hasattr(pdu, "destinations") or not pdu.destinations:
-            pdu.destinations = []
-
-        yield self.replication_layer.send_pdu(pdu)
+        yield self.replication_layer.send_pdu(event, destinations)
 
     @log_function
     @defer.inlineCallbacks
@@ -426,16 +421,17 @@ class FederationHandler(BaseHandler):
         join event for the room and return that. We don *not* persist or
         process it until the other server has signed it and sent it back.
         """
-        event = self.event_factory.create_event(
-            etype=RoomMemberEvent.TYPE,
-            content={"membership": Membership.JOIN},
-            room_id=context,
-            user_id=user_id,
-            state_key=user_id,
-        )
+        builder = self.event_builder_factory.new({
+            "type": RoomMemberEvent.TYPE,
+            "content": {"membership": Membership.JOIN},
+            "room_id": context,
+            "sender": user_id,
+            "state_key": user_id,
+        })
 
-        snapshot = yield self.store.snapshot_room(event)
-        snapshot.fill_out_prev_events(event)
+        event, context = yield self._create_new_client_event(
+            builder=builder,
+        )
 
         yield self.state_handler.annotate_event_with_state(event)
         yield self.auth.add_auth_events(event)
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 42dc4d46f3..485d8e8179 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -15,7 +15,7 @@
 
 from twisted.internet import defer
 
-from synapse.api.constants import Membership
+from synapse.api.constants import EventTypes, Membership
 from synapse.api.errors import RoomError
 from synapse.streams.config import PaginationConfig
 from synapse.util.logcontext import PreserveLoggingContext
@@ -79,7 +79,7 @@ class MessageHandler(BaseHandler):
         self.ratelimit(event.user_id)
         # TODO(paul): Why does 'event' not have a 'user' object?
         user = self.hs.parse_userid(event.user_id)
-        assert user.is_mine, "User must be our own: %s" % (user,)
+        assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
 
         snapshot = yield self.store.snapshot_room(event)
 
@@ -134,6 +134,27 @@ class MessageHandler(BaseHandler):
         defer.returnValue(chunk)
 
     @defer.inlineCallbacks
+    def handle_event(self, event_dict):
+        builder = self.event_builder_factory.new(event_dict)
+
+        event, context = yield self._create_new_client_event(
+            builder=builder,
+        )
+
+        # TODO: self.validator.validate(event)
+
+        if event.type == EventTypes.Member:
+            member_handler = self.hs.get_handlers().room_member_handler
+            yield member_handler.change_membership(event, context)
+        else:
+            yield self.handle_new_client_event(
+                event=event,
+                context=context,
+            )
+
+        defer.returnValue(event)
+
+    @defer.inlineCallbacks
     def store_room_data(self, event=None):
         """ Stores data for a room.
 
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index 84a039489f..f3abfecdee 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -147,7 +147,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def is_presence_visible(self, observer_user, observed_user):
-        assert(observed_user.is_mine)
+        assert(self.hs.is_mine(observed_user))
 
         if observer_user == observed_user:
             defer.returnValue(True)
@@ -165,7 +165,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_state(self, target_user, auth_user, as_event=False):
-        if target_user.is_mine:
+        if self.hs.is_mine(target_user):
             visible = yield self.is_presence_visible(
                 observer_user=auth_user,
                 observed_user=target_user
@@ -212,7 +212,7 @@ class PresenceHandler(BaseHandler):
         # TODO (erikj): Turn this back on. Why did we end up sending EDUs
         # everywhere?
 
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
@@ -291,7 +291,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def user_joined_room(self, user, room_id):
-        if user.is_mine:
+        if self.hs.is_mine(user):
             statuscache = self._get_or_make_usercache(user)
 
             # No actual update but we need to bump the serial anyway for the
@@ -309,7 +309,7 @@ class PresenceHandler(BaseHandler):
         rm_handler = self.homeserver.get_handlers().room_member_handler
         curr_users = yield rm_handler.get_room_members(room_id)
 
-        for local_user in [c for c in curr_users if c.is_mine]:
+        for local_user in [c for c in curr_users if self.hs.is_mine(c)]:
             self.push_update_to_local_and_remote(
                 observed_user=local_user,
                 users_to_push=[user],
@@ -318,14 +318,14 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def send_invite(self, observer_user, observed_user):
-        if not observer_user.is_mine:
+        if not self.hs.is_mine(observer_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         yield self.store.add_presence_list_pending(
             observer_user.localpart, observed_user.to_string()
         )
 
-        if observed_user.is_mine:
+        if self.hs.is_mine(observed_user):
             yield self.invite_presence(observed_user, observer_user)
         else:
             yield self.federation.send_edu(
@@ -339,7 +339,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def _should_accept_invite(self, observed_user, observer_user):
-        if not observed_user.is_mine:
+        if not self.hs.is_mine(observed_user):
             defer.returnValue(False)
 
         row = yield self.store.has_presence_state(observed_user.localpart)
@@ -359,7 +359,7 @@ class PresenceHandler(BaseHandler):
                 observed_user.localpart, observer_user.to_string()
             )
 
-        if observer_user.is_mine:
+        if self.hs.is_mine(observer_user):
             if accept:
                 yield self.accept_presence(observed_user, observer_user)
             else:
@@ -396,7 +396,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def drop(self, observed_user, observer_user):
-        if not observer_user.is_mine:
+        if not self.hs.is_mine(observer_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         yield self.store.del_presence_list(
@@ -410,7 +410,7 @@ class PresenceHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_presence_list(self, observer_user, accepted=None):
-        if not observer_user.is_mine:
+        if not self.hs.is_mine(observer_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         presence = yield self.store.get_presence_list(
@@ -465,7 +465,7 @@ class PresenceHandler(BaseHandler):
         )
 
         for target_user in target_users:
-            if target_user.is_mine:
+            if self.hs.is_mine(target_user):
                 self._start_polling_local(user, target_user)
 
                 # We want to tell the person that just came online
@@ -477,7 +477,7 @@ class PresenceHandler(BaseHandler):
                 )
 
         deferreds = []
-        remote_users = [u for u in target_users if not u.is_mine]
+        remote_users = [u for u in target_users if not self.hs.is_mine(u)]
         remoteusers_by_domain = partition(remote_users, lambda u: u.domain)
         # Only poll for people in our get_presence_list
         for domain in remoteusers_by_domain:
@@ -520,7 +520,7 @@ class PresenceHandler(BaseHandler):
     def stop_polling_presence(self, user, target_user=None):
         logger.debug("Stop polling for presence from %s", user)
 
-        if not target_user or target_user.is_mine:
+        if not target_user or self.hs.is_mine(target_user):
             self._stop_polling_local(user, target_user=target_user)
 
         deferreds = []
@@ -579,7 +579,7 @@ class PresenceHandler(BaseHandler):
     @defer.inlineCallbacks
     @log_function
     def push_presence(self, user, statuscache):
-        assert(user.is_mine)
+        assert(self.hs.is_mine(user))
 
         logger.debug("Pushing presence update from %s", user)
 
@@ -696,7 +696,7 @@ class PresenceHandler(BaseHandler):
         for poll in content.get("poll", []):
             user = self.hs.parse_userid(poll)
 
-            if not user.is_mine:
+            if not self.hs.is_mine(user):
                 continue
 
             # TODO(paul) permissions checks
@@ -711,7 +711,7 @@ class PresenceHandler(BaseHandler):
         for unpoll in content.get("unpoll", []):
             user = self.hs.parse_userid(unpoll)
 
-            if not user.is_mine:
+            if not self.hs.is_mine(user):
                 continue
 
             if user in self._remote_sendmap:
@@ -730,7 +730,7 @@ class PresenceHandler(BaseHandler):
 
         localusers, remoteusers = partitionbool(
             users_to_push,
-            lambda u: u.is_mine
+            lambda u: self.hs.is_mine(u)
         )
 
         localusers = set(localusers)
@@ -788,7 +788,7 @@ class PresenceEventSource(object):
                 [u.to_string() for u in observer_user, observed_user])):
             defer.returnValue(True)
 
-        if observed_user.is_mine:
+        if self.hs.is_mine(observed_user):
             pushmap = presence._local_pushmap
 
             defer.returnValue(
diff --git a/synapse/handlers/profile.py b/synapse/handlers/profile.py
index 814b3b68fe..f2abbc5df9 100644
--- a/synapse/handlers/profile.py
+++ b/synapse/handlers/profile.py
@@ -51,7 +51,7 @@ class ProfileHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_displayname(self, target_user):
-        if target_user.is_mine:
+        if self.hs.is_mine(target_user):
             displayname = yield self.store.get_profile_displayname(
                 target_user.localpart
             )
@@ -81,7 +81,7 @@ class ProfileHandler(BaseHandler):
     def set_displayname(self, target_user, auth_user, new_displayname):
         """target_user is the user whose displayname is to be changed;
         auth_user is the user attempting to make this change."""
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
@@ -101,7 +101,7 @@ class ProfileHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def get_avatar_url(self, target_user):
-        if target_user.is_mine:
+        if self.hs.is_mine(target_user):
             avatar_url = yield self.store.get_profile_avatar_url(
                 target_user.localpart
             )
@@ -130,7 +130,7 @@ class ProfileHandler(BaseHandler):
     def set_avatar_url(self, target_user, auth_user, new_avatar_url):
         """target_user is the user whose avatar_url is to be changed;
         auth_user is the user attempting to make this change."""
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
@@ -150,7 +150,7 @@ class ProfileHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def collect_presencelike_data(self, user, state):
-        if not user.is_mine:
+        if not self.hs.is_mine(user):
             defer.returnValue(None)
 
         with PreserveLoggingContext():
@@ -170,7 +170,7 @@ class ProfileHandler(BaseHandler):
     @defer.inlineCallbacks
     def on_profile_query(self, args):
         user = self.hs.parse_userid(args["user_id"])
-        if not user.is_mine:
+        if not self.hs.is_mine(user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         just_field = args.get("field", None)
@@ -191,7 +191,7 @@ class ProfileHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def _update_join_states(self, user):
-        if not user.is_mine:
+        if not self.hs.is_mine(user):
             return
 
         joins = yield self.store.get_rooms_for_user_where_membership_is(
@@ -210,14 +210,11 @@ class ProfileHandler(BaseHandler):
                 "collect_presencelike_data", user, content
             )
 
-            new_event = self.event_factory.create_event(
-                etype=j.type,
-                room_id=j.room_id,
-                state_key=j.state_key,
-                content=content,
-                user_id=j.state_key,
-            )
-
-            yield self._on_new_room_event(
-                new_event, snapshot, suppress_auth=True
-            )
+            msg_handler = self.hs.get_handlers().message_handler
+            yield msg_handler.handle_event({
+                "type": j.type,
+                "room_id": j.room_id,
+                "state_key": j.state_key,
+                "content": content,
+                "sender": j.state_key,
+            })
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index a000b44036..f0ffd62b7f 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -52,9 +52,9 @@ class RoomCreationHandler(BaseHandler):
         self.ratelimit(user_id)
 
         if "room_alias_name" in config:
-            room_alias = RoomAlias.create_local(
+            room_alias = RoomAlias.create(
                 config["room_alias_name"],
-                self.hs
+                self.hs.hostname,
             )
             mapping = yield self.store.get_association_from_room_alias(
                 room_alias
@@ -77,7 +77,7 @@ class RoomCreationHandler(BaseHandler):
         if room_id:
             # Ensure room_id is the correct type
             room_id_obj = RoomID.from_string(room_id, self.hs)
-            if not room_id_obj.is_mine:
+            if not self.hs.is_mine(room_id_obj):
                 raise SynapseError(400, "Room id must be local")
 
             yield self.store.store_room(
@@ -93,7 +93,10 @@ class RoomCreationHandler(BaseHandler):
             while attempts < 5:
                 try:
                     random_string = stringutils.random_string(18)
-                    gen_room_id = RoomID.create_local(random_string, self.hs)
+                    gen_room_id = RoomID.create(
+                        random_string,
+                        self.hs.hostname,
+                    )
                     yield self.store.store_room(
                         room_id=gen_room_id.to_string(),
                         room_creator_user_id=user_id,
@@ -120,59 +123,37 @@ class RoomCreationHandler(BaseHandler):
             user, room_id, is_public=is_public
         )
 
-        room_member_handler = self.hs.get_handlers().room_member_handler
-
-        @defer.inlineCallbacks
-        def handle_event(event):
-            snapshot = yield self.store.snapshot_room(event)
-
-            logger.debug("Event: %s", event)
-
-            if event.type == RoomMemberEvent.TYPE:
-                yield room_member_handler.change_membership(
-                    event,
-                    do_auth=True
-                )
-            else:
-                yield self._on_new_room_event(
-                    event, snapshot, extra_users=[user], suppress_auth=True
-                )
+        msg_handler = self.hs.get_handlers().message_handler
 
         for event in creation_events:
-            yield handle_event(event)
+            yield msg_handler.handle_event(event)
 
         if "name" in config:
             name = config["name"]
-            name_event = self.event_factory.create_event(
-                etype=RoomNameEvent.TYPE,
-                room_id=room_id,
-                user_id=user_id,
-                content={"name": name},
-            )
-
-            yield handle_event(name_event)
+            yield msg_handler.handle_event({
+                "type": RoomNameEvent.TYPE,
+                "room_id": room_id,
+                "sender": user_id,
+                "content": {"name": name},
+            })
 
         if "topic" in config:
             topic = config["topic"]
-            topic_event = self.event_factory.create_event(
-                etype=RoomTopicEvent.TYPE,
-                room_id=room_id,
-                user_id=user_id,
-                content={"topic": topic},
-            )
+            yield msg_handler.handle_event({
+                "type": RoomTopicEvent.TYPE,
+                "room_id": room_id,
+                "sender": user_id,
+                "content": {"topic": topic},
+            })
 
-            yield handle_event(topic_event)
-
-        content = {"membership": Membership.INVITE}
         for invitee in invite_list:
-            invite_event = self.event_factory.create_event(
-                etype=RoomMemberEvent.TYPE,
-                state_key=invitee,
-                room_id=room_id,
-                user_id=user_id,
-                content=content
-            )
-            yield handle_event(invite_event)
+            yield msg_handler.handle_event({
+                "type": RoomMemberEvent.TYPE,
+                "state_key": invitee,
+                "room_id": room_id,
+                "user_id": user_id,
+                "content": {"membership": Membership.INVITE},
+            })
 
         result = {"room_id": room_id}
 
@@ -189,22 +170,25 @@ class RoomCreationHandler(BaseHandler):
 
         event_keys = {
             "room_id": room_id,
-            "user_id": creator_id,
+            "sender": creator_id,
         }
 
-        def create(etype, **content):
-            return self.event_factory.create_event(
-                etype=etype,
-                content=content,
-                **event_keys
-            )
+        def create(etype, content):
+            e = {
+                "type": etype,
+                "content": content,
+            }
+
+            e.update(event_keys)
+
+            return e
 
         creation_event = create(
             etype=RoomCreateEvent.TYPE,
-            creator=creator.to_string(),
+            content={"creator": creator.to_string()},
         )
 
-        join_event = self.event_factory.create_event(
+        join_event = create(
             etype=RoomMemberEvent.TYPE,
             state_key=creator_id,
             content={
@@ -213,7 +197,7 @@ class RoomCreationHandler(BaseHandler):
             **event_keys
         )
 
-        power_levels_event = self.event_factory.create_event(
+        power_levels_event = create(
             etype=RoomPowerLevelsEvent.TYPE,
             content={
                 "users": {
@@ -230,13 +214,12 @@ class RoomCreationHandler(BaseHandler):
                 "kick": 50,
                 "redact": 50
             },
-            **event_keys
         )
 
         join_rule = JoinRules.PUBLIC if is_public else JoinRules.INVITE
         join_rules_event = create(
             etype=RoomJoinRulesEvent.TYPE,
-            join_rule=join_rule,
+            content={"join_rule": join_rule},
         )
 
         return [
@@ -287,7 +270,7 @@ class RoomMemberHandler(BaseHandler):
             if ignore_user is not None and member == ignore_user:
                 continue
 
-            if member.is_mine:
+            if self.hs.is_mine(member):
                 if localusers is not None:
                     localusers.add(member)
             else:
@@ -348,7 +331,7 @@ class RoomMemberHandler(BaseHandler):
         defer.returnValue(member)
 
     @defer.inlineCallbacks
-    def change_membership(self, event=None, do_auth=True):
+    def change_membership(self, event, context, do_auth=True):
         """ Change the membership status of a user in a room.
 
         Args:
@@ -358,8 +341,6 @@ class RoomMemberHandler(BaseHandler):
         """
         target_user_id = event.state_key
 
-        snapshot = yield self.store.snapshot_room(event)
-
         ## TODO(markjh): get prev state from snapshot.
         prev_state = yield self.store.get_room_member(
             target_user_id, event.room_id
@@ -371,10 +352,11 @@ class RoomMemberHandler(BaseHandler):
         # if this HS is not currently in the room, i.e. we have to do the
         # invite/join dance.
         if event.membership == Membership.JOIN:
-            yield self._do_join(event, snapshot, do_auth=do_auth)
+            yield self._do_join(event, context, do_auth=do_auth)
         else:
             # This is not a JOIN, so we can handle it normally.
 
+            # FIXME: This isn't idempotency.
             if prev_state and prev_state.membership == event.membership:
                 # double same action, treat this event as a NOOP.
                 defer.returnValue({})
@@ -383,7 +365,7 @@ class RoomMemberHandler(BaseHandler):
             yield self._do_local_membership_update(
                 event,
                 membership=event.content["membership"],
-                snapshot=snapshot,
+                context=context,
                 do_auth=do_auth,
             )
 
@@ -405,23 +387,21 @@ class RoomMemberHandler(BaseHandler):
         host = hosts[0]
 
         content.update({"membership": Membership.JOIN})
-        new_event = self.event_factory.create_event(
-            etype=RoomMemberEvent.TYPE,
-            state_key=joinee.to_string(),
-            room_id=room_id,
-            user_id=joinee.to_string(),
-            membership=Membership.JOIN,
-            content=content,
-        )
-
-        snapshot = yield self.store.snapshot_room(new_event)
+        event, context = yield self.create_new_client_event({
+            "type": RoomMemberEvent.TYPE,
+            "state_key": joinee.to_string(),
+            "room_id": room_id,
+            "sender": joinee.to_string(),
+            "membership": Membership.JOIN,
+            "content": content,
+        })
 
-        yield self._do_join(new_event, snapshot, room_host=host, do_auth=True)
+        yield self._do_join(event, context, room_host=host, do_auth=True)
 
         defer.returnValue({"room_id": room_id})
 
     @defer.inlineCallbacks
-    def _do_join(self, event, snapshot, room_host=None, do_auth=True):
+    def _do_join(self, event, context, room_host=None, do_auth=True):
         joinee = self.hs.parse_userid(event.state_key)
         # room_id = RoomID.from_string(event.room_id, self.hs)
         room_id = event.room_id
@@ -457,7 +437,7 @@ class RoomMemberHandler(BaseHandler):
                     prev_state.user_id, self.hs
                 )
 
-                should_do_dance = not inviter.is_mine and not room
+                should_do_dance = not self.hs.is_mine(inviter) and not room
                 room_host = inviter.domain
             else:
                 should_do_dance = False
@@ -466,7 +446,7 @@ class RoomMemberHandler(BaseHandler):
         if should_do_dance:
             handler = self.hs.get_handlers().federation_handler
             have_joined = yield handler.do_invite_join(
-                room_host, room_id, event.user_id, event.content, snapshot
+                room_host, room_id, event.user_id, event.content, context
             )
 
         # We want to do the _do_update inside the room lock.
@@ -476,7 +456,7 @@ class RoomMemberHandler(BaseHandler):
             yield self._do_local_membership_update(
                 event,
                 membership=event.content["membership"],
-                snapshot=snapshot,
+                context=context,
                 do_auth=do_auth,
             )
 
@@ -504,7 +484,7 @@ class RoomMemberHandler(BaseHandler):
                 prev_state.sender, self.hs
             )
 
-            is_remote_invite_join = not inviter.is_mine and not room
+            is_remote_invite_join = not self.hs.is_mine(inviter) and not room
             room_host = inviter.domain
         else:
             is_remote_invite_join = False
@@ -526,7 +506,7 @@ class RoomMemberHandler(BaseHandler):
         defer.returnValue(room_ids)
 
     @defer.inlineCallbacks
-    def _do_local_membership_update(self, event, membership, snapshot,
+    def _do_local_membership_update(self, event, membership, context,
                                     do_auth):
         yield run_on_reactor()
 
@@ -534,14 +514,14 @@ class RoomMemberHandler(BaseHandler):
         # HS.
         target_user_id = event.state_key
         target_user = self.hs.parse_userid(target_user_id)
-        if membership == Membership.INVITE and not target_user.is_mine:
+        if membership == Membership.INVITE and not self.hs.is_mine(target_user):
             do_invite_host = target_user.domain
         else:
             do_invite_host = None
 
-        yield self._on_new_room_event(
+        yield self.handle_new_client_event(
             event,
-            snapshot,
+            context,
             extra_users=[target_user],
             suppress_auth=(not do_auth),
             do_invite_host=do_invite_host,
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index d88a53242c..be67fb2fc2 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -47,7 +47,7 @@ class TypingNotificationHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def started_typing(self, target_user, auth_user, room_id, timeout):
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
@@ -72,7 +72,7 @@ class TypingNotificationHandler(BaseHandler):
 
     @defer.inlineCallbacks
     def stopped_typing(self, target_user, auth_user, room_id):
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "User is not hosted on this Home Server")
 
         if target_user != auth_user:
diff --git a/synapse/rest/admin.py b/synapse/rest/admin.py
index ed9b484623..d74c551512 100644
--- a/synapse/rest/admin.py
+++ b/synapse/rest/admin.py
@@ -35,7 +35,7 @@ class WhoisRestServlet(RestServlet):
         if not is_admin and target_user != auth_user:
             raise AuthError(403, "You are not a server admin")
 
-        if not target_user.is_mine:
+        if not self.hs.is_mine(target_user):
             raise SynapseError(400, "Can only whois a local user")
 
         ret = yield self.handlers.admin_handler.get_whois(target_user)
diff --git a/synapse/rest/base.py b/synapse/rest/base.py
index 79fc4dfb84..72bb66ddda 100644
--- a/synapse/rest/base.py
+++ b/synapse/rest/base.py
@@ -63,7 +63,7 @@ class RestServlet(object):
         self.hs = hs
 
         self.handlers = hs.get_handlers()
-        self.event_factory = hs.get_event_factory()
+        self.builder_factory = hs.get_event_builder_factory()
         self.auth = hs.get_auth()
         self.txns = HttpTransactionStore()
 
diff --git a/synapse/rest/login.py b/synapse/rest/login.py
index ad71f6c61d..875da076af 100644
--- a/synapse/rest/login.py
+++ b/synapse/rest/login.py
@@ -47,8 +47,8 @@ class LoginRestServlet(RestServlet):
     @defer.inlineCallbacks
     def do_password_login(self, login_submission):
         if not login_submission["user"].startswith('@'):
-            login_submission["user"] = UserID.create_local(
-                login_submission["user"], self.hs).to_string()
+            login_submission["user"] = UserID.create(
+                login_submission["user"], self.hs.hostname).to_string()
 
         handler = self.handlers.login_handler
         token = yield handler.login(
diff --git a/synapse/rest/presence.py b/synapse/rest/presence.py
index 502ed0d4ca..062c895595 100644
--- a/synapse/rest/presence.py
+++ b/synapse/rest/presence.py
@@ -83,7 +83,7 @@ class PresenceListRestServlet(RestServlet):
         user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
-        if not user.is_mine:
+        if not self.hs.is_mine(user):
             raise SynapseError(400, "User not hosted on this Home Server")
 
         if auth_user != user:
@@ -104,7 +104,7 @@ class PresenceListRestServlet(RestServlet):
         user_id = urllib.unquote(user_id)
         user = self.hs.parse_userid(user_id)
 
-        if not user.is_mine:
+        if not self.hs.is_mine(user):
             raise SynapseError(400, "User not hosted on this Home Server")
 
         if auth_user != user:
diff --git a/synapse/rest/room.py b/synapse/rest/room.py
index 3147d7a60b..3d78b4ff5c 100644
--- a/synapse/rest/room.py
+++ b/synapse/rest/room.py
@@ -117,10 +117,10 @@ class RoomStateEventRestServlet(RestServlet):
                                   self.on_PUT_no_state_key)
 
     def on_GET_no_state_key(self, request, room_id, event_type):
-        return self.on_GET(request, room_id, event_type, "")
+        return self.on_GET(request, room_id, event_type, None)
 
     def on_PUT_no_state_key(self, request, room_id, event_type):
-        return self.on_PUT(request, room_id, event_type, "")
+        return self.on_PUT(request, room_id, event_type, None)
 
     @defer.inlineCallbacks
     def on_GET(self, request, room_id, event_type, state_key):
@@ -147,28 +147,18 @@ class RoomStateEventRestServlet(RestServlet):
 
         content = _parse_json(request)
 
-        event = self.event_factory.create_event(
-            etype=event_type,  # already urldecoded
-            content=content,
-            room_id=urllib.unquote(room_id),
-            user_id=user.to_string(),
-            state_key=urllib.unquote(state_key)
-            )
-
-        self.validator.validate(event)
+        msg_handler = self.handlers.message_handler
+        yield msg_handler.handle_event(
+            {
+                "type": event_type,
+                "content": content,
+                "room_id": room_id,
+                "sender": user.to_string(),
+                "state_key": urllib.unquote(state_key),
+            }
+        )
 
-        if event_type == RoomMemberEvent.TYPE:
-            # membership events are special
-            handler = self.handlers.room_member_handler
-            yield handler.change_membership(event)
-            defer.returnValue((200, {}))
-        else:
-            # store random bits of state
-            msg_handler = self.handlers.message_handler
-            yield msg_handler.store_room_data(
-                event=event
-            )
-            defer.returnValue((200, {}))
+        defer.returnValue((200, {}))
 
 
 # TODO: Needs unit testing for generic events + feedback
@@ -184,17 +174,15 @@ class RoomSendEventRestServlet(RestServlet):
         user = yield self.auth.get_user_by_req(request)
         content = _parse_json(request)
 
-        event = self.event_factory.create_event(
-            etype=urllib.unquote(event_type),
-            room_id=urllib.unquote(room_id),
-            user_id=user.to_string(),
-            content=content
-        )
-
-        self.validator.validate(event)
-
         msg_handler = self.handlers.message_handler
-        yield msg_handler.send_message(event)
+        event = yield msg_handler.handle_event(
+            {
+                "type": urllib.unquote(event_type),
+                "content": content,
+                "room_id": urllib.unquote(room_id),
+                "sender": user.to_string(),
+            }
+        )
 
         defer.returnValue((200, {"event_id": event.event_id}))
 
@@ -251,18 +239,17 @@ class JoinRoomAliasServlet(RestServlet):
             ret_dict = yield handler.join_room_alias(user, identifier)
             defer.returnValue((200, ret_dict))
         else:  # room id
-            event = self.event_factory.create_event(
-                etype=RoomMemberEvent.TYPE,
-                content={"membership": Membership.JOIN},
-                room_id=urllib.unquote(identifier.to_string()),
-                user_id=user.to_string(),
-                state_key=user.to_string()
+            msg_handler = self.handlers.message_handler
+            yield msg_handler.handle_event(
+                {
+                    "type": RoomMemberEvent.TYPE,
+                    "content": {"membership": Membership.JOIN},
+                    "room_id": urllib.unquote(identifier.to_string()),
+                    "sender": user.to_string(),
+                    "state_key": user.to_string(),
+                }
             )
 
-            self.validator.validate(event)
-
-            handler = self.handlers.room_member_handler
-            yield handler.change_membership(event)
             defer.returnValue((200, {}))
 
     @defer.inlineCallbacks
@@ -414,18 +401,17 @@ class RoomMembershipRestServlet(RestServlet):
             if membership_action == "kick":
                 membership_action = "leave"
 
-        event = self.event_factory.create_event(
-            etype=RoomMemberEvent.TYPE,
-            content={"membership": unicode(membership_action)},
-            room_id=urllib.unquote(room_id),
-            user_id=user.to_string(),
-            state_key=state_key
+        msg_handler = self.handlers.message_handler
+        yield msg_handler.handle_event(
+            {
+                "type": RoomMemberEvent.TYPE,
+                "content": {"membership": unicode(membership_action)},
+                "room_id": urllib.unquote(room_id),
+                "sender": user.to_string(),
+                "state_key": state_key,
+            }
         )
 
-        self.validator.validate(event)
-
-        handler = self.handlers.room_member_handler
-        yield handler.change_membership(event)
         defer.returnValue((200, {}))
 
     @defer.inlineCallbacks
@@ -453,18 +439,16 @@ class RoomRedactEventRestServlet(RestServlet):
         user = yield self.auth.get_user_by_req(request)
         content = _parse_json(request)
 
-        event = self.event_factory.create_event(
-            etype=RoomRedactionEvent.TYPE,
-            room_id=urllib.unquote(room_id),
-            user_id=user.to_string(),
-            content=content,
-            redacts=urllib.unquote(event_id),
-        )
-
-        self.validator.validate(event)
-
         msg_handler = self.handlers.message_handler
-        yield msg_handler.send_message(event)
+        event = yield msg_handler.handle_event(
+            {
+                "type": RoomRedactionEvent.TYPE,
+                "content": content,
+                "room_id": urllib.unquote(room_id),
+                "sender": user.to_string(),
+                "redacts": urllib.unquote(event_id),
+            }
+        )
 
         defer.returnValue((200, {"event_id": event.event_id}))
 
diff --git a/synapse/server.py b/synapse/server.py
index da0a44433a..8bc27bbc3c 100644
--- a/synapse/server.py
+++ b/synapse/server.py
@@ -36,6 +36,7 @@ from synapse.util.lockutils import LockManager
 from synapse.streams.events import EventSources
 from synapse.api.ratelimiting import Ratelimiter
 from synapse.crypto.keyring import Keyring
+from synapse.events.builder import EventBuilderFactory
 
 
 class BaseHomeServer(object):
@@ -82,6 +83,7 @@ class BaseHomeServer(object):
         'ratelimiter',
         'keyring',
         'event_validator',
+        'event_builder_factory',
     ]
 
     def __init__(self, hostname, **kwargs):
@@ -133,22 +135,22 @@ class BaseHomeServer(object):
     def parse_userid(self, s):
         """Parse the string given by 's' as a User ID and return a UserID
         object."""
-        return UserID.from_string(s, hs=self)
+        return UserID.from_string(s)
 
     def parse_roomalias(self, s):
         """Parse the string given by 's' as a Room Alias and return a RoomAlias
         object."""
-        return RoomAlias.from_string(s, hs=self)
+        return RoomAlias.from_string(s)
 
     def parse_roomid(self, s):
         """Parse the string given by 's' as a Room ID and return a RoomID
         object."""
-        return RoomID.from_string(s, hs=self)
+        return RoomID.from_string(s)
 
     def parse_eventid(self, s):
         """Parse the string given by 's' as a Event ID and return a EventID
         object."""
-        return EventID.from_string(s, hs=self)
+        return EventID.from_string(s)
 
     def serialize_event(self, e):
         return serialize_event(self, e)
@@ -165,6 +167,9 @@ class BaseHomeServer(object):
 
         return ip_addr
 
+    def is_mine(self, domain_specific_string):
+        return domain_specific_string.domain == self.hostname
+
 # Build magic accessors for every dependency
 for depname in BaseHomeServer.DEPENDENCIES:
     BaseHomeServer._make_dependency_method(depname)
@@ -228,6 +233,12 @@ class HomeServer(BaseHomeServer):
     def build_event_validator(self):
         return EventValidator(self)
 
+    def build_event_builder_factory(self):
+        return EventBuilderFactory(
+            clock=self.get_clock(),
+            hostname=self.hostname,
+        )
+
     def register_servlets(self):
         """ Register all servlets associated with this HomeServer.
         """
diff --git a/synapse/state.py b/synapse/state.py
index 430665f7ba..8a556a27f6 100644
--- a/synapse/state.py
+++ b/synapse/state.py
@@ -89,7 +89,7 @@ class StateHandler(object):
         ids = [e for e, _ in event.prev_events]
 
         ret = yield self.resolve_state_groups(ids)
-        state_group, new_state = ret
+        state_group, new_state, _ = ret
 
         event.old_state_events = copy.deepcopy(new_state)
 
@@ -137,7 +137,7 @@ class StateHandler(object):
 
     @defer.inlineCallbacks
     @log_function
-    def resolve_state_groups(self, event_ids):
+    def resolve_state_groups(self, event_ids, event_type=None, state_key=""):
         """ Given a list of event_ids this method fetches the state at each
         event, resolves conflicts between them and returns them.
 
@@ -156,7 +156,10 @@ class StateHandler(object):
                 (e.type, e.state_key): e
                 for e in state_list
             }
-            defer.returnValue((name, state))
+            prev_state = state.get((event_type, state_key), None)
+            if prev_state:
+                prev_state = prev_state.event_id
+            defer.returnValue((name, state, [prev_state]))
 
         state = {}
         for group, g_state in state_groups.items():
@@ -177,6 +180,13 @@ class StateHandler(object):
             if len(v.values()) > 1
         }
 
+        if event_type:
+            prev_states = conflicted_state.get(
+                (event_type, state_key), {}
+            ).keys()
+        else:
+            prev_states = []
+
         try:
             new_state = {}
             new_state.update(unconflicted_state)
@@ -186,7 +196,7 @@ class StateHandler(object):
             logger.exception("Failed to resolve state")
             raise
 
-        defer.returnValue((None, new_state))
+        defer.returnValue((None, new_state, prev_states))
 
     def _get_power_level_from_event_state(self, event, user_id):
         if hasattr(event, "old_state_events") and event.old_state_events:
diff --git a/synapse/storage/__init__.py b/synapse/storage/__init__.py
index f15e3dfe62..205d125642 100644
--- a/synapse/storage/__init__.py
+++ b/synapse/storage/__init__.py
@@ -155,6 +155,25 @@ class DataStore(RoomMemberStore, RoomStore,
         if hasattr(event, "outlier"):
             outlier = event.outlier
 
+        event_dict = {
+            k: v
+            for k, v in event.get_full_dict().items()
+            if k not in [
+                "redacted",
+                "redacted_because",
+            ]
+        }
+
+        self._simple_insert_txn(
+            txn,
+            table="event_json",
+            values={
+                "event_id": event.event_id,
+                "json": json.dumps(event_dict, separators=(',', ':')),
+            },
+            or_replace=True,
+        )
+
         vals = {
             "topological_ordering": event.depth,
             "event_id": event.event_id,
diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py
index 4881f03368..bb61c20150 100644
--- a/synapse/storage/_base.py
+++ b/synapse/storage/_base.py
@@ -479,66 +479,30 @@ class SQLBaseStore(object):
         )
 
     def _parse_events_txn(self, txn, rows):
-        events = [self._parse_event_from_row(r) for r in rows]
-
-        select_event_sql = (
-            "SELECT * FROM events WHERE event_id = ? ORDER BY rowid asc"
-        )
-
-        for i, ev in enumerate(events):
-            signatures = self._get_event_signatures_txn(
-                txn, ev.event_id,
+        event_ids = [r["event_id"] for r in rows]
+
+        events = []
+        for event_id in event_ids:
+            js = self._simple_select_one_onecol_txn(
+                txn,
+                table="event_json",
+                keyvalues={"event_id": event_id},
+                retcol="json",
+                allow_none=True,
             )
 
-            ev.signatures = {
-                n: {
-                    k: encode_base64(v) for k, v in s.items()
-                }
-                for n, s in signatures.items()
-            }
-
-            hashes = self._get_event_content_hashes_txn(
-                txn, ev.event_id,
-            )
+            if not js:
+                # FIXME (erikj): What should we actually do here?
+                continue
 
-            ev.hashes = {
-                k: encode_base64(v) for k, v in hashes.items()
-            }
-
-            prevs = self._get_prev_events_and_state(txn, ev.event_id)
-
-            ev.prev_events = [
-                (e_id, h)
-                for e_id, h, is_state in prevs
-                if is_state == 0
-            ]
-
-            ev.auth_events = self._get_auth_events(txn, ev.event_id)
-
-            if hasattr(ev, "state_key"):
-                ev.prev_state = [
-                    (e_id, h)
-                    for e_id, h, is_state in prevs
-                    if is_state == 1
-                ]
-
-                if hasattr(ev, "replaces_state"):
-                    # Load previous state_content.
-                    # FIXME (erikj): Handle multiple prev_states.
-                    cursor = txn.execute(
-                        select_event_sql,
-                        (ev.replaces_state,)
-                    )
-                    prevs = self.cursor_to_dict(cursor)
-                    if prevs:
-                        prev = self._parse_event_from_row(prevs[0])
-                        ev.prev_content = prev.content
+            d = json.loads(js)
 
-            if not hasattr(ev, "redacted"):
-                logger.debug("Doesn't have redacted key: %s", ev)
-                ev.redacted = self._has_been_redacted_txn(txn, ev)
+            ev = self.event_factory.create_event(
+                etype=d["type"],
+                **d
+            )
 
-            if ev.redacted:
+            if hasattr(ev, "redacted") and ev.redacted:
                 # Get the redaction event.
                 select_event_sql = "SELECT * FROM events WHERE event_id = ?"
                 txn.execute(select_event_sql, (ev.redacted,))
@@ -549,9 +513,10 @@ class SQLBaseStore(object):
 
                 if del_evs:
                     ev = prune_event(ev)
-                    events[i] = ev
                     ev.redacted_because = del_evs[0]
 
+            events.append(ev)
+
         return events
 
     def _has_been_redacted_txn(self, txn, event):
diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql
index 8ba732a23b..cb0c494ddf 100644
--- a/synapse/storage/schema/im.sql
+++ b/synapse/storage/schema/im.sql
@@ -32,6 +32,16 @@ CREATE INDEX IF NOT EXISTS events_stream_ordering ON events (stream_ordering);
 CREATE INDEX IF NOT EXISTS events_topological_ordering ON events (topological_ordering);
 CREATE INDEX IF NOT EXISTS events_room_id ON events (room_id);
 
+
+CREATE TABLE IF NOT EXISTS event_json(
+    event_id TEXT NOT NULL,
+    json BLOB NOT NULL,
+    CONSTRAINT ev_j_uniq UNIQUE (event_id)
+);
+
+CREATE INDEX IF NOT EXISTS event_json_id ON event_json(event_id);
+
+
 CREATE TABLE IF NOT EXISTS state_events(
     event_id TEXT NOT NULL,
     room_id TEXT NOT NULL,
diff --git a/synapse/storage/signatures.py b/synapse/storage/signatures.py
index eea4f21065..3a705119fd 100644
--- a/synapse/storage/signatures.py
+++ b/synapse/storage/signatures.py
@@ -13,8 +13,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from twisted.internet import defer
+
 from _base import SQLBaseStore
 
+from syutil.base64util import encode_base64
+
 
 class SignatureStore(SQLBaseStore):
     """Persistence for event signatures and hashes"""
@@ -67,6 +71,21 @@ class SignatureStore(SQLBaseStore):
             f
         )
 
+    @defer.inlineCallbacks
+    def add_event_hashes(self, event_ids):
+        hashes = yield self.get_event_reference_hashes(
+            event_ids
+        )
+        hashes = [
+            {
+                k: encode_base64(v) for k, v in h.items()
+                if k == "sha256"
+            }
+            for h in hashes
+        ]
+
+        defer.returnValue(zip(event_ids, hashes))
+
     def _get_event_reference_hashes_txn(self, txn, event_id):
         """Get all the hashes for a given PDU.
         Args:
diff --git a/synapse/types.py b/synapse/types.py
index 649ff2f7d7..7c533193e1 100644
--- a/synapse/types.py
+++ b/synapse/types.py
@@ -19,7 +19,7 @@ from collections import namedtuple
 
 
 class DomainSpecificString(
-        namedtuple("DomainSpecificString", ("localpart", "domain", "is_mine"))
+        namedtuple("DomainSpecificString", ("localpart", "domain"))
 ):
     """Common base class among ID/name strings that have a local part and a
     domain name, prefixed with a sigil.
@@ -28,15 +28,13 @@ class DomainSpecificString(
 
         'localpart' : The local part of the name (without the leading sigil)
         'domain' : The domain part of the name
-        'is_mine' : Boolean indicating if the domain name is recognised by the
-            HomeServer as being its own
     """
 
     # Deny iteration because it will bite you if you try to create a singleton
     # set by:
     #    users = set(user)
     def __iter__(self):
-        raise ValueError("Attempted to iterate a %s" % (type(self).__name__))
+        raise ValueError("Attempted to iterate a %s" % (type(self).__name__,))
 
     # Because this class is a namedtuple of strings and booleans, it is deeply
     # immutable.
@@ -47,7 +45,7 @@ class DomainSpecificString(
         return self
 
     @classmethod
-    def from_string(cls, s, hs):
+    def from_string(cls, s):
         """Parse the string given by 's' into a structure object."""
         if s[0] != cls.SIGIL:
             raise SynapseError(400, "Expected %s string to start with '%s'" % (
@@ -66,22 +64,15 @@ class DomainSpecificString(
 
         # This code will need changing if we want to support multiple domain
         # names on one HS
-        is_mine = domain == hs.hostname
-        return cls(localpart=parts[0], domain=domain, is_mine=is_mine)
+        return cls(localpart=parts[0], domain=domain)
 
     def to_string(self):
         """Return a string encoding the fields of the structure object."""
         return "%s%s:%s" % (self.SIGIL, self.localpart, self.domain)
 
     @classmethod
-    def create_local(cls, localpart, hs):
-        """Create a structure on the local domain"""
-        return cls(localpart=localpart, domain=hs.hostname, is_mine=True)
-
-    @classmethod
-    def create(cls, localpart, domain, hs):
-        is_mine = domain == hs.hostname
-        return cls(localpart=localpart, domain=domain, is_mine=is_mine)
+    def create(cls, localpart, domain,):
+        return cls(localpart=localpart, domain=domain)
 
 
 class UserID(DomainSpecificString):