From 80c8d3a66bf87bc77d4c24ba9bb77be1515d796e Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Mon, 5 Jul 2021 16:12:44 +0200 Subject: [PATCH] Add state resolution function --- lib/matrix_server/event.ex | 204 ++++++++++++++++++++++++++++++++++--- 1 file changed, 189 insertions(+), 15 deletions(-) diff --git a/lib/matrix_server/event.ex b/lib/matrix_server/event.ex index 1756f40..d927296 100644 --- a/lib/matrix_server/event.ex +++ b/lib/matrix_server/event.ex @@ -20,6 +20,7 @@ defmodule MatrixServer.Event do def new_state_event, do: %Event{new() | event_type: :state} def new_message_event, do: %Event{new() | event_type: :message} + # TODO: remove state_key default here def new do %Event{ event_id: "", @@ -83,10 +84,20 @@ defmodule MatrixServer.Event do Map.get(power_levels, user, 0) >= level end + # No join rules specified, allow joining for room creator only. + def allowed_to_join(user, state_set) when not is_map_key(state_set, {:join_rules, ""}) do + state_set[{:create, ""}].sender == user + end + # TODO: join and power levels events def is_authorized(%Event{event_type: :create, prev_events: prev_events}, _), do: prev_events == [] + def is_authorized(%Event{event_type: :membership, content: "join", state_key: user}, state_set) do + IO.puts("WORKING YO") + allowed_to_join(user, state_set) + end + def is_authorized(%Event{sender: sender} = event, state_set) do in_room(sender, state_set) and has_power_level(sender, get_power_levels(state_set), get_event_power_level(event)) @@ -113,13 +124,9 @@ defmodule MatrixServer.Event do Map.put(state_set, {event_type, state_key}, event) end - def is_control_event(%Event{event_type: :power_levels, state_key: state_key}) - when state_key != "", - do: true + def is_control_event(%Event{event_type: :power_levels, state_key: ""}), do: true - def is_control_event(%Event{event_type: :join_rules, state_key: state_key}) - when state_key != "", - do: true + def is_control_event(%Event{event_type: :join_rules, state_key: ""}), do: true def is_control_event(%Event{ event_type: :membership, @@ -189,11 +196,12 @@ defmodule MatrixServer.Event do end def auth_difference(state_sets) do - full_auth_chains = Enum.map(state_sets, fn state_set -> - state_set - |> Map.values() - |> full_auth_chain() - end) + full_auth_chains = + Enum.map(state_sets, fn state_set -> + state_set + |> Map.values() + |> full_auth_chain() + end) auth_chain_union = Enum.reduce(full_auth_chains, MapSet.new(), &MapSet.union/2) auth_chain_intersection = Enum.reduce(full_auth_chains, MapSet.new(), &MapSet.intersection/2) @@ -201,6 +209,115 @@ defmodule MatrixServer.Event do MapSet.difference(auth_chain_union, auth_chain_intersection) end + def rev_top_pow_order( + %Event{timestamp: timestamp1, event_id: event_id1} = event1, + %Event{timestamp: timestamp2, event_id: event_id2} = event2 + ) do + {power1, power2} = {get_power_level(event1), get_power_level(event2)} + + if power1 == power2 do + if timestamp1 == timestamp2 do + event_id1 <= event_id2 + else + timestamp1 < timestamp2 + end + else + power1 < power2 + end + end + + def get_power_level(%Event{sender: sender, auth_events: auth_events}) do + pl_event = Enum.find(auth_events, &(&1.event_type == :power_levels)) + + case pl_event do + %Event{power_levels: power_levels} -> Map.get(power_levels, sender, 0) + _ -> 0 + end + end + + def mainline(event) do + event + |> mainline([]) + |> Enum.reverse() + end + + def mainline(%Event{auth_events: auth_events} = event, acc) do + case Enum.find(auth_events, &(&1.event_type == :power_levels)) do + nil -> [event | acc] + pl_event -> mainline(pl_event, [event | acc]) + end + end + + def mainline_order(p) do + mainline_map = + p + |> mainline() + |> Enum.with_index() + |> Enum.into(%{}) + + fn %Event{timestamp: timestamp1, event_id: event_id1} = event1, + %Event{timestamp: timestamp2, event_id: event_id2} = event2 -> + mainline_depth1 = get_mainline_depth(mainline_map, event1) + mainline_depth2 = get_mainline_depth(mainline_map, event2) + + if mainline_depth1 == mainline_depth2 do + if timestamp1 == timestamp2 do + event_id1 <= event_id2 + else + timestamp1 < timestamp2 + end + else + mainline_depth1 < mainline_depth2 + end + end + end + + defp get_mainline_depth(mainline_map, event) do + mainline = mainline(event) + + result = + Enum.find_value(mainline, fn mainline_event -> + if Map.has_key?(mainline_map, mainline_event) do + {:ok, mainline_map[mainline_event]} + else + nil + end + end) + + case result do + {:ok, index} -> -index + nil -> nil + end + end + + def resolve(state_sets) do + {unconflicted_state_map, conflicted_set} = calculate_conflict(state_sets) + full_conflicted_set = MapSet.union(conflicted_set, auth_difference(state_sets)) + + conflicted_control_events = + Enum.filter(full_conflicted_set, &is_control_event/1) |> MapSet.new() + + conflicted_control_events_with_auth = + MapSet.union( + conflicted_control_events, + MapSet.intersection( + full_conflicted_set, + full_auth_chain(MapSet.to_list(conflicted_control_events)) + ) + ) + + sorted_control_events = Enum.sort(conflicted_control_events_with_auth, &rev_top_pow_order/2) + partial_resolved_state = iterative_auth_checks(sorted_control_events, unconflicted_state_map) + + other_conflicted_events = MapSet.difference(full_conflicted_set, conflicted_control_events_with_auth) + + resolved_power_levels = partial_resolved_state[{:power_levels, ""}] + sorted_other_events = Enum.sort(other_conflicted_events, mainline_order(resolved_power_levels)) + nearly_final_state = iterative_auth_checks(sorted_other_events, partial_resolved_state) + + Map.merge(nearly_final_state, unconflicted_state_map) + end + def example1 do create = %Event{new() | event_id: "create", event_type: :create, sender: "@alice:example.com"} @@ -260,6 +377,8 @@ defmodule MatrixServer.Event do sender: "@alice:example.com" } + alice_joins = join("@alice:example.com") + pl1 = %Event{ set_power_levels("@alice:example.com", %{"@alice:example.com" => 100}) | event_id: "power levels 1" @@ -273,14 +392,69 @@ defmodule MatrixServer.Event do | event_id: "power levels 2" } - topic = %Event{set_topic("@bob:example.com", "This is a topic") | event_id: "topic"} + topic = %Event{set_topic("@alice:example.com", "This is a topic") | event_id: "topic"} - state_set1 = get_state_set_from_event_list([create, pl1]) - state_set2 = get_state_set_from_event_list([create, pl2, topic]) - state_set3 = get_state_set_from_event_list([create, pl2]) + state_set1 = get_state_set_from_event_list([create, alice_joins, pl1]) + state_set2 = get_state_set_from_event_list([create, alice_joins, pl2, topic]) + state_set3 = get_state_set_from_event_list([create, alice_joins, pl2]) [state_set1, state_set2, state_set3] end + def example3 do + pl1 = %Event{set_power_levels("alice", %{}) | event_id: "pl1", timestamp: 1} + + pl2 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl2", + auth_events: [pl1], + timestamp: 2 + } + + pl3 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl3", + auth_events: [pl1], + timestamp: 4 + } + + pl4 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl4", + auth_events: [pl2], + timestamp: 6 + } + + pl5 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl5", + auth_events: [pl4], + timestamp: 6 + } + + pl6 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl6", + auth_events: [pl4], + timestamp: 5 + } + + pl7 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl7", + auth_events: [pl2], + timestamp: 5 + } + + pl8 = %Event{ + set_power_levels("alice", %{}) + | event_id: "pl8", + auth_events: [pl7], + timestamp: 6 + } + + [pl1, pl2, pl3, pl4, pl5, pl6, pl7, pl8] + end + defp membership(user), do: membership(user, user) defp membership(actor, subject) do