test_event_chain.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. # Copyright 2020 The Matrix.org Foundation C.I.C.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the 'License');
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an 'AS IS' BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from typing import Dict, List, Set, Tuple
  15. from twisted.trial import unittest
  16. from synapse.api.constants import EventTypes
  17. from synapse.api.room_versions import RoomVersions
  18. from synapse.events import EventBase
  19. from synapse.events.snapshot import EventContext
  20. from synapse.rest import admin
  21. from synapse.rest.client import login, room
  22. from synapse.storage.databases.main.events import _LinkMap
  23. from synapse.types import create_requester
  24. from tests.unittest import HomeserverTestCase
  25. class EventChainStoreTestCase(HomeserverTestCase):
  26. def prepare(self, reactor, clock, hs):
  27. self.store = hs.get_datastores().main
  28. self._next_stream_ordering = 1
  29. def test_simple(self):
  30. """Test that the example in `docs/auth_chain_difference_algorithm.md`
  31. works.
  32. """
  33. event_factory = self.hs.get_event_builder_factory()
  34. bob = "@creator:test"
  35. alice = "@alice:test"
  36. room_id = "!room:test"
  37. # Ensure that we have a rooms entry so that we generate the chain index.
  38. self.get_success(
  39. self.store.store_room(
  40. room_id=room_id,
  41. room_creator_user_id="",
  42. is_public=True,
  43. room_version=RoomVersions.V6,
  44. )
  45. )
  46. create = self.get_success(
  47. event_factory.for_room_version(
  48. RoomVersions.V6,
  49. {
  50. "type": EventTypes.Create,
  51. "state_key": "",
  52. "sender": bob,
  53. "room_id": room_id,
  54. "content": {"tag": "create"},
  55. },
  56. ).build(prev_event_ids=[], auth_event_ids=[])
  57. )
  58. bob_join = self.get_success(
  59. event_factory.for_room_version(
  60. RoomVersions.V6,
  61. {
  62. "type": EventTypes.Member,
  63. "state_key": bob,
  64. "sender": bob,
  65. "room_id": room_id,
  66. "content": {"tag": "bob_join"},
  67. },
  68. ).build(prev_event_ids=[], auth_event_ids=[create.event_id])
  69. )
  70. power = self.get_success(
  71. event_factory.for_room_version(
  72. RoomVersions.V6,
  73. {
  74. "type": EventTypes.PowerLevels,
  75. "state_key": "",
  76. "sender": bob,
  77. "room_id": room_id,
  78. "content": {"tag": "power"},
  79. },
  80. ).build(
  81. prev_event_ids=[],
  82. auth_event_ids=[create.event_id, bob_join.event_id],
  83. )
  84. )
  85. alice_invite = self.get_success(
  86. event_factory.for_room_version(
  87. RoomVersions.V6,
  88. {
  89. "type": EventTypes.Member,
  90. "state_key": alice,
  91. "sender": bob,
  92. "room_id": room_id,
  93. "content": {"tag": "alice_invite"},
  94. },
  95. ).build(
  96. prev_event_ids=[],
  97. auth_event_ids=[create.event_id, bob_join.event_id, power.event_id],
  98. )
  99. )
  100. alice_join = self.get_success(
  101. event_factory.for_room_version(
  102. RoomVersions.V6,
  103. {
  104. "type": EventTypes.Member,
  105. "state_key": alice,
  106. "sender": alice,
  107. "room_id": room_id,
  108. "content": {"tag": "alice_join"},
  109. },
  110. ).build(
  111. prev_event_ids=[],
  112. auth_event_ids=[create.event_id, alice_invite.event_id, power.event_id],
  113. )
  114. )
  115. power_2 = self.get_success(
  116. event_factory.for_room_version(
  117. RoomVersions.V6,
  118. {
  119. "type": EventTypes.PowerLevels,
  120. "state_key": "",
  121. "sender": bob,
  122. "room_id": room_id,
  123. "content": {"tag": "power_2"},
  124. },
  125. ).build(
  126. prev_event_ids=[],
  127. auth_event_ids=[create.event_id, bob_join.event_id, power.event_id],
  128. )
  129. )
  130. bob_join_2 = self.get_success(
  131. event_factory.for_room_version(
  132. RoomVersions.V6,
  133. {
  134. "type": EventTypes.Member,
  135. "state_key": bob,
  136. "sender": bob,
  137. "room_id": room_id,
  138. "content": {"tag": "bob_join_2"},
  139. },
  140. ).build(
  141. prev_event_ids=[],
  142. auth_event_ids=[create.event_id, bob_join.event_id, power.event_id],
  143. )
  144. )
  145. alice_join2 = self.get_success(
  146. event_factory.for_room_version(
  147. RoomVersions.V6,
  148. {
  149. "type": EventTypes.Member,
  150. "state_key": alice,
  151. "sender": alice,
  152. "room_id": room_id,
  153. "content": {"tag": "alice_join2"},
  154. },
  155. ).build(
  156. prev_event_ids=[],
  157. auth_event_ids=[
  158. create.event_id,
  159. alice_join.event_id,
  160. power_2.event_id,
  161. ],
  162. )
  163. )
  164. events = [
  165. create,
  166. bob_join,
  167. power,
  168. alice_invite,
  169. alice_join,
  170. bob_join_2,
  171. power_2,
  172. alice_join2,
  173. ]
  174. expected_links = [
  175. (bob_join, create),
  176. (power, create),
  177. (power, bob_join),
  178. (alice_invite, create),
  179. (alice_invite, power),
  180. (alice_invite, bob_join),
  181. (bob_join_2, power),
  182. (alice_join2, power_2),
  183. ]
  184. self.persist(events)
  185. chain_map, link_map = self.fetch_chains(events)
  186. # Check that the expected links and only the expected links have been
  187. # added.
  188. self.assertEqual(len(expected_links), len(list(link_map.get_additions())))
  189. for start, end in expected_links:
  190. start_id, start_seq = chain_map[start.event_id]
  191. end_id, end_seq = chain_map[end.event_id]
  192. self.assertIn(
  193. (start_seq, end_seq), list(link_map.get_links_between(start_id, end_id))
  194. )
  195. # Test that everything can reach the create event, but the create event
  196. # can't reach anything.
  197. for event in events[1:]:
  198. self.assertTrue(
  199. link_map.exists_path_from(
  200. chain_map[event.event_id], chain_map[create.event_id]
  201. ),
  202. )
  203. self.assertFalse(
  204. link_map.exists_path_from(
  205. chain_map[create.event_id],
  206. chain_map[event.event_id],
  207. ),
  208. )
  209. def test_out_of_order_events(self):
  210. """Test that we handle persisting events that we don't have the full
  211. auth chain for yet (which should only happen for out of band memberships).
  212. """
  213. event_factory = self.hs.get_event_builder_factory()
  214. bob = "@creator:test"
  215. alice = "@alice:test"
  216. room_id = "!room:test"
  217. # Ensure that we have a rooms entry so that we generate the chain index.
  218. self.get_success(
  219. self.store.store_room(
  220. room_id=room_id,
  221. room_creator_user_id="",
  222. is_public=True,
  223. room_version=RoomVersions.V6,
  224. )
  225. )
  226. # First persist the base room.
  227. create = self.get_success(
  228. event_factory.for_room_version(
  229. RoomVersions.V6,
  230. {
  231. "type": EventTypes.Create,
  232. "state_key": "",
  233. "sender": bob,
  234. "room_id": room_id,
  235. "content": {"tag": "create"},
  236. },
  237. ).build(prev_event_ids=[], auth_event_ids=[])
  238. )
  239. bob_join = self.get_success(
  240. event_factory.for_room_version(
  241. RoomVersions.V6,
  242. {
  243. "type": EventTypes.Member,
  244. "state_key": bob,
  245. "sender": bob,
  246. "room_id": room_id,
  247. "content": {"tag": "bob_join"},
  248. },
  249. ).build(prev_event_ids=[], auth_event_ids=[create.event_id])
  250. )
  251. power = self.get_success(
  252. event_factory.for_room_version(
  253. RoomVersions.V6,
  254. {
  255. "type": EventTypes.PowerLevels,
  256. "state_key": "",
  257. "sender": bob,
  258. "room_id": room_id,
  259. "content": {"tag": "power"},
  260. },
  261. ).build(
  262. prev_event_ids=[],
  263. auth_event_ids=[create.event_id, bob_join.event_id],
  264. )
  265. )
  266. self.persist([create, bob_join, power])
  267. # Now persist an invite and a couple of memberships out of order.
  268. alice_invite = self.get_success(
  269. event_factory.for_room_version(
  270. RoomVersions.V6,
  271. {
  272. "type": EventTypes.Member,
  273. "state_key": alice,
  274. "sender": bob,
  275. "room_id": room_id,
  276. "content": {"tag": "alice_invite"},
  277. },
  278. ).build(
  279. prev_event_ids=[],
  280. auth_event_ids=[create.event_id, bob_join.event_id, power.event_id],
  281. )
  282. )
  283. alice_join = self.get_success(
  284. event_factory.for_room_version(
  285. RoomVersions.V6,
  286. {
  287. "type": EventTypes.Member,
  288. "state_key": alice,
  289. "sender": alice,
  290. "room_id": room_id,
  291. "content": {"tag": "alice_join"},
  292. },
  293. ).build(
  294. prev_event_ids=[],
  295. auth_event_ids=[create.event_id, alice_invite.event_id, power.event_id],
  296. )
  297. )
  298. alice_join2 = self.get_success(
  299. event_factory.for_room_version(
  300. RoomVersions.V6,
  301. {
  302. "type": EventTypes.Member,
  303. "state_key": alice,
  304. "sender": alice,
  305. "room_id": room_id,
  306. "content": {"tag": "alice_join2"},
  307. },
  308. ).build(
  309. prev_event_ids=[],
  310. auth_event_ids=[create.event_id, alice_join.event_id, power.event_id],
  311. )
  312. )
  313. self.persist([alice_join])
  314. self.persist([alice_join2])
  315. self.persist([alice_invite])
  316. # The end result should be sane.
  317. events = [create, bob_join, power, alice_invite, alice_join]
  318. chain_map, link_map = self.fetch_chains(events)
  319. expected_links = [
  320. (bob_join, create),
  321. (power, create),
  322. (power, bob_join),
  323. (alice_invite, create),
  324. (alice_invite, power),
  325. (alice_invite, bob_join),
  326. ]
  327. # Check that the expected links and only the expected links have been
  328. # added.
  329. self.assertEqual(len(expected_links), len(list(link_map.get_additions())))
  330. for start, end in expected_links:
  331. start_id, start_seq = chain_map[start.event_id]
  332. end_id, end_seq = chain_map[end.event_id]
  333. self.assertIn(
  334. (start_seq, end_seq), list(link_map.get_links_between(start_id, end_id))
  335. )
  336. def persist(
  337. self,
  338. events: List[EventBase],
  339. ):
  340. """Persist the given events and check that the links generated match
  341. those given.
  342. """
  343. persist_events_store = self.hs.get_datastores().persist_events
  344. for e in events:
  345. e.internal_metadata.stream_ordering = self._next_stream_ordering
  346. self._next_stream_ordering += 1
  347. def _persist(txn):
  348. # We need to persist the events to the events and state_events
  349. # tables.
  350. persist_events_store._store_event_txn(
  351. txn, [(e, EventContext(self.hs.get_storage())) for e in events]
  352. )
  353. # Actually call the function that calculates the auth chain stuff.
  354. persist_events_store._persist_event_auth_chain_txn(txn, events)
  355. self.get_success(
  356. persist_events_store.db_pool.runInteraction(
  357. "_persist",
  358. _persist,
  359. )
  360. )
  361. def fetch_chains(
  362. self, events: List[EventBase]
  363. ) -> Tuple[Dict[str, Tuple[int, int]], _LinkMap]:
  364. # Fetch the map from event ID -> (chain ID, sequence number)
  365. rows = self.get_success(
  366. self.store.db_pool.simple_select_many_batch(
  367. table="event_auth_chains",
  368. column="event_id",
  369. iterable=[e.event_id for e in events],
  370. retcols=("event_id", "chain_id", "sequence_number"),
  371. keyvalues={},
  372. )
  373. )
  374. chain_map = {
  375. row["event_id"]: (row["chain_id"], row["sequence_number"]) for row in rows
  376. }
  377. # Fetch all the links and pass them to the _LinkMap.
  378. rows = self.get_success(
  379. self.store.db_pool.simple_select_many_batch(
  380. table="event_auth_chain_links",
  381. column="origin_chain_id",
  382. iterable=[chain_id for chain_id, _ in chain_map.values()],
  383. retcols=(
  384. "origin_chain_id",
  385. "origin_sequence_number",
  386. "target_chain_id",
  387. "target_sequence_number",
  388. ),
  389. keyvalues={},
  390. )
  391. )
  392. link_map = _LinkMap()
  393. for row in rows:
  394. added = link_map.add_link(
  395. (row["origin_chain_id"], row["origin_sequence_number"]),
  396. (row["target_chain_id"], row["target_sequence_number"]),
  397. )
  398. # We shouldn't have persisted any redundant links
  399. self.assertTrue(added)
  400. return chain_map, link_map
  401. class LinkMapTestCase(unittest.TestCase):
  402. def test_simple(self):
  403. """Basic tests for the LinkMap."""
  404. link_map = _LinkMap()
  405. link_map.add_link((1, 1), (2, 1), new=False)
  406. self.assertCountEqual(link_map.get_links_between(1, 2), [(1, 1)])
  407. self.assertCountEqual(link_map.get_links_from((1, 1)), [(2, 1)])
  408. self.assertCountEqual(link_map.get_additions(), [])
  409. self.assertTrue(link_map.exists_path_from((1, 5), (2, 1)))
  410. self.assertFalse(link_map.exists_path_from((1, 5), (2, 2)))
  411. self.assertTrue(link_map.exists_path_from((1, 5), (1, 1)))
  412. self.assertFalse(link_map.exists_path_from((1, 1), (1, 5)))
  413. # Attempting to add a redundant link is ignored.
  414. self.assertFalse(link_map.add_link((1, 4), (2, 1)))
  415. self.assertCountEqual(link_map.get_links_between(1, 2), [(1, 1)])
  416. # Adding new non-redundant links works
  417. self.assertTrue(link_map.add_link((1, 3), (2, 3)))
  418. self.assertCountEqual(link_map.get_links_between(1, 2), [(1, 1), (3, 3)])
  419. self.assertTrue(link_map.add_link((2, 5), (1, 3)))
  420. self.assertCountEqual(link_map.get_links_between(2, 1), [(5, 3)])
  421. self.assertCountEqual(link_map.get_links_between(1, 2), [(1, 1), (3, 3)])
  422. self.assertCountEqual(link_map.get_additions(), [(1, 3, 2, 3), (2, 5, 1, 3)])
  423. class EventChainBackgroundUpdateTestCase(HomeserverTestCase):
  424. servlets = [
  425. admin.register_servlets,
  426. room.register_servlets,
  427. login.register_servlets,
  428. ]
  429. def prepare(self, reactor, clock, hs):
  430. self.store = hs.get_datastores().main
  431. self.user_id = self.register_user("foo", "pass")
  432. self.token = self.login("foo", "pass")
  433. self.requester = create_requester(self.user_id)
  434. def _generate_room(self) -> Tuple[str, List[Set[str]]]:
  435. """Insert a room without a chain cover index."""
  436. room_id = self.helper.create_room_as(self.user_id, tok=self.token)
  437. # Mark the room as not having a chain cover index
  438. self.get_success(
  439. self.store.db_pool.simple_update(
  440. table="rooms",
  441. keyvalues={"room_id": room_id},
  442. updatevalues={"has_auth_chain_index": False},
  443. desc="test",
  444. )
  445. )
  446. # Create a fork in the DAG with different events.
  447. event_handler = self.hs.get_event_creation_handler()
  448. latest_event_ids = self.get_success(
  449. self.store.get_prev_events_for_room(room_id)
  450. )
  451. event, context = self.get_success(
  452. event_handler.create_event(
  453. self.requester,
  454. {
  455. "type": "some_state_type",
  456. "state_key": "",
  457. "content": {},
  458. "room_id": room_id,
  459. "sender": self.user_id,
  460. },
  461. prev_event_ids=latest_event_ids,
  462. )
  463. )
  464. self.get_success(
  465. event_handler.handle_new_client_event(self.requester, event, context)
  466. )
  467. state1 = set(self.get_success(context.get_current_state_ids()).values())
  468. event, context = self.get_success(
  469. event_handler.create_event(
  470. self.requester,
  471. {
  472. "type": "some_state_type",
  473. "state_key": "",
  474. "content": {},
  475. "room_id": room_id,
  476. "sender": self.user_id,
  477. },
  478. prev_event_ids=latest_event_ids,
  479. )
  480. )
  481. self.get_success(
  482. event_handler.handle_new_client_event(self.requester, event, context)
  483. )
  484. state2 = set(self.get_success(context.get_current_state_ids()).values())
  485. # Delete the chain cover info.
  486. def _delete_tables(txn):
  487. txn.execute("DELETE FROM event_auth_chains")
  488. txn.execute("DELETE FROM event_auth_chain_links")
  489. self.get_success(self.store.db_pool.runInteraction("test", _delete_tables))
  490. return room_id, [state1, state2]
  491. def test_background_update_single_room(self):
  492. """Test that the background update to calculate auth chains for historic
  493. rooms works correctly.
  494. """
  495. # Create a room
  496. room_id, states = self._generate_room()
  497. # Insert and run the background update.
  498. self.get_success(
  499. self.store.db_pool.simple_insert(
  500. "background_updates",
  501. {"update_name": "chain_cover", "progress_json": "{}"},
  502. )
  503. )
  504. # Ugh, have to reset this flag
  505. self.store.db_pool.updates._all_done = False
  506. self.wait_for_background_updates()
  507. # Test that the `has_auth_chain_index` has been set
  508. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id)))
  509. # Test that calculating the auth chain difference using the newly
  510. # calculated chain cover works.
  511. self.get_success(
  512. self.store.db_pool.runInteraction(
  513. "test",
  514. self.store._get_auth_chain_difference_using_cover_index_txn,
  515. room_id,
  516. states,
  517. )
  518. )
  519. def test_background_update_multiple_rooms(self):
  520. """Test that the background update to calculate auth chains for historic
  521. rooms works correctly.
  522. """
  523. # Create a room
  524. room_id1, states1 = self._generate_room()
  525. room_id2, states2 = self._generate_room()
  526. room_id3, states2 = self._generate_room()
  527. # Insert and run the background update.
  528. self.get_success(
  529. self.store.db_pool.simple_insert(
  530. "background_updates",
  531. {"update_name": "chain_cover", "progress_json": "{}"},
  532. )
  533. )
  534. # Ugh, have to reset this flag
  535. self.store.db_pool.updates._all_done = False
  536. self.wait_for_background_updates()
  537. # Test that the `has_auth_chain_index` has been set
  538. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id1)))
  539. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id2)))
  540. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id3)))
  541. # Test that calculating the auth chain difference using the newly
  542. # calculated chain cover works.
  543. self.get_success(
  544. self.store.db_pool.runInteraction(
  545. "test",
  546. self.store._get_auth_chain_difference_using_cover_index_txn,
  547. room_id1,
  548. states1,
  549. )
  550. )
  551. def test_background_update_single_large_room(self):
  552. """Test that the background update to calculate auth chains for historic
  553. rooms works correctly.
  554. """
  555. # Create a room
  556. room_id, states = self._generate_room()
  557. # Add a bunch of state so that it takes multiple iterations of the
  558. # background update to process the room.
  559. for i in range(0, 150):
  560. self.helper.send_state(
  561. room_id, event_type="m.test", body={"index": i}, tok=self.token
  562. )
  563. # Insert and run the background update.
  564. self.get_success(
  565. self.store.db_pool.simple_insert(
  566. "background_updates",
  567. {"update_name": "chain_cover", "progress_json": "{}"},
  568. )
  569. )
  570. # Ugh, have to reset this flag
  571. self.store.db_pool.updates._all_done = False
  572. iterations = 0
  573. while not self.get_success(
  574. self.store.db_pool.updates.has_completed_background_updates()
  575. ):
  576. iterations += 1
  577. self.get_success(
  578. self.store.db_pool.updates.do_next_background_update(False), by=0.1
  579. )
  580. # Ensure that we did actually take multiple iterations to process the
  581. # room.
  582. self.assertGreater(iterations, 1)
  583. # Test that the `has_auth_chain_index` has been set
  584. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id)))
  585. # Test that calculating the auth chain difference using the newly
  586. # calculated chain cover works.
  587. self.get_success(
  588. self.store.db_pool.runInteraction(
  589. "test",
  590. self.store._get_auth_chain_difference_using_cover_index_txn,
  591. room_id,
  592. states,
  593. )
  594. )
  595. def test_background_update_multiple_large_room(self):
  596. """Test that the background update to calculate auth chains for historic
  597. rooms works correctly.
  598. """
  599. # Create the rooms
  600. room_id1, _ = self._generate_room()
  601. room_id2, _ = self._generate_room()
  602. # Add a bunch of state so that it takes multiple iterations of the
  603. # background update to process the room.
  604. for i in range(0, 150):
  605. self.helper.send_state(
  606. room_id1, event_type="m.test", body={"index": i}, tok=self.token
  607. )
  608. for i in range(0, 150):
  609. self.helper.send_state(
  610. room_id2, event_type="m.test", body={"index": i}, tok=self.token
  611. )
  612. # Insert and run the background update.
  613. self.get_success(
  614. self.store.db_pool.simple_insert(
  615. "background_updates",
  616. {"update_name": "chain_cover", "progress_json": "{}"},
  617. )
  618. )
  619. # Ugh, have to reset this flag
  620. self.store.db_pool.updates._all_done = False
  621. iterations = 0
  622. while not self.get_success(
  623. self.store.db_pool.updates.has_completed_background_updates()
  624. ):
  625. iterations += 1
  626. self.get_success(
  627. self.store.db_pool.updates.do_next_background_update(False), by=0.1
  628. )
  629. # Ensure that we did actually take multiple iterations to process the
  630. # room.
  631. self.assertGreater(iterations, 1)
  632. # Test that the `has_auth_chain_index` has been set
  633. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id1)))
  634. self.assertTrue(self.get_success(self.store.has_auth_chain_index(room_id2)))