1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-08-11 17:50:59 +00:00

feat: MSC4297, State Resolution v2.1

This commit is contained in:
Matthias Ahouansou 2025-07-12 21:43:38 +01:00
parent bd8686ec20
commit d71d94a0c8
No known key found for this signature in database
3 changed files with 132 additions and 20 deletions

22
Cargo.lock generated
View file

@ -2521,7 +2521,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.12.6"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"assign",
"js_int",
@ -2540,7 +2540,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.12.2"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"js_int",
"ruma-common",
@ -2552,7 +2552,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.20.4"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"as_variant",
"assign",
@ -2575,7 +2575,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.15.4"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"as_variant",
"base64 0.22.1",
@ -2607,7 +2607,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.30.5"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"as_variant",
"indexmap 2.9.0",
@ -2631,7 +2631,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.11.2"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"bytes",
"headers",
@ -2653,7 +2653,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.10.1"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"js_int",
"thiserror 2.0.12",
@ -2662,7 +2662,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.15.2"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"cfg-if",
"proc-macro-crate",
@ -2677,7 +2677,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.11.0"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"js_int",
"ruma-common",
@ -2689,7 +2689,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.17.1"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@ -2705,7 +2705,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.13.0"
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
dependencies = [
"js_int",
"ruma-common",

View file

@ -5,7 +5,7 @@ use std::{
};
pub use data::Data;
use ruma::{api::client::error::ErrorKind, EventId, RoomId};
use ruma::{api::client::error::ErrorKind, state_res::StateMap, EventId, RoomId};
use tracing::{debug, error, warn};
use crate::{services, Error, Result};
@ -161,4 +161,87 @@ impl Service {
Ok(found)
}
#[tracing::instrument(skip(self, conflicted_state_set))]
/// Fetches the conflicted state subgraph of the given events
pub fn get_conflicted_state_subgraph(
&self,
room_id: &RoomId,
conflicted_state_set: &StateMap<Vec<Arc<EventId>>>,
) -> Result<HashSet<Arc<EventId>>> {
let conflicted_event_ids: HashSet<_> =
conflicted_state_set.values().flatten().cloned().collect();
let mut conflicted_state_subgraph = HashSet::new();
let mut stack = vec![conflicted_event_ids.iter().cloned().collect::<Vec<_>>()];
let mut path = Vec::new();
let mut seen_events = HashSet::new();
let next_event = |stack: &mut Vec<Vec<_>>, path: &mut Vec<_>| {
while stack.last().is_some_and(|s| s.is_empty()) {
stack.pop();
path.pop();
}
stack.last_mut().and_then(|s| s.pop())
};
while let Some(event_id) = next_event(&mut stack, &mut path) {
path.push(event_id.clone());
if conflicted_state_subgraph.contains(&event_id) {
// If we reach a conflicted state subgraph path, this path must also be part of
// the conflicted state subgraph, as we will eventually reach a conflicted event
// if we follow this path.
//
// We check if path > 1 here and below, as we don't consider a single conflicted
// event to be a path from one conflicted to another.
if path.len() > 1 {
conflicted_state_subgraph.extend(path.iter().cloned());
}
// All possible paths from this event must have been traversed in the iteration
// that caused this event to be added to the conflicted state subgraph in the first
// place.
//
// We pop the path here and below as it won't be removed by `next_event`, due to us
// never pushing it's auth events to the stack.
path.pop();
continue;
}
if conflicted_event_ids.contains(&event_id) && path.len() > 1 {
conflicted_state_subgraph.extend(path.iter().cloned());
}
if seen_events.contains(&event_id) {
// All possible paths from this event must have been traversed in the iteration
// that caused this event to be added to the conflicted state subgraph in the first
// place.
path.pop();
continue;
}
if let Some(pdu) = services().rooms.timeline.get_pdu(&event_id)? {
if pdu.room_id().as_ref() != room_id {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Evil event in db",
));
}
stack.push(pdu.auth_events.clone());
} else {
warn!(?event_id, "Could not find pdu mentioned in auth events");
return Err(Error::BadDatabase(
"Missing auth event for PDU stored in database",
));
}
seen_events.insert(event_id);
}
Ok(conflicted_state_subgraph)
}
}

View file

@ -31,7 +31,7 @@ use ruma::{
StateEventType, TimelineEventType,
},
int,
room_version_rules::{AuthorizationRules, RoomVersionRules},
room_version_rules::{AuthorizationRules, RoomVersionRules, StateResolutionV2Rules},
state_res::{self, StateMap},
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
OwnedServerName, OwnedServerSigningKeyId, RoomId, ServerName,
@ -709,10 +709,11 @@ impl Service {
let lock = services().globals.stateres_mutex.lock();
let result = state_res::resolve(
&room_version_id
.rules()
.expect("Supported room version has rules")
.authorization,
&room_version_rules.authorization,
room_version_rules
.state_res
.v2_rules()
.expect("We only support room versions using state resolution v2"),
&fork_states,
auth_chain_sets,
|id| {
@ -722,6 +723,13 @@ impl Service {
}
res.ok().flatten()
},
|css| {
services()
.rooms
.auth_chain
.get_conflicted_state_subgraph(room_id, css)
.ok()
},
);
drop(lock);
@ -961,7 +969,15 @@ impl Service {
}
let new_room_state = self
.resolve_state(room_id, &room_version_rules.authorization, state_after)
.resolve_state(
room_id,
&room_version_rules.authorization,
room_version_rules
.state_res
.v2_rules()
.expect("We only support room versions using state resolution v2"),
state_after,
)
.await?;
// Set the new room state to the resolved state
@ -1039,6 +1055,7 @@ impl Service {
&self,
room_id: &RoomId,
auth_rules: &AuthorizationRules,
state_res_rules: &StateResolutionV2Rules,
incoming_state: HashMap<u64, Arc<EventId>>,
) -> Result<Arc<HashSet<CompressedStateEvent>>> {
debug!("Loading current room state ids");
@ -1097,8 +1114,20 @@ impl Service {
};
let lock = services().globals.stateres_mutex.lock();
let state = match state_res::resolve(auth_rules, &fork_states, auth_chain_sets, fetch_event)
{
let state = match state_res::resolve(
auth_rules,
state_res_rules,
&fork_states,
auth_chain_sets,
fetch_event,
|css| {
services()
.rooms
.auth_chain
.get_conflicted_state_subgraph(room_id, css)
.ok()
},
) {
Ok(new_state) => new_state,
Err(_) => {
return Err(Error::bad_database("State resolution failed, either an event could not be found or deserialization"));