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:
parent
bd8686ec20
commit
d71d94a0c8
3 changed files with 132 additions and 20 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -2521,7 +2521,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.12.6"
|
version = "0.12.6"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
"js_int",
|
"js_int",
|
||||||
|
@ -2540,7 +2540,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2552,7 +2552,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.20.4"
|
version = "0.20.4"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
|
@ -2575,7 +2575,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
|
@ -2607,7 +2607,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.30.5"
|
version = "0.30.5"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap 2.9.0",
|
"indexmap 2.9.0",
|
||||||
|
@ -2631,7 +2631,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"headers",
|
"headers",
|
||||||
|
@ -2653,7 +2653,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
|
@ -2662,7 +2662,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
|
@ -2677,7 +2677,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
@ -2689,7 +2689,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.17.1"
|
version = "0.17.1"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
@ -2705,7 +2705,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-state-res"
|
name = "ruma-state-res"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://github.com/ruma/ruma.git#796cc9f3aacb7e69f6ec3a0d5c2ba900c3e65910"
|
source = "git+https://github.com/ruma/ruma.git#547efbf24831066ae3199dc51b93f6b3a30ea8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use data::Data;
|
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 tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::{services, Error, Result};
|
use crate::{services, Error, Result};
|
||||||
|
@ -161,4 +161,87 @@ impl Service {
|
||||||
|
|
||||||
Ok(found)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ use ruma::{
|
||||||
StateEventType, TimelineEventType,
|
StateEventType, TimelineEventType,
|
||||||
},
|
},
|
||||||
int,
|
int,
|
||||||
room_version_rules::{AuthorizationRules, RoomVersionRules},
|
room_version_rules::{AuthorizationRules, RoomVersionRules, StateResolutionV2Rules},
|
||||||
state_res::{self, StateMap},
|
state_res::{self, StateMap},
|
||||||
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
uint, CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch,
|
||||||
OwnedServerName, OwnedServerSigningKeyId, RoomId, ServerName,
|
OwnedServerName, OwnedServerSigningKeyId, RoomId, ServerName,
|
||||||
|
@ -709,10 +709,11 @@ impl Service {
|
||||||
let lock = services().globals.stateres_mutex.lock();
|
let lock = services().globals.stateres_mutex.lock();
|
||||||
|
|
||||||
let result = state_res::resolve(
|
let result = state_res::resolve(
|
||||||
&room_version_id
|
&room_version_rules.authorization,
|
||||||
.rules()
|
room_version_rules
|
||||||
.expect("Supported room version has rules")
|
.state_res
|
||||||
.authorization,
|
.v2_rules()
|
||||||
|
.expect("We only support room versions using state resolution v2"),
|
||||||
&fork_states,
|
&fork_states,
|
||||||
auth_chain_sets,
|
auth_chain_sets,
|
||||||
|id| {
|
|id| {
|
||||||
|
@ -722,6 +723,13 @@ impl Service {
|
||||||
}
|
}
|
||||||
res.ok().flatten()
|
res.ok().flatten()
|
||||||
},
|
},
|
||||||
|
|css| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.auth_chain
|
||||||
|
.get_conflicted_state_subgraph(room_id, css)
|
||||||
|
.ok()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
|
@ -961,7 +969,15 @@ impl Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_room_state = self
|
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?;
|
.await?;
|
||||||
|
|
||||||
// Set the new room state to the resolved state
|
// Set the new room state to the resolved state
|
||||||
|
@ -1039,6 +1055,7 @@ impl Service {
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
auth_rules: &AuthorizationRules,
|
auth_rules: &AuthorizationRules,
|
||||||
|
state_res_rules: &StateResolutionV2Rules,
|
||||||
incoming_state: HashMap<u64, Arc<EventId>>,
|
incoming_state: HashMap<u64, Arc<EventId>>,
|
||||||
) -> Result<Arc<HashSet<CompressedStateEvent>>> {
|
) -> Result<Arc<HashSet<CompressedStateEvent>>> {
|
||||||
debug!("Loading current room state ids");
|
debug!("Loading current room state ids");
|
||||||
|
@ -1097,8 +1114,20 @@ impl Service {
|
||||||
};
|
};
|
||||||
|
|
||||||
let lock = services().globals.stateres_mutex.lock();
|
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,
|
Ok(new_state) => new_state,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(Error::bad_database("State resolution failed, either an event could not be found or deserialization"));
|
return Err(Error::bad_database("State resolution failed, either an event could not be found or deserialization"));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue