diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index f73385c7f..a0abf7ff0 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -198,7 +198,7 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( // If we successfully performed a send_join above then the other // server now thinks we're a part of the room. Send the newly // returned state to the roomserver to update our local view. - if err = roomserverAPI.SendEventWithState( + if err = roomserverAPI.SendEventWithRewrite( ctx, r.rsAPI, respState, event.Headered(respMakeJoin.RoomVersion), diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index cd8b5e619..e53393119 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -40,10 +40,50 @@ func SendEvents( return SendInputRoomEvents(ctx, rsAPI, ires) } -// SendEventWithState writes an event with KindNew to the roomserver along +// SendEventWithState writes an event with KindNew to the roomserver +// with the state at the event as KindOutlier before it. Will not send any event that is +// marked as `true` in haveEventIDs +func SendEventWithState( + ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, + event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, +) error { + outliers, err := state.Events() + if err != nil { + return err + } + + var ires []InputRoomEvent + for _, outlier := range outliers { + if haveEventIDs[outlier.EventID()] { + continue + } + ires = append(ires, InputRoomEvent{ + Kind: KindOutlier, + Event: outlier.Headered(event.RoomVersion), + AuthEventIDs: outlier.AuthEventIDs(), + }) + } + + stateEventIDs := make([]string, len(state.StateEvents)) + for i := range state.StateEvents { + stateEventIDs[i] = state.StateEvents[i].EventID() + } + + ires = append(ires, InputRoomEvent{ + Kind: KindNew, + Event: event, + AuthEventIDs: event.AuthEventIDs(), + HasState: true, + StateEventIDs: stateEventIDs, + }) + + return SendInputRoomEvents(ctx, rsAPI, ires) +} + +// SendEventWithRewrite writes an event with KindNew to the roomserver along // with a number of rewrite and outlier events for state and auth events // respectively. -func SendEventWithState( +func SendEventWithRewrite( ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, ) error { @@ -75,6 +115,9 @@ func SendEventWithState( if haveEventIDs[authOrStateEvent.EventID()] { continue } + if event.StateKey() == nil { + continue + } // We will handle an event as if it's an outlier if one of the // following conditions is true: diff --git a/roomserver/internal/input/input_latest_events.go b/roomserver/internal/input/input_latest_events.go index 68c1d577a..5c2a1de6a 100644 --- a/roomserver/internal/input/input_latest_events.go +++ b/roomserver/internal/input/input_latest_events.go @@ -342,6 +342,11 @@ func (u *latestEventsUpdater) makeOutputNewRoomEvent() (*api.OutputEvent, error) return nil, fmt.Errorf("failed to load add_state_events from db: %w", err) } } + // State is rewritten if the input room event HasState and we actually produced a delta on state events. + // Without this check, /get_missing_events which produce events with associated (but not complete) state + // will incorrectly purge the room and set it to no state. TODO: This is likely flakey, as if /gme produced + // a state conflict res which just so happens to include 2+ events we might purge the room state downstream. + ore.RewritesState = len(ore.AddsStateEventIDs) > 1 return &api.OutputEvent{ Type: api.OutputTypeNewRoomEvent,