Initial commit sketching out ideas

This commit is contained in:
Devin R 2020-07-17 10:07:03 -04:00
commit 61f485ea3f
7 changed files with 450 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "state-res"
version = "0.1.0"
authors = ["Devin R <devin.ragotzy@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
petgraph = "0.5.1"
serde = { version = "1.0.114", features = ["derive"] }
serde_json = "1.0.56"
[dependencies.ruma]
git = "https://github.com/ruma/ruma"
features = ["client-api", "federation-api", "appservice-api"]

154
src/lib.rs Normal file
View File

@ -0,0 +1,154 @@
use std::{collections::BTreeMap, time::SystemTime};
use petgraph::Graph;
use ruma::{
events::{
room::{self},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
},
identifiers::{EventId, RoomId, RoomVersionId},
};
use serde::{Deserialize, Serialize};
mod state_event;
mod state_store;
pub use state_event::StateEvent;
pub use state_store::StateStore;
pub enum ResolutionResult {
Conflicted(Vec<StateMap<EventId>>),
Resolved(Vec<StateMap<EventId>>),
}
/// A mapping of event type and state_key to some value `T`, usually an `EventId`.
pub type StateMap<T> = BTreeMap<(EventType, String), T>;
/// A mapping of `EventId` to `T`, usually a `StateEvent`.
pub type EventMap<T> = BTreeMap<EventId, T>;
#[derive(Debug, Default, Deserialize, Serialize)] // TODO make the ser/de impls useful
pub struct StateResolution {
// TODO remove pub after initial testing
/// The set of resolved events over time.
pub resolved_events: Vec<StateEvent>,
/// The resolved state, kept to have easy access to the last resolved
/// layer of state.
pub state: BTreeMap<EventType, BTreeMap<String, StateEvent>>,
/// The graph of authenticated events, kept to find the most recent auth event
/// in a chain for incoming state sets.
pub auth_graph: BTreeMap<EventId, Vec<StateMap<EventId>>>,
/// The last known point in the state graph.
pub most_recent_resolved: Option<(EventType, String)>,
// fields for temp storage during resolution
pub conflicting_events: Vec<StateEvent>,
}
impl StateResolution {
/// Resolve sets of state events as they come in. Internally `StateResolution` builds a graph
/// and an auth chain to allow for state conflict resolution.
pub fn resolve(
&mut self,
room_id: &RoomId,
room_version: &RoomVersionId,
state_sets: Vec<StateMap<EventId>>,
store: &mut dyn StateStore,
// TODO actual error handling (`thiserror`??)
) -> Result<ResolutionResult, serde_json::Error> {
let mut event_map = EventMap::new();
// split non-conflicting and conflicting state
let (clean, mut conflicting) = self.seperate(&state_sets);
if conflicting.is_empty() {
return Ok(ResolutionResult::Resolved(clean));
}
// the set of auth events that are not common across server forks
let mut auth_diff = self.get_auth_chain_diff(&state_sets, &mut event_map, store)?;
// add the auth_diff to conflicting now we have a full set of conflicting events
auth_diff.extend(conflicting.iter().flat_map(|map| map.values().cloned()));
let all_conflicted = auth_diff;
let all_conflicted = conflicting;
let power_events = all_conflicted
.iter()
.filter(is_power_event)
.flat_map(|map| map.values())
.cloned()
.collect::<Vec<_>>();
// sort the power events based on power_level/clock/event_id and outgoing/incoming edges
let sorted_power_levels = self.revers_topological_power_sort(
room_id,
&power_events,
&mut event_map,
store,
&all_conflicted,
);
// sequentially auth check each event.
let resolved = self.iterative_auth_check(
room_id,
room_version,
&power_events,
&clean,
&mut event_map,
store,
);
// TODO return something not a place holder
Ok(ResolutionResult::Resolved(vec![]))
}
fn seperate(
&mut self,
state_sets: &[StateMap<EventId>],
) -> (Vec<StateMap<EventId>>, Vec<StateMap<EventId>>) {
panic!()
}
/// Returns a Vec of deduped EventIds that appear in some chains but no others.
fn get_auth_chain_diff(
&mut self,
state_sets: &[StateMap<EventId>],
event_map: &EventMap<StateEvent>,
store: &mut dyn StateStore,
) -> Result<Vec<EventId>, serde_json::Error> {
panic!()
}
fn revers_topological_power_sort(
&mut self,
room_id: &RoomId,
power_events: &[EventId],
event_map: &EventMap<StateEvent>,
store: &mut dyn StateStore,
conflicted_set: &[StateMap<EventId>],
) -> Vec<StateEvent> {
panic!()
}
fn iterative_auth_check(
&mut self,
room_id: &RoomId,
room_version: &RoomVersionId,
power_events: &[EventId],
unconflicted_state: &[StateMap<EventId>],
event_map: &EventMap<StateEvent>,
store: &mut dyn StateStore,
) -> Vec<StateEvent> {
panic!()
}
}
pub fn is_power_event(event: &&StateMap<EventId>) -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
}

49
src/state_event.rs Normal file
View File

@ -0,0 +1,49 @@
use ruma::events::{
from_raw_json_value, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper,
};
use serde::{de, Serialize};
use serde_json::value::RawValue as RawJsonValue;
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum StateEvent {
Full(AnyStateEvent),
Sync(AnySyncStateEvent),
Stripped(AnyStrippedStateEvent),
}
impl<'de> de::Deserialize<'de> for StateEvent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
let EventDeHelper {
state_key,
event_id,
room_id,
unsigned,
..
} = from_raw_json_value(&json)?;
// Determine whether the event is a full, sync, or stripped
// based on the fields present.
if room_id.is_some() {
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
panic!("TODO deal with redacted events")
}
_ => StateEvent::Full(from_raw_json_value(&json)?),
})
} else if event_id.is_some() {
Ok(match unsigned {
Some(unsigned) if unsigned.redacted_because.is_some() => {
panic!("TODO deal with redacted events")
}
_ => StateEvent::Sync(from_raw_json_value(&json)?),
})
} else {
Ok(StateEvent::Stripped(from_raw_json_value(&json)?))
}
}
}

