From b05e028f7d8befc045dcb62e87d09ad462bb277c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 13 Sep 2022 12:52:09 +0100 Subject: [PATCH 1/7] Tweak `LoadMembershipAtEvent` behaviour when state not known (#2716) Previously `LoadMembershipAtEvent` would fail if the state before one of the events was not known, i.e. because it was an outlier. This modifies it so that it gracefully handles not knowing the state and returns no memberships instead, so that history visibility doesn't freak out and kill `/sync` requests dead. --- roomserver/api/query.go | 3 ++- roomserver/internal/query/query.go | 9 ++++++++- roomserver/state/state.go | 8 ++++++-- syncapi/streams/stream_pdu.go | 5 ++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/roomserver/api/query.go b/roomserver/api/query.go index 32d63bb51..aa7dc4735 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -439,6 +439,7 @@ type QueryMembershipAtEventRequest struct { // QueryMembershipAtEventResponse is the response to QueryMembershipAtEventRequest. type QueryMembershipAtEventResponse struct { - // Memberships is a map from eventID to a list of events (if any). + // Memberships is a map from eventID to a list of events (if any). Events that + // do not have known state will return an empty array here. Memberships map[string][]*gomatrixserverlib.HeaderedEvent `json:"memberships"` } diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index d08c5c491..b41a92e94 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -208,6 +208,9 @@ func (r *Queryer) QueryMembershipForUser( return err } +// QueryMembershipAtEvent returns the known memberships at a given event. +// If the state before an event is not known, an empty list will be returned +// for that event instead. func (r *Queryer) QueryMembershipAtEvent( ctx context.Context, request *api.QueryMembershipAtEventRequest, @@ -237,7 +240,11 @@ func (r *Queryer) QueryMembershipAtEvent( } for _, eventID := range request.EventIDs { - stateEntry := stateEntries[eventID] + stateEntry, ok := stateEntries[eventID] + if !ok { + response.Memberships[eventID] = []*gomatrixserverlib.HeaderedEvent{} + continue + } memberships, err := helpers.GetMembershipsAtState(ctx, r.DB, stateEntry, false) if err != nil { return fmt.Errorf("unable to get memberships at state: %w", err) diff --git a/roomserver/state/state.go b/roomserver/state/state.go index a40a2e9ba..cb96d83ec 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -18,6 +18,7 @@ package state import ( "context" + "database/sql" "fmt" "sort" "sync" @@ -134,11 +135,14 @@ func (v *StateResolution) LoadMembershipAtEvent( for i := range eventIDs { eventID := eventIDs[i] snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID) - if err != nil { + if err != nil && err != sql.ErrNoRows { return nil, fmt.Errorf("LoadStateAtEvent.SnapshotNIDFromEventID failed for event %s : %w", eventID, err) } if snapshotNID == 0 { - return nil, fmt.Errorf("LoadStateAtEvent.SnapshotNIDFromEventID(%s) returned 0 NID, was this event stored?", eventID) + // If we don't know a state snapshot for this event then we can't calculate + // memberships at the time of the event, so skip over it. This means that + // it isn't guaranteed that the response map will contain every single event. + continue } snapshotNIDMap[snapshotNID] = append(snapshotNIDMap[snapshotNID], eventID) } diff --git a/syncapi/streams/stream_pdu.go b/syncapi/streams/stream_pdu.go index ffcf64df6..0ab6de886 100644 --- a/syncapi/streams/stream_pdu.go +++ b/syncapi/streams/stream_pdu.go @@ -388,7 +388,7 @@ func applyHistoryVisibilityFilter( if err != nil { // Not a fatal error, we can continue without the stateEvents, // they are only needed if there are state events in the timeline. - logrus.WithError(err).Warnf("failed to get current room state") + logrus.WithError(err).Warnf("Failed to get current room state for history visibility") } alwaysIncludeIDs := make(map[string]struct{}, len(stateEvents)) for _, ev := range stateEvents { @@ -397,7 +397,6 @@ func applyHistoryVisibilityFilter( startTime := time.Now() events, err := internal.ApplyHistoryVisibilityFilter(ctx, db, rsAPI, recentEvents, alwaysIncludeIDs, userID, "sync") if err != nil { - return nil, err } logrus.WithFields(logrus.Fields{ @@ -405,7 +404,7 @@ func applyHistoryVisibilityFilter( "room_id": roomID, "before": len(recentEvents), "after": len(events), - }).Debug("applied history visibility (sync)") + }).Trace("Applied history visibility (sync)") return events, nil } From 482914aef4a7d637a8c468d46904fde9f478b5d1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 13 Sep 2022 15:25:02 +0100 Subject: [PATCH 2/7] Use `AckNone` on the ephemeral room input consumer --- roomserver/internal/input/input.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roomserver/internal/input/input.go b/roomserver/internal/input/input.go index a8a3e0248..1cb980dc3 100644 --- a/roomserver/internal/input/input.go +++ b/roomserver/internal/input/input.go @@ -172,11 +172,10 @@ func (r *Inputer) Start() error { func(m *nats.Msg) { roomID := m.Header.Get(jetstream.RoomID) r.startWorkerForRoom(roomID) - _ = m.Ack() }, nats.HeadersOnly(), nats.DeliverAll(), - nats.AckAll(), + nats.AckNone(), nats.BindStream(r.InputRoomEventTopic), ) return err From 7f89fed1e45cc6ea65c420e32a017df634f4164f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Sep 2022 09:55:50 +0100 Subject: [PATCH 3/7] Revert 482914aef4a7d637a8c468d46904fde9f478b5d1 --- roomserver/internal/input/input.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/roomserver/internal/input/input.go b/roomserver/internal/input/input.go index 1cb980dc3..c47793f0a 100644 --- a/roomserver/internal/input/input.go +++ b/roomserver/internal/input/input.go @@ -172,10 +172,12 @@ func (r *Inputer) Start() error { func(m *nats.Msg) { roomID := m.Header.Get(jetstream.RoomID) r.startWorkerForRoom(roomID) + _ = m.Ack() }, nats.HeadersOnly(), nats.DeliverAll(), - nats.AckNone(), + nats.AckExplicit(), + nats.ReplayInstant(), nats.BindStream(r.InputRoomEventTopic), ) return err From d4cdab0a4400d26f555449c1d01f58795ecc5b06 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Sep 2022 11:18:50 +0100 Subject: [PATCH 4/7] Admin API to redownload room state from another server --- clientapi/routing/admin.go | 40 ++++++ clientapi/routing/routing.go | 6 + roomserver/api/api.go | 1 + roomserver/api/api_trace.go | 10 ++ roomserver/api/perform.go | 10 ++ roomserver/internal/perform/perform_admin.go | 141 +++++++++++++++++++ roomserver/inthttp/client.go | 36 +++-- roomserver/inthttp/server.go | 5 + 8 files changed, 237 insertions(+), 12 deletions(-) diff --git a/clientapi/routing/admin.go b/clientapi/routing/admin.go index 0c5f8c167..56373a112 100644 --- a/clientapi/routing/admin.go +++ b/clientapi/routing/admin.go @@ -138,3 +138,43 @@ func AdminResetPassword(req *http.Request, cfg *config.ClientAPI, device *userap }, } } + +func AdminDownloadState(req *http.Request, cfg *config.ClientAPI, device *userapi.Device, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + roomID, ok := vars["roomID"] + if !ok { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.MissingArgument("Expecting room ID."), + } + } + serverName, ok := vars["serverName"] + if !ok { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.MissingArgument("Expecting remote server name."), + } + } + res := &roomserverAPI.PerformAdminDownloadStateResponse{} + if err := rsAPI.PerformAdminDownloadState( + req.Context(), + &roomserverAPI.PerformAdminDownloadStateRequest{ + UserID: device.UserID, + RoomID: roomID, + ServerName: gomatrixserverlib.ServerName(serverName), + }, + res, + ); err != nil { + return jsonerror.InternalAPIError(req.Context(), err) + } + if err := res.Error; err != nil { + return err.JSONResponse() + } + return util.JSONResponse{ + Code: 200, + JSON: map[string]interface{}{}, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index d7a48d228..984540ebd 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -161,6 +161,12 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) + dendriteAdminRouter.Handle("/admin/downloadState/{serverName}/{roomID}", + httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + return AdminDownloadState(req, cfg, device, rsAPI) + }), + ).Methods(http.MethodPost, http.MethodOptions) + // server notifications if cfg.Matrix.ServerNotices.Enabled { logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice") diff --git a/roomserver/api/api.go b/roomserver/api/api.go index baf63aa31..5b43f6bd0 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -150,6 +150,7 @@ type ClientRoomserverAPI interface { PerformRoomUpgrade(ctx context.Context, req *PerformRoomUpgradeRequest, resp *PerformRoomUpgradeResponse) error PerformAdminEvacuateRoom(ctx context.Context, req *PerformAdminEvacuateRoomRequest, res *PerformAdminEvacuateRoomResponse) error PerformAdminEvacuateUser(ctx context.Context, req *PerformAdminEvacuateUserRequest, res *PerformAdminEvacuateUserResponse) error + PerformAdminDownloadState(ctx context.Context, req *PerformAdminDownloadStateRequest, res *PerformAdminDownloadStateResponse) error PerformPeek(ctx context.Context, req *PerformPeekRequest, res *PerformPeekResponse) error PerformUnpeek(ctx context.Context, req *PerformUnpeekRequest, res *PerformUnpeekResponse) error PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 8bef35379..342a3904c 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -131,6 +131,16 @@ func (t *RoomserverInternalAPITrace) PerformAdminEvacuateUser( return err } +func (t *RoomserverInternalAPITrace) PerformAdminDownloadState( + ctx context.Context, + req *PerformAdminDownloadStateRequest, + res *PerformAdminDownloadStateResponse, +) error { + err := t.Impl.PerformAdminDownloadState(ctx, req, res) + util.GetLogger(ctx).WithError(err).Infof("PerformAdminDownloadState req=%+v res=%+v", js(req), js(res)) + return err +} + func (t *RoomserverInternalAPITrace) PerformInboundPeek( ctx context.Context, req *PerformInboundPeekRequest, diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 20931f807..08238c5e5 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -234,3 +234,13 @@ type PerformAdminEvacuateUserResponse struct { Affected []string `json:"affected"` Error *PerformError } + +type PerformAdminDownloadStateRequest struct { + RoomID string `json:"room_id"` + UserID string `json:"user_id"` + ServerName gomatrixserverlib.ServerName `json:"server_name"` +} + +type PerformAdminDownloadStateResponse struct { + Error *PerformError `json:"error,omitempty"` +} diff --git a/roomserver/internal/perform/perform_admin.go b/roomserver/internal/perform/perform_admin.go index cb6b22d32..4b233104e 100644 --- a/roomserver/internal/perform/perform_admin.go +++ b/roomserver/internal/perform/perform_admin.go @@ -231,3 +231,144 @@ func (r *Admin) PerformAdminEvacuateUser( } return nil } + +func (r *Admin) PerformAdminDownloadState( + ctx context.Context, + req *api.PerformAdminDownloadStateRequest, + res *api.PerformAdminDownloadStateResponse, +) error { + roomInfo, err := r.DB.RoomInfo(ctx, req.RoomID) + if err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("r.DB.RoomInfo: %s", err), + } + return nil + } + + if roomInfo == nil || roomInfo.IsStub() { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("room %q not found", req.RoomID), + } + return nil + } + + fwdExtremities, _, depth, err := r.DB.LatestEventIDs(ctx, roomInfo.RoomNID) + if err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("r.DB.LatestEventIDs: %s", err), + } + return nil + } + + authEventMap := map[string]*gomatrixserverlib.Event{} + stateEventMap := map[string]*gomatrixserverlib.Event{} + + for _, fwdExtremity := range fwdExtremities { + var state gomatrixserverlib.RespState + state, err = r.Inputer.FSAPI.LookupState(ctx, req.ServerName, req.RoomID, fwdExtremity.EventID, roomInfo.RoomVersion) + if err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("r.Inputer.FSAPI.LookupState (%q): %s", fwdExtremity.EventID, err), + } + return nil + } + for _, authEvent := range state.AuthEvents.UntrustedEvents(roomInfo.RoomVersion) { + if err = authEvent.VerifyEventSignatures(ctx, r.Inputer.KeyRing); err != nil { + continue + } + authEventMap[authEvent.EventID()] = authEvent + } + for _, stateEvent := range state.StateEvents.UntrustedEvents(roomInfo.RoomVersion) { + if err = stateEvent.VerifyEventSignatures(ctx, r.Inputer.KeyRing); err != nil { + continue + } + stateEventMap[stateEvent.EventID()] = stateEvent + } + } + + authEvents := make([]*gomatrixserverlib.HeaderedEvent, 0, len(authEventMap)) + stateEvents := make([]*gomatrixserverlib.HeaderedEvent, 0, len(stateEventMap)) + stateIDs := make([]string, 0, len(stateEventMap)) + + for _, authEvent := range authEventMap { + authEvents = append(authEvents, authEvent.Headered(roomInfo.RoomVersion)) + } + for _, stateEvent := range stateEventMap { + stateEvents = append(stateEvents, stateEvent.Headered(roomInfo.RoomVersion)) + stateIDs = append(stateIDs, stateEvent.EventID()) + } + + builder := &gomatrixserverlib.EventBuilder{ + Type: "org.matrix.dendrite.state_download", + Sender: req.UserID, + RoomID: req.RoomID, + } + + eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) + if err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("gomatrixserverlib.StateNeededForEventBuilder: %s", err), + } + return nil + } + + queryRes := &api.QueryLatestEventsAndStateResponse{ + RoomExists: true, + RoomVersion: roomInfo.RoomVersion, + LatestEvents: fwdExtremities, + StateEvents: stateEvents, + Depth: depth, + } + + ev, err := eventutil.BuildEvent(ctx, builder, r.Cfg.Matrix, time.Now(), &eventsNeeded, queryRes) + if err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("eventutil.BuildEvent: %s", err), + } + return nil + } + + inputReq := &api.InputRoomEventsRequest{ + Asynchronous: false, + } + inputRes := &api.InputRoomEventsResponse{} + + for _, authEvent := range append(authEvents, stateEvents...) { + inputReq.InputRoomEvents = append(inputReq.InputRoomEvents, api.InputRoomEvent{ + Kind: api.KindOutlier, + Event: authEvent, + Origin: authEvent.Origin(), + }) + } + + inputReq.InputRoomEvents = append(inputReq.InputRoomEvents, api.InputRoomEvent{ + Kind: api.KindNew, + Event: ev, + Origin: r.Cfg.Matrix.ServerName, + HasState: true, + StateEventIDs: stateIDs, + }) + + if err := r.Inputer.InputRoomEvents(ctx, inputReq, inputRes); err != nil { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("r.Inputer.InputRoomEvents: %s", err), + } + return nil + } + + if inputRes.ErrMsg != "" { + res.Error = &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: inputRes.ErrMsg, + } + } + + return nil +} diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index a1dfc6aac..1bd1b3fb7 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -27,18 +27,19 @@ const ( RoomserverInputRoomEventsPath = "/roomserver/inputRoomEvents" // Perform operations - RoomserverPerformInvitePath = "/roomserver/performInvite" - RoomserverPerformPeekPath = "/roomserver/performPeek" - RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" - RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade" - RoomserverPerformJoinPath = "/roomserver/performJoin" - RoomserverPerformLeavePath = "/roomserver/performLeave" - RoomserverPerformBackfillPath = "/roomserver/performBackfill" - RoomserverPerformPublishPath = "/roomserver/performPublish" - RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" - RoomserverPerformForgetPath = "/roomserver/performForget" - RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom" - RoomserverPerformAdminEvacuateUserPath = "/roomserver/performAdminEvacuateUser" + RoomserverPerformInvitePath = "/roomserver/performInvite" + RoomserverPerformPeekPath = "/roomserver/performPeek" + RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" + RoomserverPerformRoomUpgradePath = "/roomserver/performRoomUpgrade" + RoomserverPerformJoinPath = "/roomserver/performJoin" + RoomserverPerformLeavePath = "/roomserver/performLeave" + RoomserverPerformBackfillPath = "/roomserver/performBackfill" + RoomserverPerformPublishPath = "/roomserver/performPublish" + RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" + RoomserverPerformForgetPath = "/roomserver/performForget" + RoomserverPerformAdminEvacuateRoomPath = "/roomserver/performAdminEvacuateRoom" + RoomserverPerformAdminEvacuateUserPath = "/roomserver/performAdminEvacuateUser" + RoomserverPerformAdminDownloadStatePath = "/roomserver/performAdminDownloadState" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" @@ -261,6 +262,17 @@ func (h *httpRoomserverInternalAPI) PerformAdminEvacuateRoom( ) } +func (h *httpRoomserverInternalAPI) PerformAdminDownloadState( + ctx context.Context, + request *api.PerformAdminDownloadStateRequest, + response *api.PerformAdminDownloadStateResponse, +) error { + return httputil.CallInternalRPCAPI( + "PerformAdminDownloadState", h.roomserverURL+RoomserverPerformAdminDownloadStatePath, + h.httpClient, ctx, request, response, + ) +} + func (h *httpRoomserverInternalAPI) PerformAdminEvacuateUser( ctx context.Context, request *api.PerformAdminEvacuateUserRequest, diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index 3b688174a..4d37e90b5 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -65,6 +65,11 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { httputil.MakeInternalRPCAPI("RoomserverPerformAdminEvacuateUser", r.PerformAdminEvacuateUser), ) + internalAPIMux.Handle( + RoomserverPerformAdminDownloadStatePath, + httputil.MakeInternalRPCAPI("RoomserverPerformAdminDownloadState", r.PerformAdminDownloadState), + ) + internalAPIMux.Handle( RoomserverQueryPublishedRoomsPath, httputil.MakeInternalRPCAPI("RoomserverQueryPublishedRooms", r.QueryPublishedRooms), From 19e0ce855d4b2dd3cd460cc6d3e959dbc0e37dd1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Sep 2022 11:20:52 +0100 Subject: [PATCH 5/7] Get --- clientapi/routing/routing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 984540ebd..5760116ad 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -165,7 +165,7 @@ func Setup( httputil.MakeAdminAPI("admin_download_state", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return AdminDownloadState(req, cfg, device, rsAPI) }), - ).Methods(http.MethodPost, http.MethodOptions) + ).Methods(http.MethodGet, http.MethodOptions) // server notifications if cfg.Matrix.ServerNotices.Enabled { From 07df8fdd42c711b6237e3c55962605eb5b66e37d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Sep 2022 11:22:00 +0100 Subject: [PATCH 6/7] Add an empty body --- roomserver/internal/perform/perform_admin.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/roomserver/internal/perform/perform_admin.go b/roomserver/internal/perform/perform_admin.go index 4b233104e..00832c3de 100644 --- a/roomserver/internal/perform/perform_admin.go +++ b/roomserver/internal/perform/perform_admin.go @@ -303,9 +303,10 @@ func (r *Admin) PerformAdminDownloadState( } builder := &gomatrixserverlib.EventBuilder{ - Type: "org.matrix.dendrite.state_download", - Sender: req.UserID, - RoomID: req.RoomID, + Type: "org.matrix.dendrite.state_download", + Sender: req.UserID, + RoomID: req.RoomID, + Content: gomatrixserverlib.RawJSON("{}"), } eventsNeeded, err := gomatrixserverlib.StateNeededForEventBuilder(builder) From e87f5763033e7630d5833a116e8d203b721ec0a4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 14 Sep 2022 11:23:21 +0100 Subject: [PATCH 7/7] Send the glue event --- roomserver/internal/perform/perform_admin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/roomserver/internal/perform/perform_admin.go b/roomserver/internal/perform/perform_admin.go index 00832c3de..aaf50db4c 100644 --- a/roomserver/internal/perform/perform_admin.go +++ b/roomserver/internal/perform/perform_admin.go @@ -354,6 +354,7 @@ func (r *Admin) PerformAdminDownloadState( Origin: r.Cfg.Matrix.ServerName, HasState: true, StateEventIDs: stateIDs, + SendAsServer: string(r.Cfg.Matrix.ServerName), }) if err := r.Inputer.InputRoomEvents(ctx, inputReq, inputRes); err != nil {