Skip to main content

tuwunel_service/rooms/state_res/
resolve.rs

1#[cfg(test)]
2mod tests;
3
4mod auth_difference;
5mod conflicted_subgraph;
6mod iterative_auth_check;
7mod mainline_sort;
8mod power_sort;
9mod split_conflicted;
10
11use std::{
12	collections::{BTreeMap, HashSet},
13	ops::Deref,
14	vec::IntoIter,
15};
16
17use futures::{FutureExt, Stream, StreamExt, TryFutureExt};
18use ruma::{OwnedEventId, events::StateEventType, room_version_rules::RoomVersionRules};
19use tuwunel_core::{
20	Result, debug,
21	itertools::Itertools,
22	matrix::{Event, TypeStateKey, event_id::RandomState},
23	smallvec::SmallVec,
24	trace,
25	utils::{
26		BoolExt,
27		stream::{BroadbandExt, IterStream},
28	},
29};
30
31use self::{
32	auth_difference::auth_difference, conflicted_subgraph::conflicted_subgraph_dfs,
33	iterative_auth_check::iterative_auth_check, mainline_sort::mainline_sort,
34	power_sort::power_sort, split_conflicted::split_conflicted_state,
35};
36#[cfg(test)]
37use super::test_utils;
38
39/// A mapping of event type and state_key to some value `T`, usually an
40/// `EventId`.
41pub type StateMap<Id> = BTreeMap<TypeStateKey, Id>;
42
43/// Full recursive auth chain for one candidate [`StateMap`].
44///
45/// Values are distinct and immutable after construction. Their order is
46/// arbitrary, and consumers must not depend on it.
47#[derive(Clone)]
48pub struct AuthSet<Id>(Vec<Id>);
49
50/// Conflicting event ids for each contested state key.
51pub type ConflictMap<Id> = StateMap<ConflictVec<Id>>;
52
53/// Event ids contesting one state key.
54///
55/// Two forks disputing a key is the modal conflict, so two ids stay inline.
56type ConflictVec<Id> = SmallVec<[Id; 2]>;
57
58/// The full conflicted set (arbitrary order).
59type ConflictedSet = HashSet<OwnedEventId, RandomState>;
60
61impl<Id> AuthSet<Id> {
62	/// Creates an auth set from distinct identifiers.
63	///
64	/// The caller must ensure `ids` contains no duplicates. Duplicates are
65	/// not checked, so hot paths avoid redundant work.
66	#[inline]
67	#[must_use]
68	pub(crate) fn from_distinct(ids: Vec<Id>) -> Self { Self(ids) }
69}
70
71impl<Id> Default for AuthSet<Id> {
72	fn default() -> Self { Self(Vec::new()) }
73}
74
75impl<Id: Ord> FromIterator<Id> for AuthSet<Id> {
76	fn from_iter<I: IntoIterator<Item = Id>>(iter: I) -> Self {
77		Self::from_distinct(
78			iter.into_iter()
79				.sorted_unstable()
80				.dedup()
81				.collect(),
82		)
83	}
84}
85
86impl<Id> IntoIterator for AuthSet<Id> {
87	type IntoIter = IntoIter<Id>;
88	type Item = Id;
89
90	fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
91}
92
93/// Apply the [state resolution] algorithm introduced in room version 2 to
94/// resolve the state of a room.
95///
96/// ## Arguments
97///
98/// * `rules` - The rules to apply for the version of the current room.
99///
100/// * `state_maps` - The incoming states to resolve. Each `StateMap` represents
101///   a possible fork in the state of a room.
102///
103/// * `auth_sets` - The list of full recursive sets of `auth_events` for each
104///   event in the `state_maps`. Inputs must not contain duplicates.
105///
106/// * `fetch_event` - Function to fetch an event in the room given its event ID.
107///
108/// ## Invariants
109///
110/// The caller of `resolve` must ensure that all the events are from the same
111/// room.
112///
113/// ## Returns
114///
115/// The resolved room state.
116///
117/// [state resolution]: https://spec.matrix.org/latest/rooms/v2/#state-resolution
118#[tracing::instrument(level = "debug", skip_all)]
119pub async fn resolve<States, AuthSets, FetchExists, ExistsFut, FetchEvent, EventFut, Pdu>(
120	rules: &RoomVersionRules,
121	state_maps: States,
122	auth_sets: AuthSets,
123	fetch: &FetchEvent,
124	exists: &FetchExists,
125	hydra_backports: bool,
126) -> Result<StateMap<OwnedEventId>>
127where
128	States: Stream<Item = StateMap<OwnedEventId>> + Send,
129	AuthSets: Stream<Item = AuthSet<OwnedEventId>> + Send,
130	FetchExists: Fn(OwnedEventId) -> ExistsFut + Sync,
131	ExistsFut: Future<Output = bool> + Send,
132	FetchEvent: Fn(OwnedEventId) -> EventFut + Sync,
133	EventFut: Future<Output = Result<Pdu>> + Send,
134	Pdu: Event + Clone,
135{
136	// Split the unconflicted state map and the conflicted state set.
137	let (unconflicted_state, conflicted_states) = split_conflicted_state(state_maps).await;
138
139	debug!(
140		unconflicted = unconflicted_state.len(),
141		conflicted_states = conflicted_states.len(),
142		conflicted_events = conflicted_states
143			.values()
144			.fold(0_usize, |a, s| a.saturating_add(s.len())),
145		"unresolved states"
146	);
147
148	trace!(
149		?unconflicted_state,
150		?conflicted_states,
151		unconflicted = unconflicted_state.len(),
152		conflicted_states = conflicted_states.len(),
153		"unresolved states"
154	);
155
156	if conflicted_states.is_empty() {
157		return Ok(unconflicted_state.into_iter().collect());
158	}
159
160	// 0. The full conflicted set is the union of the conflicted state set and the
161	//    auth difference. Don't honor events that don't exist.
162	let full_conflicted_set =
163		full_conflicted_set(rules, conflicted_states, auth_sets, fetch, exists, hydra_backports)
164			.await;
165
166	// 1. Select the set X of all power events that appear in the full conflicted
167	//    set. For each such power event P, enlarge X by adding the events in the
168	//    auth chain of P which also belong to the full conflicted set. Sort X into
169	//    a list using the reverse topological power ordering.
170	let sorted_power_set: Vec<_> = power_sort(rules, &full_conflicted_set, fetch)
171		.inspect_ok(|list| debug!(count = list.len(), "sorted power events"))
172		.inspect_ok(|list| trace!(?list, "sorted power events"))
173		.boxed()
174		.await?;
175
176	let power_set_event_ids: Vec<_> = sorted_power_set
177		.iter()
178		.sorted_unstable()
179		.collect();
180
181	let sorted_power_set = sorted_power_set
182		.iter()
183		.stream()
184		.map(AsRef::as_ref);
185
186	let begin_with_empty_state_map = rules
187		.state_res
188		.v2_rules()
189		.is_some_and(|r| r.begin_iterative_auth_checks_with_empty_state_map)
190		|| hydra_backports;
191
192	let initial_state = begin_with_empty_state_map
193		.is_false()
194		.then(|| unconflicted_state.clone())
195		.unwrap_or_default();
196
197	// 2. Apply the iterative auth checks algorithm, starting from the unconflicted
198	//    state map, to the list of events from the previous step to get a partially
199	//    resolved state.
200	let partially_resolved_state =
201		iterative_auth_check(rules, sorted_power_set, initial_state, fetch)
202			.inspect_ok(|map| debug!(count = map.len(), "partially resolved power state"))
203			.inspect_ok(|map| trace!(?map, "partially resolved power state"))
204			.boxed()
205			.await?;
206
207	// This "epochs" power level event
208	let power_ty_sk = (StateEventType::RoomPowerLevels, "".into());
209	let power_event = partially_resolved_state.get(&power_ty_sk);
210	debug!(event_id = ?power_event, "epoch power event");
211
212	let remaining_events: Vec<_> = full_conflicted_set
213		.into_iter()
214		.filter(|id| power_set_event_ids.binary_search(&id).is_err())
215		.collect();
216
217	debug!(count = remaining_events.len(), "remaining events");
218	trace!(list = ?remaining_events, "remaining events");
219
220	let have_remaining_events = !remaining_events.is_empty();
221	let remaining_events = remaining_events
222		.iter()
223		.stream()
224		.map(AsRef::as_ref);
225
226	// 3. Take all remaining events that weren’t picked in step 1 and order them by
227	//    the mainline ordering based on the power level in the partially resolved
228	//    state obtained in step 2.
229	let sorted_remaining_events = have_remaining_events
230		.then_async(move || mainline_sort(power_event.cloned(), remaining_events, fetch))
231		.boxed();
232
233	let sorted_remaining_events = sorted_remaining_events
234		.await
235		.unwrap_or(Ok(Vec::new()))?;
236
237	debug!(count = sorted_remaining_events.len(), "sorted remaining events");
238	trace!(list = ?sorted_remaining_events, "sorted remaining events");
239
240	let sorted_remaining_events = sorted_remaining_events
241		.iter()
242		.stream()
243		.map(AsRef::as_ref);
244
245	// 4. Apply the iterative auth checks algorithm on the partial resolved state
246	//    and the list of events from the previous step.
247	let mut resolved_state =
248		iterative_auth_check(rules, sorted_remaining_events, partially_resolved_state, fetch)
249			.boxed()
250			.await?;
251
252	// 5. Update the result by replacing any event with the event with the same key
253	//    from the unconflicted state map, if such an event exists, to get the final
254	//    resolved state.
255	resolved_state.extend(unconflicted_state);
256
257	debug!(resolved_state = resolved_state.len(), "resolved state");
258	trace!(?resolved_state, "resolved state");
259
260	Ok(resolved_state)
261}
262
263#[tracing::instrument(
264	name = "conflicted",
265	level = "debug",
266	skip_all,
267	fields(
268		states = conflicted_states.len(),
269		events = conflicted_states.values().flatten().count()
270	),
271)]
272async fn full_conflicted_set<AuthSets, FetchExists, ExistsFut, FetchEvent, EventFut, Pdu>(
273	rules: &RoomVersionRules,
274	conflicted_states: ConflictMap<OwnedEventId>,
275	auth_sets: AuthSets,
276	fetch: &FetchEvent,
277	exists: &FetchExists,
278	hydra_backports: bool,
279) -> ConflictedSet
280where
281	AuthSets: Stream<Item = AuthSet<OwnedEventId>> + Send,
282	FetchExists: Fn(OwnedEventId) -> ExistsFut + Sync,
283	ExistsFut: Future<Output = bool> + Send,
284	FetchEvent: Fn(OwnedEventId) -> EventFut + Sync,
285	EventFut: Future<Output = Result<Pdu>> + Send,
286	Pdu: Event,
287{
288	let consider_conflicted_subgraph = rules
289		.state_res
290		.v2_rules()
291		.is_some_and(|rules| rules.consider_conflicted_state_subgraph)
292		|| hydra_backports;
293
294	let conflicted_state_set: Vec<_> = conflicted_states
295		.values()
296		.flatten()
297		.sorted_unstable()
298		.dedup()
299		.collect();
300
301	// Since `org.matrix.hydra.11`, fetch the conflicted state subgraph.
302	let conflicted_subgraph = consider_conflicted_subgraph
303		.then_async(async || conflicted_subgraph_dfs(&conflicted_state_set, fetch))
304		.map(Option::into_iter)
305		.map(IterStream::stream)
306		.flatten_stream()
307		.flatten()
308		.boxed();
309
310	let conflicted_state_ids = conflicted_state_set
311		.iter()
312		.map(Deref::deref)
313		.cloned()
314		.stream();
315
316	auth_difference(auth_sets)
317		.chain(conflicted_state_ids)
318		.broad_filter_map(async |id| exists(id.clone()).await.then_some(id))
319		.chain(conflicted_subgraph)
320		.collect::<ConflictedSet>()
321		.inspect(|set| debug!(count = set.len(), "full conflicted set"))
322		.inspect(|set| trace!(?set, "full conflicted set"))
323		.await
324}