# Buggy code
def upload_room_keys(self, user_id, version, room_keys):
    """Bulk upload a list of room keys into a given backup version, asserting
    that the given version is the current backup version.  room_keys are merged
    into the current backup as described in RoomKeysServlet.on_PUT().

    Args:
        user_id(str): the user whose backup we're setting
        version(str): the version ID of the backup we're updating
        room_keys(dict): a nested dict describing the room_keys we're setting:

    {
        "rooms": {
            "!abc:matrix.org": {
                "sessions": {
                    "c0ff33": {
                        "first_message_index": 1,
                        "forwarded_count": 1,
                        "is_verified": false,
                        "session_data": "SSBBTSBBIEZJU0gK"
                    }
                }
            }
        }
    }

    Returns:
        A dict containing the count and etag for the backup version

    Raises:
        NotFoundError: if there are no versions defined
        RoomKeysVersionError: if the uploaded version is not the current version
    """

    # TODO: Validate the JSON to make sure it has the right keys.

    # XXX: perhaps we should use a finer grained lock here?
    with (yield self._upload_linearizer.queue(user_id)):
        # Check that the version we're trying to upload is the current version
        try:
            version_info = yield self.store.get_e2e_room_keys_version_info(user_id)
        except StoreError as e:
            if e.code == 404:
                raise NotFoundError("Version '%s' not found" % (version,))
            else:
                raise

        if version_info["version"] != version:
            # Check that the version we're trying to upload actually exists
            try:
                version_info = yield self.store.get_e2e_room_keys_version_info(
                    user_id, version
                )
                # if we get this far, the version must exist
                raise RoomKeysVersionError(current_version=version_info["version"])
            except StoreError as e:
                if e.code == 404:
                    raise NotFoundError("Version '%s' not found" % (version,))
                else:
                    raise

        # Fetch any existing room keys for the sessions that have been
        # submitted.  Then compare them with the submitted keys.  If the
        # key is new, insert it; if the key should be updated, then update
        # it; otherwise, drop it.
        existing_keys = yield self.store.get_e2e_room_keys_multi(
            user_id, version, room_keys["rooms"]
        )
        to_insert = []  # batch the inserts together
        changed = False  # if anything has changed, we need to update the etag
        for room_id, room in iteritems(room_keys["rooms"]):
            for session_id, room_key in iteritems(room["sessions"]):
                log_kv(
                    {
                        "message": "Trying to upload room key",
                        "room_id": room_id,
                        "session_id": session_id,
                        "user_id": user_id,
                    }
                )
                current_room_key = existing_keys.get(room_id, {}).get(session_id)
                if current_room_key:
                    if self._should_replace_room_key(current_room_key, room_key):
                        log_kv({"message": "Replacing room key."})
                        # updates are done one at a time in the DB, so send
                        # updates right away rather than batching them up,
                        # like we do with the inserts
                        yield self.store.update_e2e_room_key(
                            user_id, version, room_id, session_id, room_key
                        )
                        changed = True
                    else:
                        log_kv({"message": "Not replacing room_key."})
                else:
                    log_kv(
                        {
                            "message": "Room key not found.",
                            "room_id": room_id,
                            "user_id": user_id,
                        }
                    )
                    log_kv({"message": "Replacing room key."})
                    to_insert.append((room_id, session_id, room_key))
                    changed = True

        if len(to_insert):
            yield self.store.add_e2e_room_keys(user_id, version, to_insert)

        version_etag = version_info["etag"]
        if changed:
            version_etag = version_etag + 1
            yield self.store.update_e2e_room_keys_version(
                user_id, version, None, version_etag
            )

        count = yield self.store.count_e2e_room_keys(user_id, version)
        return {"etag": str(version_etag), "count": count}


# Traceback (psycopg2.ProgrammingError)
[homeserver_1] 2020-03-04 17:46:42,842 - synapse.http.server - 110 - ERROR - PUT-58- Failed handle request via 'RoomKeysServlet': <XForwardedForRequest at 0x7fd4f193bd68 method='PUT' uri='/_matrix/client/unstable/room_keys/keys?version=2' clientproto='HTTP/1.0' site=8118>
Traceback (most recent call last):
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/http/server.py", line 78, in wrapped_request_handler
await h(self, request)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/http/server.py", line 331, in _async_render
callback_return = await callback_return
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/rest/client/v2_alpha/room_keys.py", line 134, in on_PUT
ret = await self.e2e_room_keys_handler.upload_room_keys(user_id, version, body)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/handlers/e2e_room_keys.py", line 226, in upload_room_keys
user_id, version, room_id, session_id, room_key
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/data_stores/main/e2e_room_keys.py", line 55, in update_e2e_room_key
desc="update_e2e_room_key",
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 495, in runInteraction
**kwargs
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/failure.py", line 491, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 543, in runWithConnection
self._db_pool.runWithConnection(inner_func, *args, **kwargs)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/threadpool.py", line 250, in inContext
result = inContext.theWork()
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/threadpool.py", line 266, in <lambda>
inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/context.py", line 122, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/context.py", line 85, in callWithContext
return func(*args,**kw)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/enterprise/adbapi.py", line 306, in _runWithConnection
compat.reraise(excValue, excTraceback)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/python/compat.py", line 464, in reraise
raise exception.with_traceback(traceback)
File "/home/matrix/synapse/lib/python3.6/site-packages/twisted/enterprise/adbapi.py", line 297, in _runWithConnection
result = func(conn, *args, **kw)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 540, in inner_func
return func(conn, *args, **kwargs)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 378, in new_transaction
r = func(cursor, *args, **kwargs)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 1211, in simple_update_one_txn
rowcount = cls.simple_update_txn(txn, table, keyvalues, updatevalues)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 1181, in simple_update_txn
txn.execute(update_sql, list(updatevalues.values()) + list(keyvalues.values()))
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 175, in execute
self._do_execute(self.txn.execute, sql, *args)
File "/home/matrix/synapse/lib/python3.6/site-packages/synapse/storage/database.py", line 201, in _do_execute
return func(sql, *args)
psycopg2.ProgrammingError: can't adapt type 'dict'
[homeserver_1] 2020-03-04 17:46:42,941 - synapse.access.http.8118 - 302 - INFO - PUT-58- 174.3.196.16 - 8118 - {@travis:t2l.io} Processed request: 0.144sec/0.000sec (0.093sec, 0.000sec) (0.002sec/0.047sec/3) 67B 500 "PUT /_matrix/client/unstable/room_keys/keys?version=2 HTTP/1.0" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36" [0 dbevts]

