1
0

test_v2.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  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 itertools
  16. from typing import List
  17. import attr
  18. from twisted.internet import defer
  19. from synapse.api.constants import EventTypes, JoinRules, Membership
  20. from synapse.api.room_versions import RoomVersions
  21. from synapse.event_auth import auth_types_for_event
  22. from synapse.events import make_event_from_dict
  23. from synapse.state.v2 import lexicographical_topological_sort, resolve_events_with_store
  24. from synapse.types import EventID
  25. from tests import unittest
  26. ALICE = "@alice:example.com"
  27. BOB = "@bob:example.com"
  28. CHARLIE = "@charlie:example.com"
  29. EVELYN = "@evelyn:example.com"
  30. ZARA = "@zara:example.com"
  31. ROOM_ID = "!test:example.com"
  32. MEMBERSHIP_CONTENT_JOIN = {"membership": Membership.JOIN}
  33. MEMBERSHIP_CONTENT_BAN = {"membership": Membership.BAN}
  34. ORIGIN_SERVER_TS = 0
  35. class FakeClock:
  36. def sleep(self, msec):
  37. return defer.succeed(None)
  38. class FakeEvent:
  39. """A fake event we use as a convenience.
  40. NOTE: Again as a convenience we use "node_ids" rather than event_ids to
  41. refer to events. The event_id has node_id as localpart and example.com
  42. as domain.
  43. """
  44. def __init__(self, id, sender, type, state_key, content):
  45. self.node_id = id
  46. self.event_id = EventID(id, "example.com").to_string()
  47. self.sender = sender
  48. self.type = type
  49. self.state_key = state_key
  50. self.content = content
  51. self.room_id = ROOM_ID
  52. def to_event(self, auth_events, prev_events):
  53. """Given the auth_events and prev_events, convert to a Frozen Event
  54. Args:
  55. auth_events (list[str]): list of event_ids
  56. prev_events (list[str]): list of event_ids
  57. Returns:
  58. FrozenEvent
  59. """
  60. global ORIGIN_SERVER_TS
  61. ts = ORIGIN_SERVER_TS
  62. ORIGIN_SERVER_TS = ORIGIN_SERVER_TS + 1
  63. event_dict = {
  64. "auth_events": [(a, {}) for a in auth_events],
  65. "prev_events": [(p, {}) for p in prev_events],
  66. "event_id": self.node_id,
  67. "sender": self.sender,
  68. "type": self.type,
  69. "content": self.content,
  70. "origin_server_ts": ts,
  71. "room_id": ROOM_ID,
  72. }
  73. if self.state_key is not None:
  74. event_dict["state_key"] = self.state_key
  75. return make_event_from_dict(event_dict)
  76. # All graphs start with this set of events
  77. INITIAL_EVENTS = [
  78. FakeEvent(
  79. id="CREATE",
  80. sender=ALICE,
  81. type=EventTypes.Create,
  82. state_key="",
  83. content={"creator": ALICE},
  84. ),
  85. FakeEvent(
  86. id="IMA",
  87. sender=ALICE,
  88. type=EventTypes.Member,
  89. state_key=ALICE,
  90. content=MEMBERSHIP_CONTENT_JOIN,
  91. ),
  92. FakeEvent(
  93. id="IPOWER",
  94. sender=ALICE,
  95. type=EventTypes.PowerLevels,
  96. state_key="",
  97. content={"users": {ALICE: 100}},
  98. ),
  99. FakeEvent(
  100. id="IJR",
  101. sender=ALICE,
  102. type=EventTypes.JoinRules,
  103. state_key="",
  104. content={"join_rule": JoinRules.PUBLIC},
  105. ),
  106. FakeEvent(
  107. id="IMB",
  108. sender=BOB,
  109. type=EventTypes.Member,
  110. state_key=BOB,
  111. content=MEMBERSHIP_CONTENT_JOIN,
  112. ),
  113. FakeEvent(
  114. id="IMC",
  115. sender=CHARLIE,
  116. type=EventTypes.Member,
  117. state_key=CHARLIE,
  118. content=MEMBERSHIP_CONTENT_JOIN,
  119. ),
  120. FakeEvent(
  121. id="IMZ",
  122. sender=ZARA,
  123. type=EventTypes.Member,
  124. state_key=ZARA,
  125. content=MEMBERSHIP_CONTENT_JOIN,
  126. ),
  127. FakeEvent(
  128. id="START", sender=ZARA, type=EventTypes.Message, state_key=None, content={}
  129. ),
  130. FakeEvent(
  131. id="END", sender=ZARA, type=EventTypes.Message, state_key=None, content={}
  132. ),
  133. ]
  134. INITIAL_EDGES = ["START", "IMZ", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"]
  135. class StateTestCase(unittest.TestCase):
  136. def test_ban_vs_pl(self):
  137. events = [
  138. FakeEvent(
  139. id="PA",
  140. sender=ALICE,
  141. type=EventTypes.PowerLevels,
  142. state_key="",
  143. content={"users": {ALICE: 100, BOB: 50}},
  144. ),
  145. FakeEvent(
  146. id="MA",
  147. sender=ALICE,
  148. type=EventTypes.Member,
  149. state_key=ALICE,
  150. content={"membership": Membership.JOIN},
  151. ),
  152. FakeEvent(
  153. id="MB",
  154. sender=ALICE,
  155. type=EventTypes.Member,
  156. state_key=BOB,
  157. content={"membership": Membership.BAN},
  158. ),
  159. FakeEvent(
  160. id="PB",
  161. sender=BOB,
  162. type=EventTypes.PowerLevels,
  163. state_key="",
  164. content={"users": {ALICE: 100, BOB: 50}},
  165. ),
  166. ]
  167. edges = [["END", "MB", "MA", "PA", "START"], ["END", "PB", "PA"]]
  168. expected_state_ids = ["PA", "MA", "MB"]
  169. self.do_check(events, edges, expected_state_ids)
  170. def test_join_rule_evasion(self):
  171. events = [
  172. FakeEvent(
  173. id="JR",
  174. sender=ALICE,
  175. type=EventTypes.JoinRules,
  176. state_key="",
  177. content={"join_rules": JoinRules.PRIVATE},
  178. ),
  179. FakeEvent(
  180. id="ME",
  181. sender=EVELYN,
  182. type=EventTypes.Member,
  183. state_key=EVELYN,
  184. content={"membership": Membership.JOIN},
  185. ),
  186. ]
  187. edges = [["END", "JR", "START"], ["END", "ME", "START"]]
  188. expected_state_ids = ["JR"]
  189. self.do_check(events, edges, expected_state_ids)
  190. def test_offtopic_pl(self):
  191. events = [
  192. FakeEvent(
  193. id="PA",
  194. sender=ALICE,
  195. type=EventTypes.PowerLevels,
  196. state_key="",
  197. content={"users": {ALICE: 100, BOB: 50}},
  198. ),
  199. FakeEvent(
  200. id="PB",
  201. sender=BOB,
  202. type=EventTypes.PowerLevels,
  203. state_key="",
  204. content={"users": {ALICE: 100, BOB: 50, CHARLIE: 50}},
  205. ),
  206. FakeEvent(
  207. id="PC",
  208. sender=CHARLIE,
  209. type=EventTypes.PowerLevels,
  210. state_key="",
  211. content={"users": {ALICE: 100, BOB: 50, CHARLIE: 0}},
  212. ),
  213. ]
  214. edges = [["END", "PC", "PB", "PA", "START"], ["END", "PA"]]
  215. expected_state_ids = ["PC"]
  216. self.do_check(events, edges, expected_state_ids)
  217. def test_topic_basic(self):
  218. events = [
  219. FakeEvent(
  220. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  221. ),
  222. FakeEvent(
  223. id="PA1",
  224. sender=ALICE,
  225. type=EventTypes.PowerLevels,
  226. state_key="",
  227. content={"users": {ALICE: 100, BOB: 50}},
  228. ),
  229. FakeEvent(
  230. id="T2", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  231. ),
  232. FakeEvent(
  233. id="PA2",
  234. sender=ALICE,
  235. type=EventTypes.PowerLevels,
  236. state_key="",
  237. content={"users": {ALICE: 100, BOB: 0}},
  238. ),
  239. FakeEvent(
  240. id="PB",
  241. sender=BOB,
  242. type=EventTypes.PowerLevels,
  243. state_key="",
  244. content={"users": {ALICE: 100, BOB: 50}},
  245. ),
  246. FakeEvent(
  247. id="T3", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  248. ),
  249. ]
  250. edges = [["END", "PA2", "T2", "PA1", "T1", "START"], ["END", "T3", "PB", "PA1"]]
  251. expected_state_ids = ["PA2", "T2"]
  252. self.do_check(events, edges, expected_state_ids)
  253. def test_topic_reset(self):
  254. events = [
  255. FakeEvent(
  256. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  257. ),
  258. FakeEvent(
  259. id="PA",
  260. sender=ALICE,
  261. type=EventTypes.PowerLevels,
  262. state_key="",
  263. content={"users": {ALICE: 100, BOB: 50}},
  264. ),
  265. FakeEvent(
  266. id="T2", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  267. ),
  268. FakeEvent(
  269. id="MB",
  270. sender=ALICE,
  271. type=EventTypes.Member,
  272. state_key=BOB,
  273. content={"membership": Membership.BAN},
  274. ),
  275. ]
  276. edges = [["END", "MB", "T2", "PA", "T1", "START"], ["END", "T1"]]
  277. expected_state_ids = ["T1", "MB", "PA"]
  278. self.do_check(events, edges, expected_state_ids)
  279. def test_topic(self):
  280. events = [
  281. FakeEvent(
  282. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  283. ),
  284. FakeEvent(
  285. id="PA1",
  286. sender=ALICE,
  287. type=EventTypes.PowerLevels,
  288. state_key="",
  289. content={"users": {ALICE: 100, BOB: 50}},
  290. ),
  291. FakeEvent(
  292. id="T2", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  293. ),
  294. FakeEvent(
  295. id="PA2",
  296. sender=ALICE,
  297. type=EventTypes.PowerLevels,
  298. state_key="",
  299. content={"users": {ALICE: 100, BOB: 0}},
  300. ),
  301. FakeEvent(
  302. id="PB",
  303. sender=BOB,
  304. type=EventTypes.PowerLevels,
  305. state_key="",
  306. content={"users": {ALICE: 100, BOB: 50}},
  307. ),
  308. FakeEvent(
  309. id="T3", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  310. ),
  311. FakeEvent(
  312. id="MZ1",
  313. sender=ZARA,
  314. type=EventTypes.Message,
  315. state_key=None,
  316. content={},
  317. ),
  318. FakeEvent(
  319. id="T4", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  320. ),
  321. ]
  322. edges = [
  323. ["END", "T4", "MZ1", "PA2", "T2", "PA1", "T1", "START"],
  324. ["END", "MZ1", "T3", "PB", "PA1"],
  325. ]
  326. expected_state_ids = ["T4", "PA2"]
  327. self.do_check(events, edges, expected_state_ids)
  328. def do_check(self, events, edges, expected_state_ids):
  329. """Take a list of events and edges and calculate the state of the
  330. graph at END, and asserts it matches `expected_state_ids`
  331. Args:
  332. events (list[FakeEvent])
  333. edges (list[list[str]]): A list of chains of event edges, e.g.
  334. `[[A, B, C]]` are edges A->B and B->C.
  335. expected_state_ids (list[str]): The expected state at END, (excluding
  336. the keys that haven't changed since START).
  337. """
  338. # We want to sort the events into topological order for processing.
  339. graph = {}
  340. # node_id -> FakeEvent
  341. fake_event_map = {}
  342. for ev in itertools.chain(INITIAL_EVENTS, events):
  343. graph[ev.node_id] = set()
  344. fake_event_map[ev.node_id] = ev
  345. for a, b in pairwise(INITIAL_EDGES):
  346. graph[a].add(b)
  347. for edge_list in edges:
  348. for a, b in pairwise(edge_list):
  349. graph[a].add(b)
  350. # event_id -> FrozenEvent
  351. event_map = {}
  352. # node_id -> state
  353. state_at_event = {}
  354. # We copy the map as the sort consumes the graph
  355. graph_copy = {k: set(v) for k, v in graph.items()}
  356. for node_id in lexicographical_topological_sort(graph_copy, key=lambda e: e):
  357. fake_event = fake_event_map[node_id]
  358. event_id = fake_event.event_id
  359. prev_events = list(graph[node_id])
  360. if len(prev_events) == 0:
  361. state_before = {}
  362. elif len(prev_events) == 1:
  363. state_before = dict(state_at_event[prev_events[0]])
  364. else:
  365. state_d = resolve_events_with_store(
  366. FakeClock(),
  367. ROOM_ID,
  368. RoomVersions.V2.identifier,
  369. [state_at_event[n] for n in prev_events],
  370. event_map=event_map,
  371. state_res_store=TestStateResolutionStore(event_map),
  372. )
  373. state_before = self.successResultOf(defer.ensureDeferred(state_d))
  374. state_after = dict(state_before)
  375. if fake_event.state_key is not None:
  376. state_after[(fake_event.type, fake_event.state_key)] = event_id
  377. auth_types = set(auth_types_for_event(fake_event))
  378. auth_events = []
  379. for key in auth_types:
  380. if key in state_before:
  381. auth_events.append(state_before[key])
  382. event = fake_event.to_event(auth_events, prev_events)
  383. state_at_event[node_id] = state_after
  384. event_map[event_id] = event
  385. expected_state = {}
  386. for node_id in expected_state_ids:
  387. # expected_state_ids are node IDs rather than event IDs,
  388. # so we have to convert
  389. event_id = EventID(node_id, "example.com").to_string()
  390. event = event_map[event_id]
  391. key = (event.type, event.state_key)
  392. expected_state[key] = event_id
  393. start_state = state_at_event["START"]
  394. end_state = {
  395. key: value
  396. for key, value in state_at_event["END"].items()
  397. if key in expected_state or start_state.get(key) != value
  398. }
  399. self.assertEqual(expected_state, end_state)
  400. class LexicographicalTestCase(unittest.TestCase):
  401. def test_simple(self):
  402. graph = {"l": {"o"}, "m": {"n", "o"}, "n": {"o"}, "o": set(), "p": {"o"}}
  403. res = list(lexicographical_topological_sort(graph, key=lambda x: x))
  404. self.assertEqual(["o", "l", "n", "m", "p"], res)
  405. class SimpleParamStateTestCase(unittest.TestCase):
  406. def setUp(self):
  407. # We build up a simple DAG.
  408. event_map = {}
  409. create_event = FakeEvent(
  410. id="CREATE",
  411. sender=ALICE,
  412. type=EventTypes.Create,
  413. state_key="",
  414. content={"creator": ALICE},
  415. ).to_event([], [])
  416. event_map[create_event.event_id] = create_event
  417. alice_member = FakeEvent(
  418. id="IMA",
  419. sender=ALICE,
  420. type=EventTypes.Member,
  421. state_key=ALICE,
  422. content=MEMBERSHIP_CONTENT_JOIN,
  423. ).to_event([create_event.event_id], [create_event.event_id])
  424. event_map[alice_member.event_id] = alice_member
  425. join_rules = FakeEvent(
  426. id="IJR",
  427. sender=ALICE,
  428. type=EventTypes.JoinRules,
  429. state_key="",
  430. content={"join_rule": JoinRules.PUBLIC},
  431. ).to_event(
  432. auth_events=[create_event.event_id, alice_member.event_id],
  433. prev_events=[alice_member.event_id],
  434. )
  435. event_map[join_rules.event_id] = join_rules
  436. # Bob and Charlie join at the same time, so there is a fork
  437. bob_member = FakeEvent(
  438. id="IMB",
  439. sender=BOB,
  440. type=EventTypes.Member,
  441. state_key=BOB,
  442. content=MEMBERSHIP_CONTENT_JOIN,
  443. ).to_event(
  444. auth_events=[create_event.event_id, join_rules.event_id],
  445. prev_events=[join_rules.event_id],
  446. )
  447. event_map[bob_member.event_id] = bob_member
  448. charlie_member = FakeEvent(
  449. id="IMC",
  450. sender=CHARLIE,
  451. type=EventTypes.Member,
  452. state_key=CHARLIE,
  453. content=MEMBERSHIP_CONTENT_JOIN,
  454. ).to_event(
  455. auth_events=[create_event.event_id, join_rules.event_id],
  456. prev_events=[join_rules.event_id],
  457. )
  458. event_map[charlie_member.event_id] = charlie_member
  459. self.event_map = event_map
  460. self.create_event = create_event
  461. self.alice_member = alice_member
  462. self.join_rules = join_rules
  463. self.bob_member = bob_member
  464. self.charlie_member = charlie_member
  465. self.state_at_bob = {
  466. (e.type, e.state_key): e.event_id
  467. for e in [create_event, alice_member, join_rules, bob_member]
  468. }
  469. self.state_at_charlie = {
  470. (e.type, e.state_key): e.event_id
  471. for e in [create_event, alice_member, join_rules, charlie_member]
  472. }
  473. self.expected_combined_state = {
  474. (e.type, e.state_key): e.event_id
  475. for e in [
  476. create_event,
  477. alice_member,
  478. join_rules,
  479. bob_member,
  480. charlie_member,
  481. ]
  482. }
  483. def test_event_map_none(self):
  484. # Test that we correctly handle passing `None` as the event_map
  485. state_d = resolve_events_with_store(
  486. FakeClock(),
  487. ROOM_ID,
  488. RoomVersions.V2.identifier,
  489. [self.state_at_bob, self.state_at_charlie],
  490. event_map=None,
  491. state_res_store=TestStateResolutionStore(self.event_map),
  492. )
  493. state = self.successResultOf(defer.ensureDeferred(state_d))
  494. self.assert_dict(self.expected_combined_state, state)
  495. def pairwise(iterable):
  496. "s -> (s0,s1), (s1,s2), (s2, s3), ..."
  497. a, b = itertools.tee(iterable)
  498. next(b, None)
  499. return zip(a, b)
  500. @attr.s
  501. class TestStateResolutionStore:
  502. event_map = attr.ib()
  503. def get_events(self, event_ids, allow_rejected=False):
  504. """Get events from the database
  505. Args:
  506. event_ids (list): The event_ids of the events to fetch
  507. allow_rejected (bool): If True return rejected events.
  508. Returns:
  509. Deferred[dict[str, FrozenEvent]]: Dict from event_id to event.
  510. """
  511. return defer.succeed(
  512. {eid: self.event_map[eid] for eid in event_ids if eid in self.event_map}
  513. )
  514. def _get_auth_chain(self, event_ids: List[str]) -> List[str]:
  515. """Gets the full auth chain for a set of events (including rejected
  516. events).
  517. Includes the given event IDs in the result.
  518. Note that:
  519. 1. All events must be state events.
  520. 2. For v1 rooms this may not have the full auth chain in the
  521. presence of rejected events
  522. Args:
  523. event_ids: The event IDs of the events to fetch the auth
  524. chain for. Must be state events.
  525. Returns:
  526. List of event IDs of the auth chain.
  527. """
  528. # Simple DFS for auth chain
  529. result = set()
  530. stack = list(event_ids)
  531. while stack:
  532. event_id = stack.pop()
  533. if event_id in result:
  534. continue
  535. result.add(event_id)
  536. event = self.event_map[event_id]
  537. for aid in event.auth_event_ids():
  538. stack.append(aid)
  539. return list(result)
  540. def get_auth_chain_difference(self, auth_sets):
  541. chains = [frozenset(self._get_auth_chain(a)) for a in auth_sets]
  542. common = set(chains[0]).intersection(*chains[1:])
  543. return defer.succeed(set(chains[0]).union(*chains[1:]) - common)