tuwunel_matrix/room/state/resolution/
resolve.rs1#[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, TryStreamExt};
18use ruma::{OwnedEventId, events::StateEventType, room_version_rules::RoomVersionRules};
19use tuwunel_core::{
20 Result, debug,
21 itertools::Itertools,
22 smallvec::SmallVec,
23 trace,
24 utils::{
25 BoolExt,
26 stream::{BroadbandExt, IterStream, ReadyExt},
27 },
28};
29
30use self::{
31 auth_difference::auth_difference, conflicted_subgraph::conflicted_subgraph_dfs,
32 iterative_auth_check::iterative_auth_check, mainline_sort::mainline_sort,
33 power_sort::power_sort, split_conflicted::split_conflicted_state,
34};
35use super::FetchEvent;
36#[cfg(test)]
37use super::test_utils;
38use crate::{TypeStateKey, event_id::RandomState};
39
40pub type StateMap<Id> = BTreeMap<TypeStateKey, Id>;
43
44#[derive(Clone)]
49pub struct AuthSet<Id>(Vec<Id>);
50
51pub type ConflictMap<Id> = StateMap<ConflictVec<Id>>;
53
54type ConflictVec<Id> = SmallVec<[Id; 2]>;
58
59type ConflictedSet = HashSet<OwnedEventId, RandomState>;
61
62impl<Id> AuthSet<Id> {
63 #[inline]
69 #[must_use]
70 pub fn from_distinct(ids: Vec<Id>) -> Self { Self(ids) }
71}
72
73impl<Id> Default for AuthSet<Id> {
74 fn default() -> Self { Self(Vec::new()) }
75}
76
77impl<Id: Ord> FromIterator<Id> for AuthSet<Id> {
78 fn from_iter<I: IntoIterator<Item = Id>>(iter: I) -> Self {
79 Self::from_distinct(
80 iter.into_iter()
81 .sorted_unstable()
82 .dedup()
83 .collect(),
84 )
85 }
86}
87
88impl<Id> IntoIterator for AuthSet<Id> {
89 type IntoIter = IntoIter<Id>;
90 type Item = Id;
91
92 fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
93}
94
95#[tracing::instrument(level = "debug", skip_all)]
105pub async fn resolve<States, AuthSets, Fetch>(
106 rules: &RoomVersionRules,
107 state_maps: States,
108 auth_sets: AuthSets,
109 fetch: Fetch,
110 hydra_backports: bool,
111) -> Result<StateMap<OwnedEventId>>
112where
113 States: Stream<Item = StateMap<OwnedEventId>> + Send,
114 AuthSets: Stream<Item = AuthSet<OwnedEventId>> + Send,
115 Fetch: FetchEvent,
116{
117 let (unconflicted_state, conflicted_states) = split_conflicted_state(state_maps).await;
119
120 debug!(
121 unconflicted = unconflicted_state.len(),
122 conflicted_states = conflicted_states.len(),
123 conflicted_events = conflicted_states
124 .values()
125 .fold(0_usize, |a, s| a.saturating_add(s.len())),
126 "unresolved states"
127 );
128
129 trace!(
130 ?unconflicted_state,
131 ?conflicted_states,
132 unconflicted = unconflicted_state.len(),
133 conflicted_states = conflicted_states.len(),
134 "unresolved states"
135 );
136
137 if conflicted_states.is_empty() {
138 return Ok(unconflicted_state.into_iter().collect());
139 }
140
141 let full_conflicted_set =
144 full_conflicted_set(rules, conflicted_states, auth_sets, fetch, hydra_backports).await?;
145
146 let sorted_power_set: Vec<_> = power_sort(rules, &full_conflicted_set, fetch)
151 .inspect_ok(|list| debug!(count = list.len(), "sorted power events"))
152 .inspect_ok(|list| trace!(?list, "sorted power events"))
153 .boxed() .await?;
155
156 let power_set_event_ids: Vec<_> = sorted_power_set
157 .iter()
158 .sorted_unstable()
159 .collect();
160
161 let sorted_power_set = sorted_power_set
162 .iter()
163 .stream()
164 .map(AsRef::as_ref);
165
166 let begin_with_empty_state_map = rules
167 .state_res
168 .v2_rules()
169 .is_some_and(|r| r.begin_iterative_auth_checks_with_empty_state_map)
170 || hydra_backports;
171
172 let initial_state = begin_with_empty_state_map
173 .is_false()
174 .then(|| unconflicted_state.clone())
175 .unwrap_or_default();
176
177 let partially_resolved_state =
182 iterative_auth_check(rules, sorted_power_set, initial_state, fetch)
183 .boxed()
184 .inspect_ok(|map| debug!(count = map.len(), "partially resolved power state"))
185 .inspect_ok(|map| trace!(?map, "partially resolved power state"))
186 .await?;
187
188 let power_ty_sk = (StateEventType::RoomPowerLevels, "".into());
190 let power_event = partially_resolved_state.get(&power_ty_sk);
191 debug!(event_id = ?power_event, "epoch power event");
192
193 let remaining_events: Vec<_> = full_conflicted_set
194 .into_iter()
195 .filter(|id| power_set_event_ids.binary_search(&id).is_err())
196 .collect();
197
198 debug!(count = remaining_events.len(), "remaining events");
199 trace!(list = ?remaining_events, "remaining events");
200
201 let have_remaining_events = !remaining_events.is_empty();
202 let remaining_events = remaining_events
203 .iter()
204 .stream()
205 .map(AsRef::as_ref);
206
207 let sorted_remaining_events = have_remaining_events
211 .then_async(move || mainline_sort(power_event.cloned(), remaining_events, fetch))
212 .boxed(); let sorted_remaining_events = sorted_remaining_events
215 .await
216 .unwrap_or(Ok(Vec::new()))?;
217
218 debug!(count = sorted_remaining_events.len(), "sorted remaining events");
219 trace!(list = ?sorted_remaining_events, "sorted remaining events");
220
221 let sorted_remaining_events = sorted_remaining_events
222 .iter()
223 .stream()
224 .map(AsRef::as_ref);
225
226 let mut resolved_state =
230 iterative_auth_check(rules, sorted_remaining_events, partially_resolved_state, fetch)
231 .boxed()
232 .await?;
233
234 resolved_state.extend(unconflicted_state);
238
239 debug!(resolved_state = resolved_state.len(), "resolved state");
240 trace!(?resolved_state, "resolved state");
241
242 Ok(resolved_state)
243}
244
245#[tracing::instrument(
246 name = "conflicted",
247 level = "debug",
248 skip_all,
249 fields(
250 states = conflicted_states.len(),
251 events = conflicted_states.values().flatten().count()
252 ),
253)]
254async fn full_conflicted_set<AuthSets>(
255 rules: &RoomVersionRules,
256 conflicted_states: ConflictMap<OwnedEventId>,
257 auth_sets: AuthSets,
258 fetch: impl FetchEvent,
259 hydra_backports: bool,
260) -> Result<ConflictedSet>
261where
262 AuthSets: Stream<Item = AuthSet<OwnedEventId>> + Send,
263{
264 let consider_conflicted_subgraph = rules
265 .state_res
266 .v2_rules()
267 .is_some_and(|rules| rules.consider_conflicted_state_subgraph)
268 || hydra_backports;
269
270 let conflicted_state_set: Vec<_> = conflicted_states
271 .values()
272 .flatten()
273 .sorted_unstable()
274 .dedup()
275 .collect();
276
277 let conflicted_subgraph = consider_conflicted_subgraph
279 .then_async(async || conflicted_subgraph_dfs(&conflicted_state_set, fetch))
280 .map(Option::into_iter)
281 .map(IterStream::stream)
282 .flatten_stream()
283 .flatten()
284 .boxed(); let conflicted_state_ids = conflicted_state_set
287 .iter()
288 .map(Deref::deref)
289 .cloned()
290 .stream();
291
292 auth_difference(auth_sets)
293 .chain(conflicted_state_ids)
294 .broad_then(async |id| {
295 let exists = fetch.exists(&id).await?;
296
297 Ok(exists.then_some(id))
298 })
299 .ready_filter_map(Result::transpose)
300 .chain(conflicted_subgraph)
301 .try_collect::<ConflictedSet>()
302 .inspect_ok(|set| debug!(count = set.len(), "full conflicted set"))
303 .inspect_ok(|set| trace!(?set, "full conflicted set"))
304 .await
305}