test_event_federation.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2018 New Vector Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the 'License');
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an 'AS IS' BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import attr
  16. from parameterized import parameterized
  17. from synapse.events import _EventInternalMetadata
  18. import tests.unittest
  19. import tests.utils
  20. class EventFederationWorkerStoreTestCase(tests.unittest.HomeserverTestCase):
  21. def prepare(self, reactor, clock, hs):
  22. self.store = hs.get_datastore()
  23. def test_get_prev_events_for_room(self):
  24. room_id = "@ROOM:local"
  25. # add a bunch of events and hashes to act as forward extremities
  26. def insert_event(txn, i):
  27. event_id = "$event_%i:local" % i
  28. txn.execute(
  29. (
  30. "INSERT INTO events ("
  31. " room_id, event_id, type, depth, topological_ordering,"
  32. " content, processed, outlier, stream_ordering) "
  33. "VALUES (?, ?, 'm.test', ?, ?, 'test', ?, ?, ?)"
  34. ),
  35. (room_id, event_id, i, i, True, False, i),
  36. )
  37. txn.execute(
  38. (
  39. "INSERT INTO event_forward_extremities (room_id, event_id) "
  40. "VALUES (?, ?)"
  41. ),
  42. (room_id, event_id),
  43. )
  44. txn.execute(
  45. (
  46. "INSERT INTO event_reference_hashes "
  47. "(event_id, algorithm, hash) "
  48. "VALUES (?, 'sha256', ?)"
  49. ),
  50. (event_id, bytearray(b"ffff")),
  51. )
  52. for i in range(0, 20):
  53. self.get_success(
  54. self.store.db_pool.runInteraction("insert", insert_event, i)
  55. )
  56. # this should get the last ten
  57. r = self.get_success(self.store.get_prev_events_for_room(room_id))
  58. self.assertEqual(10, len(r))
  59. for i in range(0, 10):
  60. self.assertEqual("$event_%i:local" % (19 - i), r[i])
  61. def test_get_rooms_with_many_extremities(self):
  62. room1 = "#room1"
  63. room2 = "#room2"
  64. room3 = "#room3"
  65. def insert_event(txn, i, room_id):
  66. event_id = "$event_%i:local" % i
  67. txn.execute(
  68. (
  69. "INSERT INTO event_forward_extremities (room_id, event_id) "
  70. "VALUES (?, ?)"
  71. ),
  72. (room_id, event_id),
  73. )
  74. for i in range(0, 20):
  75. self.get_success(
  76. self.store.db_pool.runInteraction("insert", insert_event, i, room1)
  77. )
  78. self.get_success(
  79. self.store.db_pool.runInteraction("insert", insert_event, i, room2)
  80. )
  81. self.get_success(
  82. self.store.db_pool.runInteraction("insert", insert_event, i, room3)
  83. )
  84. # Test simple case
  85. r = self.get_success(self.store.get_rooms_with_many_extremities(5, 5, []))
  86. self.assertEqual(len(r), 3)
  87. # Does filter work?
  88. r = self.get_success(self.store.get_rooms_with_many_extremities(5, 5, [room1]))
  89. self.assertTrue(room2 in r)
  90. self.assertTrue(room3 in r)
  91. self.assertEqual(len(r), 2)
  92. r = self.get_success(
  93. self.store.get_rooms_with_many_extremities(5, 5, [room1, room2])
  94. )
  95. self.assertEqual(r, [room3])
  96. # Does filter and limit work?
  97. r = self.get_success(self.store.get_rooms_with_many_extremities(5, 1, [room1]))
  98. self.assertTrue(r == [room2] or r == [room3])
  99. @parameterized.expand([(True,), (False,)])
  100. def test_auth_difference(self, use_chain_cover_index: bool):
  101. room_id = "@ROOM:local"
  102. # The silly auth graph we use to test the auth difference algorithm,
  103. # where the top are the most recent events.
  104. #
  105. # A B
  106. # \ /
  107. # D E
  108. # \ |
  109. # ` F C
  110. # | /|
  111. # G ´ |
  112. # | \ |
  113. # H I
  114. # | |
  115. # K J
  116. auth_graph = {
  117. "a": ["e"],
  118. "b": ["e"],
  119. "c": ["g", "i"],
  120. "d": ["f"],
  121. "e": ["f"],
  122. "f": ["g"],
  123. "g": ["h", "i"],
  124. "h": ["k"],
  125. "i": ["j"],
  126. "k": [],
  127. "j": [],
  128. }
  129. depth_map = {
  130. "a": 7,
  131. "b": 7,
  132. "c": 4,
  133. "d": 6,
  134. "e": 6,
  135. "f": 5,
  136. "g": 3,
  137. "h": 2,
  138. "i": 2,
  139. "k": 1,
  140. "j": 1,
  141. }
  142. # Mark the room as not having a cover index
  143. def store_room(txn):
  144. self.store.db_pool.simple_insert_txn(
  145. txn,
  146. "rooms",
  147. {
  148. "room_id": room_id,
  149. "creator": "room_creator_user_id",
  150. "is_public": True,
  151. "room_version": "6",
  152. "has_auth_chain_index": use_chain_cover_index,
  153. },
  154. )
  155. self.get_success(self.store.db_pool.runInteraction("store_room", store_room))
  156. # We rudely fiddle with the appropriate tables directly, as that's much
  157. # easier than constructing events properly.
  158. def insert_event(txn):
  159. stream_ordering = 0
  160. for event_id in auth_graph:
  161. stream_ordering += 1
  162. depth = depth_map[event_id]
  163. self.store.db_pool.simple_insert_txn(
  164. txn,
  165. table="events",
  166. values={
  167. "event_id": event_id,
  168. "room_id": room_id,
  169. "depth": depth,
  170. "topological_ordering": depth,
  171. "type": "m.test",
  172. "processed": True,
  173. "outlier": False,
  174. "stream_ordering": stream_ordering,
  175. },
  176. )
  177. self.hs.datastores.persist_events._persist_event_auth_chain_txn(
  178. txn,
  179. [
  180. FakeEvent(event_id, room_id, auth_graph[event_id])
  181. for event_id in auth_graph
  182. ],
  183. )
  184. self.get_success(self.store.db_pool.runInteraction("insert", insert_event,))
  185. # Now actually test that various combinations give the right result:
  186. difference = self.get_success(
  187. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}])
  188. )
  189. self.assertSetEqual(difference, {"a", "b"})
  190. difference = self.get_success(
  191. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"c"}])
  192. )
  193. self.assertSetEqual(difference, {"a", "b", "c", "e", "f"})
  194. difference = self.get_success(
  195. self.store.get_auth_chain_difference(room_id, [{"a", "c"}, {"b"}])
  196. )
  197. self.assertSetEqual(difference, {"a", "b", "c"})
  198. difference = self.get_success(
  199. self.store.get_auth_chain_difference(room_id, [{"a", "c"}, {"b", "c"}])
  200. )
  201. self.assertSetEqual(difference, {"a", "b"})
  202. difference = self.get_success(
  203. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"d"}])
  204. )
  205. self.assertSetEqual(difference, {"a", "b", "d", "e"})
  206. difference = self.get_success(
  207. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"c"}, {"d"}])
  208. )
  209. self.assertSetEqual(difference, {"a", "b", "c", "d", "e", "f"})
  210. difference = self.get_success(
  211. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"e"}])
  212. )
  213. self.assertSetEqual(difference, {"a", "b"})
  214. difference = self.get_success(
  215. self.store.get_auth_chain_difference(room_id, [{"a"}])
  216. )
  217. self.assertSetEqual(difference, set())
  218. def test_auth_difference_partial_cover(self):
  219. """Test that we correctly handle rooms where not all events have a chain
  220. cover calculated. This can happen in some obscure edge cases, including
  221. during the background update that calculates the chain cover for old
  222. rooms.
  223. """
  224. room_id = "@ROOM:local"
  225. # The silly auth graph we use to test the auth difference algorithm,
  226. # where the top are the most recent events.
  227. #
  228. # A B
  229. # \ /
  230. # D E
  231. # \ |
  232. # ` F C
  233. # | /|
  234. # G ´ |
  235. # | \ |
  236. # H I
  237. # | |
  238. # K J
  239. auth_graph = {
  240. "a": ["e"],
  241. "b": ["e"],
  242. "c": ["g", "i"],
  243. "d": ["f"],
  244. "e": ["f"],
  245. "f": ["g"],
  246. "g": ["h", "i"],
  247. "h": ["k"],
  248. "i": ["j"],
  249. "k": [],
  250. "j": [],
  251. }
  252. depth_map = {
  253. "a": 7,
  254. "b": 7,
  255. "c": 4,
  256. "d": 6,
  257. "e": 6,
  258. "f": 5,
  259. "g": 3,
  260. "h": 2,
  261. "i": 2,
  262. "k": 1,
  263. "j": 1,
  264. }
  265. # We rudely fiddle with the appropriate tables directly, as that's much
  266. # easier than constructing events properly.
  267. def insert_event(txn):
  268. # First insert the room and mark it as having a chain cover.
  269. self.store.db_pool.simple_insert_txn(
  270. txn,
  271. "rooms",
  272. {
  273. "room_id": room_id,
  274. "creator": "room_creator_user_id",
  275. "is_public": True,
  276. "room_version": "6",
  277. "has_auth_chain_index": True,
  278. },
  279. )
  280. stream_ordering = 0
  281. for event_id in auth_graph:
  282. stream_ordering += 1
  283. depth = depth_map[event_id]
  284. self.store.db_pool.simple_insert_txn(
  285. txn,
  286. table="events",
  287. values={
  288. "event_id": event_id,
  289. "room_id": room_id,
  290. "depth": depth,
  291. "topological_ordering": depth,
  292. "type": "m.test",
  293. "processed": True,
  294. "outlier": False,
  295. "stream_ordering": stream_ordering,
  296. },
  297. )
  298. # Insert all events apart from 'B'
  299. self.hs.datastores.persist_events._persist_event_auth_chain_txn(
  300. txn,
  301. [
  302. FakeEvent(event_id, room_id, auth_graph[event_id])
  303. for event_id in auth_graph
  304. if event_id != "b"
  305. ],
  306. )
  307. # Now we insert the event 'B' without a chain cover, by temporarily
  308. # pretending the room doesn't have a chain cover.
  309. self.store.db_pool.simple_update_txn(
  310. txn,
  311. table="rooms",
  312. keyvalues={"room_id": room_id},
  313. updatevalues={"has_auth_chain_index": False},
  314. )
  315. self.hs.datastores.persist_events._persist_event_auth_chain_txn(
  316. txn, [FakeEvent("b", room_id, auth_graph["b"])],
  317. )
  318. self.store.db_pool.simple_update_txn(
  319. txn,
  320. table="rooms",
  321. keyvalues={"room_id": room_id},
  322. updatevalues={"has_auth_chain_index": True},
  323. )
  324. self.get_success(self.store.db_pool.runInteraction("insert", insert_event,))
  325. # Now actually test that various combinations give the right result:
  326. difference = self.get_success(
  327. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}])
  328. )
  329. self.assertSetEqual(difference, {"a", "b"})
  330. difference = self.get_success(
  331. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"c"}])
  332. )
  333. self.assertSetEqual(difference, {"a", "b", "c", "e", "f"})
  334. difference = self.get_success(
  335. self.store.get_auth_chain_difference(room_id, [{"a", "c"}, {"b"}])
  336. )
  337. self.assertSetEqual(difference, {"a", "b", "c"})
  338. difference = self.get_success(
  339. self.store.get_auth_chain_difference(room_id, [{"a", "c"}, {"b", "c"}])
  340. )
  341. self.assertSetEqual(difference, {"a", "b"})
  342. difference = self.get_success(
  343. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"d"}])
  344. )
  345. self.assertSetEqual(difference, {"a", "b", "d", "e"})
  346. difference = self.get_success(
  347. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"c"}, {"d"}])
  348. )
  349. self.assertSetEqual(difference, {"a", "b", "c", "d", "e", "f"})
  350. difference = self.get_success(
  351. self.store.get_auth_chain_difference(room_id, [{"a"}, {"b"}, {"e"}])
  352. )
  353. self.assertSetEqual(difference, {"a", "b"})
  354. difference = self.get_success(
  355. self.store.get_auth_chain_difference(room_id, [{"a"}])
  356. )
  357. self.assertSetEqual(difference, set())
  358. @attr.s
  359. class FakeEvent:
  360. event_id = attr.ib()
  361. room_id = attr.ib()
  362. auth_events = attr.ib()
  363. type = "foo"
  364. state_key = "foo"
  365. internal_metadata = _EventInternalMetadata({})
  366. def auth_event_ids(self):
  367. return self.auth_events
  368. def is_state(self):
  369. return True