# Fixed code
def upload_room_keys(self, user_id, version, room_keys):
    """Bulk upload a list of room keys into a given backup version, asserting
    that the given version is the current backup version.  room_keys are merged
    into the current backup as described in RoomKeysServlet.on_PUT().

    Args:
        user_id(str): the user whose backup we're setting
        version(str): the version ID of the backup we're updating
        room_keys(dict): a nested dict describing the room_keys we're setting:

    {
        "rooms": {
            "!abc:matrix.org": {
                "sessions": {
                    "c0ff33": {
                        "first_message_index": 1,
                        "forwarded_count": 1,
                        "is_verified": false,
                        "session_data": "SSBBTSBBIEZJU0gK"
                    }
                }
            }
        }
    }

    Returns:
        A dict containing the count and etag for the backup version

    Raises:
        NotFoundError: if there are no versions defined
        RoomKeysVersionError: if the uploaded version is not the current version
    """

    # TODO: Validate the JSON to make sure it has the right keys.

    # XXX: perhaps we should use a finer grained lock here?
    with (yield self._upload_linearizer.queue(user_id)):
        # Check that the version we're trying to upload is the current version
        try:
            version_info = yield self.store.get_e2e_room_keys_version_info(user_id)
        except StoreError as e:
            if e.code == 404:
                raise NotFoundError("Version '%s' not found" % (version,))
            else:
                raise

        if version_info["version"] != version:
            # Check that the version we're trying to upload actually exists
            try:
                version_info = yield self.store.get_e2e_room_keys_version_info(
                    user_id, version
                )
                # if we get this far, the version must exist
                raise RoomKeysVersionError(current_version=version_info["version"])
            except StoreError as e:
                if e.code == 404:
                    raise NotFoundError("Version '%s' not found" % (version,))
                else:
                    raise

        # Fetch any existing room keys for the sessions that have been
        # submitted.  Then compare them with the submitted keys.  If the
        # key is new, insert it; if the key should be updated, then update
        # it; otherwise, drop it.
        existing_keys = yield self.store.get_e2e_room_keys_multi(
            user_id, version, room_keys["rooms"]
        )
        to_insert = []  # batch the inserts together
        changed = False  # if anything has changed, we need to update the etag
        for room_id, room in iteritems(room_keys["rooms"]):
            for session_id, room_key in iteritems(room["sessions"]):
                if not isinstance(room_key["is_verified"], bool):
                    msg = (
                        "is_verified must be a boolean in keys for session %s in"
                        "room %s" % (session_id, room_id)
                    )
                    raise SynapseError(400, msg, Codes.INVALID_PARAM)

                log_kv(
                    {
                        "message": "Trying to upload room key",
                        "room_id": room_id,
                        "session_id": session_id,
                        "user_id": user_id,
                    }
                )
                current_room_key = existing_keys.get(room_id, {}).get(session_id)
                if current_room_key:
                    if self._should_replace_room_key(current_room_key, room_key):
                        log_kv({"message": "Replacing room key."})
                        # updates are done one at a time in the DB, so send
                        # updates right away rather than batching them up,
                        # like we do with the inserts
                        yield self.store.update_e2e_room_key(
                            user_id, version, room_id, session_id, room_key
                        )
                        changed = True
                    else:
                        log_kv({"message": "Not replacing room_key."})
                else:
                    log_kv(
                        {
                            "message": "Room key not found.",
                            "room_id": room_id,
                            "user_id": user_id,
                        }
                    )
                    log_kv({"message": "Replacing room key."})
                    to_insert.append((room_id, session_id, room_key))
                    changed = True

        if len(to_insert):
            yield self.store.add_e2e_room_keys(user_id, version, to_insert)

        version_etag = version_info["etag"]
        if changed:
            version_etag = version_etag + 1
            yield self.store.update_e2e_room_keys_version(
                user_id, version, None, version_etag
            )

        count = yield self.store.count_e2e_room_keys(user_id, version)
        return {"etag": str(version_etag), "count": count}