test_v2.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. # Copyright 2018 New Vector Ltd
  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. import itertools
  15. from typing import List
  16. import attr
  17. from twisted.internet import defer
  18. from synapse.api.constants import EventTypes, JoinRules, Membership
  19. from synapse.api.room_versions import RoomVersions
  20. from synapse.event_auth import auth_types_for_event
  21. from synapse.events import make_event_from_dict
  22. from synapse.state.v2 import (
  23. _get_auth_chain_difference,
  24. lexicographical_topological_sort,
  25. resolve_events_with_store,
  26. )
  27. from synapse.types import EventID
  28. from tests import unittest
  29. ALICE = "@alice:example.com"
  30. BOB = "@bob:example.com"
  31. CHARLIE = "@charlie:example.com"
  32. EVELYN = "@evelyn:example.com"
  33. ZARA = "@zara:example.com"
  34. ROOM_ID = "!test:example.com"
  35. MEMBERSHIP_CONTENT_JOIN = {"membership": Membership.JOIN}
  36. MEMBERSHIP_CONTENT_BAN = {"membership": Membership.BAN}
  37. ORIGIN_SERVER_TS = 0
  38. class FakeClock:
  39. def sleep(self, msec):
  40. return defer.succeed(None)
  41. class FakeEvent:
  42. """A fake event we use as a convenience.
  43. NOTE: Again as a convenience we use "node_ids" rather than event_ids to
  44. refer to events. The event_id has node_id as localpart and example.com
  45. as domain.
  46. """
  47. def __init__(self, id, sender, type, state_key, content):
  48. self.node_id = id
  49. self.event_id = EventID(id, "example.com").to_string()
  50. self.sender = sender
  51. self.type = type
  52. self.state_key = state_key
  53. self.content = content
  54. self.room_id = ROOM_ID
  55. def to_event(self, auth_events, prev_events):
  56. """Given the auth_events and prev_events, convert to a Frozen Event
  57. Args:
  58. auth_events (list[str]): list of event_ids
  59. prev_events (list[str]): list of event_ids
  60. Returns:
  61. FrozenEvent
  62. """
  63. global ORIGIN_SERVER_TS
  64. ts = ORIGIN_SERVER_TS
  65. ORIGIN_SERVER_TS = ORIGIN_SERVER_TS + 1
  66. event_dict = {
  67. "auth_events": [(a, {}) for a in auth_events],
  68. "prev_events": [(p, {}) for p in prev_events],
  69. "event_id": self.event_id,
  70. "sender": self.sender,
  71. "type": self.type,
  72. "content": self.content,
  73. "origin_server_ts": ts,
  74. "room_id": ROOM_ID,
  75. }
  76. if self.state_key is not None:
  77. event_dict["state_key"] = self.state_key
  78. return make_event_from_dict(event_dict)
  79. # All graphs start with this set of events
  80. INITIAL_EVENTS = [
  81. FakeEvent(
  82. id="CREATE",
  83. sender=ALICE,
  84. type=EventTypes.Create,
  85. state_key="",
  86. content={"creator": ALICE},
  87. ),
  88. FakeEvent(
  89. id="IMA",
  90. sender=ALICE,
  91. type=EventTypes.Member,
  92. state_key=ALICE,
  93. content=MEMBERSHIP_CONTENT_JOIN,
  94. ),
  95. FakeEvent(
  96. id="IPOWER",
  97. sender=ALICE,
  98. type=EventTypes.PowerLevels,
  99. state_key="",
  100. content={"users": {ALICE: 100}},
  101. ),
  102. FakeEvent(
  103. id="IJR",
  104. sender=ALICE,
  105. type=EventTypes.JoinRules,
  106. state_key="",
  107. content={"join_rule": JoinRules.PUBLIC},
  108. ),
  109. FakeEvent(
  110. id="IMB",
  111. sender=BOB,
  112. type=EventTypes.Member,
  113. state_key=BOB,
  114. content=MEMBERSHIP_CONTENT_JOIN,
  115. ),
  116. FakeEvent(
  117. id="IMC",
  118. sender=CHARLIE,
  119. type=EventTypes.Member,
  120. state_key=CHARLIE,
  121. content=MEMBERSHIP_CONTENT_JOIN,
  122. ),
  123. FakeEvent(
  124. id="IMZ",
  125. sender=ZARA,
  126. type=EventTypes.Member,
  127. state_key=ZARA,
  128. content=MEMBERSHIP_CONTENT_JOIN,
  129. ),
  130. FakeEvent(
  131. id="START", sender=ZARA, type=EventTypes.Message, state_key=None, content={}
  132. ),
  133. FakeEvent(
  134. id="END", sender=ZARA, type=EventTypes.Message, state_key=None, content={}
  135. ),
  136. ]
  137. INITIAL_EDGES = ["START", "IMZ", "IMC", "IMB", "IJR", "IPOWER", "IMA", "CREATE"]
  138. class StateTestCase(unittest.TestCase):
  139. def test_ban_vs_pl(self):
  140. events = [
  141. FakeEvent(
  142. id="PA",
  143. sender=ALICE,
  144. type=EventTypes.PowerLevels,
  145. state_key="",
  146. content={"users": {ALICE: 100, BOB: 50}},
  147. ),
  148. FakeEvent(
  149. id="MA",
  150. sender=ALICE,
  151. type=EventTypes.Member,
  152. state_key=ALICE,
  153. content={"membership": Membership.JOIN},
  154. ),
  155. FakeEvent(
  156. id="MB",
  157. sender=ALICE,
  158. type=EventTypes.Member,
  159. state_key=BOB,
  160. content={"membership": Membership.BAN},
  161. ),
  162. FakeEvent(
  163. id="PB",
  164. sender=BOB,
  165. type=EventTypes.PowerLevels,
  166. state_key="",
  167. content={"users": {ALICE: 100, BOB: 50}},
  168. ),
  169. ]
  170. edges = [["END", "MB", "MA", "PA", "START"], ["END", "PB", "PA"]]
  171. expected_state_ids = ["PA", "MA", "MB"]
  172. self.do_check(events, edges, expected_state_ids)
  173. def test_join_rule_evasion(self):
  174. events = [
  175. FakeEvent(
  176. id="JR",
  177. sender=ALICE,
  178. type=EventTypes.JoinRules,
  179. state_key="",
  180. content={"join_rules": JoinRules.PRIVATE},
  181. ),
  182. FakeEvent(
  183. id="ME",
  184. sender=EVELYN,
  185. type=EventTypes.Member,
  186. state_key=EVELYN,
  187. content={"membership": Membership.JOIN},
  188. ),
  189. ]
  190. edges = [["END", "JR", "START"], ["END", "ME", "START"]]
  191. expected_state_ids = ["JR"]
  192. self.do_check(events, edges, expected_state_ids)
  193. def test_offtopic_pl(self):
  194. events = [
  195. FakeEvent(
  196. id="PA",
  197. sender=ALICE,
  198. type=EventTypes.PowerLevels,
  199. state_key="",
  200. content={"users": {ALICE: 100, BOB: 50}},
  201. ),
  202. FakeEvent(
  203. id="PB",
  204. sender=BOB,
  205. type=EventTypes.PowerLevels,
  206. state_key="",
  207. content={"users": {ALICE: 100, BOB: 50, CHARLIE: 50}},
  208. ),
  209. FakeEvent(
  210. id="PC",
  211. sender=CHARLIE,
  212. type=EventTypes.PowerLevels,
  213. state_key="",
  214. content={"users": {ALICE: 100, BOB: 50, CHARLIE: 0}},
  215. ),
  216. ]
  217. edges = [["END", "PC", "PB", "PA", "START"], ["END", "PA"]]
  218. expected_state_ids = ["PC"]
  219. self.do_check(events, edges, expected_state_ids)
  220. def test_topic_basic(self):
  221. events = [
  222. FakeEvent(
  223. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  224. ),
  225. FakeEvent(
  226. id="PA1",
  227. sender=ALICE,
  228. type=EventTypes.PowerLevels,
  229. state_key="",
  230. content={"users": {ALICE: 100, BOB: 50}},
  231. ),
  232. FakeEvent(
  233. id="T2", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  234. ),
  235. FakeEvent(
  236. id="PA2",
  237. sender=ALICE,
  238. type=EventTypes.PowerLevels,
  239. state_key="",
  240. content={"users": {ALICE: 100, BOB: 0}},
  241. ),
  242. FakeEvent(
  243. id="PB",
  244. sender=BOB,
  245. type=EventTypes.PowerLevels,
  246. state_key="",
  247. content={"users": {ALICE: 100, BOB: 50}},
  248. ),
  249. FakeEvent(
  250. id="T3", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  251. ),
  252. ]
  253. edges = [["END", "PA2", "T2", "PA1", "T1", "START"], ["END", "T3", "PB", "PA1"]]
  254. expected_state_ids = ["PA2", "T2"]
  255. self.do_check(events, edges, expected_state_ids)
  256. def test_topic_reset(self):
  257. events = [
  258. FakeEvent(
  259. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  260. ),
  261. FakeEvent(
  262. id="PA",
  263. sender=ALICE,
  264. type=EventTypes.PowerLevels,
  265. state_key="",
  266. content={"users": {ALICE: 100, BOB: 50}},
  267. ),
  268. FakeEvent(
  269. id="T2", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  270. ),
  271. FakeEvent(
  272. id="MB",
  273. sender=ALICE,
  274. type=EventTypes.Member,
  275. state_key=BOB,
  276. content={"membership": Membership.BAN},
  277. ),
  278. ]
  279. edges = [["END", "MB", "T2", "PA", "T1", "START"], ["END", "T1"]]
  280. expected_state_ids = ["T1", "MB", "PA"]
  281. self.do_check(events, edges, expected_state_ids)
  282. def test_topic(self):
  283. events = [
  284. FakeEvent(
  285. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  286. ),
  287. FakeEvent(
  288. id="PA1",
  289. sender=ALICE,
  290. type=EventTypes.PowerLevels,
  291. state_key="",
  292. content={"users": {ALICE: 100, BOB: 50}},
  293. ),
  294. FakeEvent(
  295. id="T2", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  296. ),
  297. FakeEvent(
  298. id="PA2",
  299. sender=ALICE,
  300. type=EventTypes.PowerLevels,
  301. state_key="",
  302. content={"users": {ALICE: 100, BOB: 0}},
  303. ),
  304. FakeEvent(
  305. id="PB",
  306. sender=BOB,
  307. type=EventTypes.PowerLevels,
  308. state_key="",
  309. content={"users": {ALICE: 100, BOB: 50}},
  310. ),
  311. FakeEvent(
  312. id="T3", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  313. ),
  314. FakeEvent(
  315. id="MZ1",
  316. sender=ZARA,
  317. type=EventTypes.Message,
  318. state_key=None,
  319. content={},
  320. ),
  321. FakeEvent(
  322. id="T4", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  323. ),
  324. ]
  325. edges = [
  326. ["END", "T4", "MZ1", "PA2", "T2", "PA1", "T1", "START"],
  327. ["END", "MZ1", "T3", "PB", "PA1"],
  328. ]
  329. expected_state_ids = ["T4", "PA2"]
  330. self.do_check(events, edges, expected_state_ids)
  331. def test_mainline_sort(self):
  332. """Tests that the mainline ordering works correctly."""
  333. events = [
  334. FakeEvent(
  335. id="T1", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  336. ),
  337. FakeEvent(
  338. id="PA1",
  339. sender=ALICE,
  340. type=EventTypes.PowerLevels,
  341. state_key="",
  342. content={"users": {ALICE: 100, BOB: 50}},
  343. ),
  344. FakeEvent(
  345. id="T2", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  346. ),
  347. FakeEvent(
  348. id="PA2",
  349. sender=ALICE,
  350. type=EventTypes.PowerLevels,
  351. state_key="",
  352. content={
  353. "users": {ALICE: 100, BOB: 50},
  354. "events": {EventTypes.PowerLevels: 100},
  355. },
  356. ),
  357. FakeEvent(
  358. id="PB",
  359. sender=BOB,
  360. type=EventTypes.PowerLevels,
  361. state_key="",
  362. content={"users": {ALICE: 100, BOB: 50}},
  363. ),
  364. FakeEvent(
  365. id="T3", sender=BOB, type=EventTypes.Topic, state_key="", content={}
  366. ),
  367. FakeEvent(
  368. id="T4", sender=ALICE, type=EventTypes.Topic, state_key="", content={}
  369. ),
  370. ]
  371. edges = [
  372. ["END", "T3", "PA2", "T2", "PA1", "T1", "START"],
  373. ["END", "T4", "PB", "PA1"],
  374. ]
  375. # We expect T3 to be picked as the other topics are pointing at older
  376. # power levels. Note that without mainline ordering we'd pick T4 due to
  377. # it being sent *after* T3.
  378. expected_state_ids = ["T3", "PA2"]
  379. self.do_check(events, edges, expected_state_ids)
  380. def do_check(self, events, edges, expected_state_ids):
  381. """Take a list of events and edges and calculate the state of the
  382. graph at END, and asserts it matches `expected_state_ids`
  383. Args:
  384. events (list[FakeEvent])
  385. edges (list[list[str]]): A list of chains of event edges, e.g.
  386. `[[A, B, C]]` are edges A->B and B->C.
  387. expected_state_ids (list[str]): The expected state at END, (excluding
  388. the keys that haven't changed since START).
  389. """
  390. # We want to sort the events into topological order for processing.
  391. graph = {}
  392. # node_id -> FakeEvent
  393. fake_event_map = {}
  394. for ev in itertools.chain(INITIAL_EVENTS, events):
  395. graph[ev.node_id] = set()
  396. fake_event_map[ev.node_id] = ev
  397. for a, b in pairwise(INITIAL_EDGES):
  398. graph[a].add(b)
  399. for edge_list in edges:
  400. for a, b in pairwise(edge_list):
  401. graph[a].add(b)
  402. # event_id -> FrozenEvent
  403. event_map = {}
  404. # node_id -> state
  405. state_at_event = {}
  406. # We copy the map as the sort consumes the graph
  407. graph_copy = {k: set(v) for k, v in graph.items()}
  408. for node_id in lexicographical_topological_sort(graph_copy, key=lambda e: e):
  409. fake_event = fake_event_map[node_id]
  410. event_id = fake_event.event_id
  411. prev_events = list(graph[node_id])
  412. if len(prev_events) == 0:
  413. state_before = {}
  414. elif len(prev_events) == 1:
  415. state_before = dict(state_at_event[prev_events[0]])
  416. else:
  417. state_d = resolve_events_with_store(
  418. FakeClock(),
  419. ROOM_ID,
  420. RoomVersions.V2,
  421. [state_at_event[n] for n in prev_events],
  422. event_map=event_map,
  423. state_res_store=TestStateResolutionStore(event_map),
  424. )
  425. state_before = self.successResultOf(defer.ensureDeferred(state_d))
  426. state_after = dict(state_before)
  427. if fake_event.state_key is not None:
  428. state_after[(fake_event.type, fake_event.state_key)] = event_id
  429. auth_types = set(auth_types_for_event(RoomVersions.V6, fake_event))
  430. auth_events = []
  431. for key in auth_types:
  432. if key in state_before:
  433. auth_events.append(state_before[key])
  434. event = fake_event.to_event(auth_events, prev_events)
  435. state_at_event[node_id] = state_after
  436. event_map[event_id] = event
  437. expected_state = {}
  438. for node_id in expected_state_ids:
  439. # expected_state_ids are node IDs rather than event IDs,
  440. # so we have to convert
  441. event_id = EventID(node_id, "example.com").to_string()
  442. event = event_map[event_id]
  443. key = (event.type, event.state_key)
  444. expected_state[key] = event_id
  445. start_state = state_at_event["START"]
  446. end_state = {
  447. key: value
  448. for key, value in state_at_event["END"].items()
  449. if key in expected_state or start_state.get(key) != value
  450. }
  451. self.assertEqual(expected_state, end_state)
  452. class LexicographicalTestCase(unittest.TestCase):
  453. def test_simple(self):
  454. graph = {"l": {"o"}, "m": {"n", "o"}, "n": {"o"}, "o": set(), "p": {"o"}}
  455. res = list(lexicographical_topological_sort(graph, key=lambda x: x))
  456. self.assertEqual(["o", "l", "n", "m", "p"], res)
  457. class SimpleParamStateTestCase(unittest.TestCase):
  458. def setUp(self):
  459. # We build up a simple DAG.
  460. event_map = {}
  461. create_event = FakeEvent(
  462. id="CREATE",
  463. sender=ALICE,
  464. type=EventTypes.Create,
  465. state_key="",
  466. content={"creator": ALICE},
  467. ).to_event([], [])
  468. event_map[create_event.event_id] = create_event
  469. alice_member = FakeEvent(
  470. id="IMA",
  471. sender=ALICE,
  472. type=EventTypes.Member,
  473. state_key=ALICE,
  474. content=MEMBERSHIP_CONTENT_JOIN,
  475. ).to_event([create_event.event_id], [create_event.event_id])
  476. event_map[alice_member.event_id] = alice_member
  477. join_rules = FakeEvent(
  478. id="IJR",
  479. sender=ALICE,
  480. type=EventTypes.JoinRules,
  481. state_key="",
  482. content={"join_rule": JoinRules.PUBLIC},
  483. ).to_event(
  484. auth_events=[create_event.event_id, alice_member.event_id],
  485. prev_events=[alice_member.event_id],
  486. )
  487. event_map[join_rules.event_id] = join_rules
  488. # Bob and Charlie join at the same time, so there is a fork
  489. bob_member = FakeEvent(
  490. id="IMB",
  491. sender=BOB,
  492. type=EventTypes.Member,
  493. state_key=BOB,
  494. content=MEMBERSHIP_CONTENT_JOIN,
  495. ).to_event(
  496. auth_events=[create_event.event_id, join_rules.event_id],
  497. prev_events=[join_rules.event_id],
  498. )
  499. event_map[bob_member.event_id] = bob_member
  500. charlie_member = FakeEvent(
  501. id="IMC",
  502. sender=CHARLIE,
  503. type=EventTypes.Member,
  504. state_key=CHARLIE,
  505. content=MEMBERSHIP_CONTENT_JOIN,
  506. ).to_event(
  507. auth_events=[create_event.event_id, join_rules.event_id],
  508. prev_events=[join_rules.event_id],
  509. )
  510. event_map[charlie_member.event_id] = charlie_member
  511. self.event_map = event_map
  512. self.create_event = create_event
  513. self.alice_member = alice_member
  514. self.join_rules = join_rules
  515. self.bob_member = bob_member
  516. self.charlie_member = charlie_member
  517. self.state_at_bob = {
  518. (e.type, e.state_key): e.event_id
  519. for e in [create_event, alice_member, join_rules, bob_member]
  520. }
  521. self.state_at_charlie = {
  522. (e.type, e.state_key): e.event_id
  523. for e in [create_event, alice_member, join_rules, charlie_member]
  524. }
  525. self.expected_combined_state = {
  526. (e.type, e.state_key): e.event_id
  527. for e in [
  528. create_event,
  529. alice_member,
  530. join_rules,
  531. bob_member,
  532. charlie_member,
  533. ]
  534. }
  535. def test_event_map_none(self):
  536. # Test that we correctly handle passing `None` as the event_map
  537. state_d = resolve_events_with_store(
  538. FakeClock(),
  539. ROOM_ID,
  540. RoomVersions.V2,
  541. [self.state_at_bob, self.state_at_charlie],
  542. event_map=None,
  543. state_res_store=TestStateResolutionStore(self.event_map),
  544. )
  545. state = self.successResultOf(defer.ensureDeferred(state_d))
  546. self.assert_dict(self.expected_combined_state, state)
  547. class AuthChainDifferenceTestCase(unittest.TestCase):
  548. """We test that `_get_auth_chain_difference` correctly handles unpersisted
  549. events.
  550. """
  551. def test_simple(self):
  552. # Test getting the auth difference for a simple chain with a single
  553. # unpersisted event:
  554. #
  555. # Unpersisted | Persisted
  556. # |
  557. # C -|-> B -> A
  558. a = FakeEvent(
  559. id="A",
  560. sender=ALICE,
  561. type=EventTypes.Member,
  562. state_key="",
  563. content={},
  564. ).to_event([], [])
  565. b = FakeEvent(
  566. id="B",
  567. sender=ALICE,
  568. type=EventTypes.Member,
  569. state_key="",
  570. content={},
  571. ).to_event([a.event_id], [])
  572. c = FakeEvent(
  573. id="C",
  574. sender=ALICE,
  575. type=EventTypes.Member,
  576. state_key="",
  577. content={},
  578. ).to_event([b.event_id], [])
  579. persisted_events = {a.event_id: a, b.event_id: b}
  580. unpersited_events = {c.event_id: c}
  581. state_sets = [{"a": a.event_id, "b": b.event_id}, {"c": c.event_id}]
  582. store = TestStateResolutionStore(persisted_events)
  583. diff_d = _get_auth_chain_difference(
  584. ROOM_ID, state_sets, unpersited_events, store
  585. )
  586. difference = self.successResultOf(defer.ensureDeferred(diff_d))
  587. self.assertEqual(difference, {c.event_id})
  588. def test_multiple_unpersisted_chain(self):
  589. # Test getting the auth difference for a simple chain with multiple
  590. # unpersisted events:
  591. #
  592. # Unpersisted | Persisted
  593. # |
  594. # D -> C -|-> B -> A
  595. a = FakeEvent(
  596. id="A",
  597. sender=ALICE,
  598. type=EventTypes.Member,
  599. state_key="",
  600. content={},
  601. ).to_event([], [])
  602. b = FakeEvent(
  603. id="B",
  604. sender=ALICE,
  605. type=EventTypes.Member,
  606. state_key="",
  607. content={},
  608. ).to_event([a.event_id], [])
  609. c = FakeEvent(
  610. id="C",
  611. sender=ALICE,
  612. type=EventTypes.Member,
  613. state_key="",
  614. content={},
  615. ).to_event([b.event_id], [])
  616. d = FakeEvent(
  617. id="D",
  618. sender=ALICE,
  619. type=EventTypes.Member,
  620. state_key="",
  621. content={},
  622. ).to_event([c.event_id], [])
  623. persisted_events = {a.event_id: a, b.event_id: b}
  624. unpersited_events = {c.event_id: c, d.event_id: d}
  625. state_sets = [
  626. {"a": a.event_id, "b": b.event_id},
  627. {"c": c.event_id, "d": d.event_id},
  628. ]
  629. store = TestStateResolutionStore(persisted_events)
  630. diff_d = _get_auth_chain_difference(
  631. ROOM_ID, state_sets, unpersited_events, store
  632. )
  633. difference = self.successResultOf(defer.ensureDeferred(diff_d))
  634. self.assertEqual(difference, {d.event_id, c.event_id})
  635. def test_unpersisted_events_different_sets(self):
  636. # Test getting the auth difference for with multiple unpersisted events
  637. # in different branches:
  638. #
  639. # Unpersisted | Persisted
  640. # |
  641. # D --> C -|-> B -> A
  642. # E ----^ -|---^
  643. # |
  644. a = FakeEvent(
  645. id="A",
  646. sender=ALICE,
  647. type=EventTypes.Member,
  648. state_key="",
  649. content={},
  650. ).to_event([], [])
  651. b = FakeEvent(
  652. id="B",
  653. sender=ALICE,
  654. type=EventTypes.Member,
  655. state_key="",
  656. content={},
  657. ).to_event([a.event_id], [])
  658. c = FakeEvent(
  659. id="C",
  660. sender=ALICE,
  661. type=EventTypes.Member,
  662. state_key="",
  663. content={},
  664. ).to_event([b.event_id], [])
  665. d = FakeEvent(
  666. id="D",
  667. sender=ALICE,
  668. type=EventTypes.Member,
  669. state_key="",
  670. content={},
  671. ).to_event([c.event_id], [])
  672. e = FakeEvent(
  673. id="E",
  674. sender=ALICE,
  675. type=EventTypes.Member,
  676. state_key="",
  677. content={},
  678. ).to_event([c.event_id, b.event_id], [])
  679. persisted_events = {a.event_id: a, b.event_id: b}
  680. unpersited_events = {c.event_id: c, d.event_id: d, e.event_id: e}
  681. state_sets = [
  682. {"a": a.event_id, "b": b.event_id, "e": e.event_id},
  683. {"c": c.event_id, "d": d.event_id},
  684. ]
  685. store = TestStateResolutionStore(persisted_events)
  686. diff_d = _get_auth_chain_difference(
  687. ROOM_ID, state_sets, unpersited_events, store
  688. )
  689. difference = self.successResultOf(defer.ensureDeferred(diff_d))
  690. self.assertEqual(difference, {d.event_id, e.event_id})
  691. def pairwise(iterable):
  692. "s -> (s0,s1), (s1,s2), (s2, s3), ..."
  693. a, b = itertools.tee(iterable)
  694. next(b, None)
  695. return zip(a, b)
  696. @attr.s
  697. class TestStateResolutionStore:
  698. event_map = attr.ib()
  699. def get_events(self, event_ids, allow_rejected=False):
  700. """Get events from the database
  701. Args:
  702. event_ids (list): The event_ids of the events to fetch
  703. allow_rejected (bool): If True return rejected events.
  704. Returns:
  705. Deferred[dict[str, FrozenEvent]]: Dict from event_id to event.
  706. """
  707. return defer.succeed(
  708. {eid: self.event_map[eid] for eid in event_ids if eid in self.event_map}
  709. )
  710. def _get_auth_chain(self, event_ids: List[str]) -> List[str]:
  711. """Gets the full auth chain for a set of events (including rejected
  712. events).
  713. Includes the given event IDs in the result.
  714. Note that:
  715. 1. All events must be state events.
  716. 2. For v1 rooms this may not have the full auth chain in the
  717. presence of rejected events
  718. Args:
  719. event_ids: The event IDs of the events to fetch the auth
  720. chain for. Must be state events.
  721. Returns:
  722. List of event IDs of the auth chain.
  723. """
  724. # Simple DFS for auth chain
  725. result = set()
  726. stack = list(event_ids)
  727. while stack:
  728. event_id = stack.pop()
  729. if event_id in result:
  730. continue
  731. result.add(event_id)
  732. event = self.event_map[event_id]
  733. for aid in event.auth_event_ids():
  734. stack.append(aid)
  735. return list(result)
  736. def get_auth_chain_difference(self, room_id, auth_sets):
  737. chains = [frozenset(self._get_auth_chain(a)) for a in auth_sets]
  738. common = set(chains[0]).intersection(*chains[1:])
  739. return defer.succeed(set(chains[0]).union(*chains[1:]) - common)