26
src/state_store.rs Normal file
View File

@ -0,0 +1,26 @@
use std::{collections::BTreeMap, time::SystemTime};
use petgraph::Graph;
use ruma::{
events::{
room::{self},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
},
identifiers::{EventId, RoomId, RoomVersionId},
};
use crate::StateEvent;
pub trait StateStore {
/// Returns the events that correspond to the `event_ids` sorted in the same order.
fn get_events(&self, event_ids: &[EventId]) -> Result<Vec<StateEvent>, serde_json::Error>;
/// Returns a tuple of requested state events from `event_id` and the auth chain events that
/// relate to the.
fn get_remote_state_for_room(
&self,
room_id: &RoomId,
version: &RoomVersionId,
event_id: &EventId,
) -> Result<(Vec<StateEvent>, Vec<StateEvent>), serde_json::Error>;
}

20
state.md Normal file
View File

@ -0,0 +1,20 @@
Would it be possible to abstract state res into a `ruma-state-res` crate? I've been thinking about something along the lines of
```rust
// The would need to be Serialize/Deserialize to save state
struct StateResV2 {
resolved_events: Vec<Event>,
state_graph: of indexes into the events field?,
most_recent_resolved: index or ptr into the graph?,
// fields for temp storage during resolution
conflicting_events: Vec<Event>,
}
impl StateResV2 {
/// The point of this all add nonconflicting events to the graph
/// and resolve and add conflicting events.
fn resolve(&mut self, events: Vec<Event>) -> Vec<Event> { }
}
```
Now to be totally fair I have no real understanding of state res

184
tests/init.rs Normal file
View File

@ -0,0 +1,184 @@
use std::convert::TryFrom;
use ruma::{
events::{
room::{self},
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
},
identifiers::{EventId, RoomId, RoomVersionId},
};
use serde_json::{from_value as from_json_value, json, Value as JsonValue};
use state_res::{ResolutionResult, StateEvent, StateResolution, StateStore};
// TODO make this an array of events
fn federated_json() -> JsonValue {
json!({
"content": {
"creator": "@example:example.org",
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "6"
},
"event_id": "$aaa:example.org",
"origin_server_ts": 1,
"room_id": "!room_id:example.org",
"sender": "@alice:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234
}
})
}
fn room_create() -> JsonValue {
json!({
"content": {
"creator": "@example:example.org",
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "6"
},
"event_id": "$aaa:example.org",
"origin_server_ts": 1,
"room_id": "!room_id:example.org",
"sender": "@alice:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234
}
})
}
fn join_rules() -> JsonValue {
json!({
"content": {
"join_rule": "public"
},
"event_id": "$bbb:example.org",
"origin_server_ts": 2,
"room_id": "!room_id:example.org",
"sender": "@alice:example.org",
"state_key": "",
"type": "m.room.join_rules",
"unsigned": {
"age": 1234
}
})
}
fn join_event() -> JsonValue {
json!({
"content": {
"avatar_url": null,
"displayname": "example",
"membership": "join"
},
"event_id": "$ccc:example.org",
"membership": "join",
"room_id": "!room_id:example.org",
"origin_server_ts": 3,
"sender": "@alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1,
"replaces_state": "$151800111315tsynI:example.org",
"prev_content": {
"avatar_url": null,
"displayname": "example",
"membership": "invite"
}
}
})
}
fn power_levels() -> JsonValue {
json!({
"content": {
"ban": 50,
"events": {
"m.room.name": 100,
"m.room.power_levels": 100
},
"events_default": 0,
"invite": 50,
"kick": 50,
"notifications": {
"room": 20
},
"redact": 50,
"state_default": 50,
"users": {
"@example:example.org": 100
},
"users_default": 0
},
"event_id": "$ddd:example.org",
"origin_server_ts": 4,
"room_id": "!room_id:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.power_levels",
"unsigned": {
"age": 1234
}
})
}
pub struct TestStore;
impl StateStore for TestStore {
fn get_events(&self, events: &[EventId]) -> Result<Vec<StateEvent>, serde_json::Error> {
Ok(vec![from_json_value(power_levels())?])
}
fn get_remote_state_for_room(
&self,
room_id: &RoomId,
version: &RoomVersionId,
event_id: &EventId,
) -> Result<(Vec<StateEvent>, Vec<StateEvent>), serde_json::Error> {
Ok((
vec![from_json_value(federated_json())?],
vec![from_json_value(power_levels())?],
))
}
}
#[test]
fn it_works() {
let mut store = TestStore;
let room_id = RoomId::try_from("!room_id:example.org").unwrap();
let room_version = RoomVersionId::version_6();
let a = from_json_value::<StateEvent>(room_create()).unwrap();
let b = from_json_value::<StateEvent>(join_rules()).unwrap();
let c = from_json_value::<StateEvent>(join_event()).unwrap();
let mut resolver = StateResolution::default();
let res = resolver
.resolve(&room_id, &room_version, vec![a.clone()], &mut store)
.unwrap();
assert!(if let ResolutionResult::Resolved(_) = res {
true
} else {
false
});
let resolved = resolver
.resolve(&room_id, &room_version, vec![b, c], &mut store)
.unwrap();
assert!(resolver.conflicting_events.is_empty());
assert_eq!(resolver.resolved_events.len(), 3);
}