From b9342d9ee2f2c0f6e56a7a668c95c7d5f7ea3d77 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 30 Aug 2020 17:46:15 +0300 Subject: [PATCH 01/68] a very very WIP first cut of peeking via MSC2753. doesn't yet compile or work. needs to actually add the peeking block into the sync response. checking in now before it gets any bigger, and to gather any initial feedback on the vague shape of it. --- clientapi/routing/joinroom.go | 2 +- clientapi/routing/peekroom.go | 71 ++++++++++ clientapi/routing/routing.go | 11 ++ docs/peeking.md | 19 +++ roomserver/api/api.go | 6 + roomserver/api/output.go | 15 ++- roomserver/api/perform.go | 14 ++ roomserver/internal/perform_peek.go | 171 +++++++++++++++++++++++++ syncapi/consumers/roomserver.go | 22 ++++ syncapi/storage/interface.go | 2 + syncapi/storage/shared/syncserver.go | 17 +++ syncapi/storage/sqlite3/peeks_table.go | 151 ++++++++++++++++++++++ syncapi/storage/tables/interface.go | 7 + syncapi/sync/notifier.go | 97 ++++++++++++-- syncapi/types/types.go | 24 ++++ 15 files changed, 616 insertions(+), 13 deletions(-) create mode 100644 clientapi/routing/peekroom.go create mode 100644 docs/peeking.md create mode 100644 roomserver/internal/perform_peek.go create mode 100644 syncapi/storage/sqlite3/peeks_table.go diff --git a/clientapi/routing/joinroom.go b/clientapi/routing/joinroom.go index cb68fe1962..3c7421bb23 100644 --- a/clientapi/routing/joinroom.go +++ b/clientapi/routing/joinroom.go @@ -41,7 +41,7 @@ func JoinRoomByIDOrAlias( } joinRes := roomserverAPI.PerformJoinResponse{} - // If content was provided in the request then incude that + // If content was provided in the request then include that // in the request. It'll get used as a part of the membership // event content. _ = httputil.UnmarshalJSONRequest(req, &joinReq.Content) diff --git a/clientapi/routing/peekroom.go b/clientapi/routing/peekroom.go new file mode 100644 index 0000000000..98983c60a9 --- /dev/null +++ b/clientapi/routing/peekroom.go @@ -0,0 +1,71 @@ +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "net/http" + + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/clientapi/httputil" + roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/dendrite/userapi/storage/accounts" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +func PeekRoomByIDOrAlias( + req *http.Request, + device *api.Device, + rsAPI roomserverAPI.RoomserverInternalAPI, + accountDB accounts.Database, + roomIDOrAlias string, +) util.JSONResponse { + // if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to + // to call /peek and /state on the remote server. + // TODO: in future we could skip this if we know we're already participating in the room, + // but this is fiddly in case we stop participating in the room. + + // then we create a local peek. + peekReq := roomserverAPI.PerformPeekRequest{ + RoomIDOrAlias: roomIDOrAlias, + UserID: device.UserID, + DeviceID: device.ID, + } + peekRes := roomserverAPI.PerformPeekResponse{} + + // Ask the roomserver to perform the join. + rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes) + if peekRes.Error != nil { + return peekRes.Error.JSONResponse() + } + + // if this user is already joined to the room, we let them peek anyway + // (given they might be about to part the room, and it makes things less fiddly) + + + // Peeking stops if none of the devices who started peeking have been + // /syncing for a while, or if everyone who was peeking calls /leave + // (or /unpeek with a server_name param? or DELETE /peek?) + // on the peeked room. + + return util.JSONResponse{ + Code: http.StatusOK, + // TODO: Put the response struct somewhere internal. + JSON: struct { + RoomID string `json:"room_id"` + }{joinRes.RoomID}, + } +} diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index c259e5293f..69c76cf931 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -103,6 +103,17 @@ func Setup( ) }), ).Methods(http.MethodPost, http.MethodOptions) + r0mux.Handle("/peek/{roomIDOrAlias}", + httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + return PeekRoomByIDOrAlias( + req, device, rsAPI, accountDB, vars["roomIDOrAlias"], + ) + }), + ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/joined_rooms", httputil.MakeAuthAPI("joined_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { return GetJoinedRooms(req, device, stateAPI) diff --git a/docs/peeking.md b/docs/peeking.md new file mode 100644 index 0000000000..35f1d9d834 --- /dev/null +++ b/docs/peeking.md @@ -0,0 +1,19 @@ +## Peeking + +Peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753). + +Implementationwise, this means: + * Users call `/peek` and `/unpeek` on the clientapi from a given device. + * The clientapi delegates these via HTTP to the roomserver, which coordinates peeking in general for a given room + * The roomserver writes an NewPeek event into the kafka log headed to the syncserver + * The syncserver tracks the existence of the local peek in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peeking` section of the /sync response. + +Questions (given this is [my](https://github.com/ara4n) first time hacking on Dendrite): + * The whole clientapi -> roomserver -> syncapi flow to initiate a peek seems very indirect. Is there a reason not to just let syncapi itself host the implementation of `/peek`? + +In future, peeking over federation will be added as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444). + * The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated `/peek` + * The `federationsender` tracks the existence of the remote peek in question + * The `federationsender` regularly renews the remote peek as long as there are still peeking devices syncing for it. + * TBD: how do we tell if there are no devices currently syncing for a given peeked room? The syncserver needs to tell the roomserver + somehow who then needs to warn the federationsender. \ No newline at end of file diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 0fe30b8b59..3b2d4bd771 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -36,6 +36,12 @@ type RoomserverInternalAPI interface { res *PerformLeaveResponse, ) error + PerformPeek( + ctx context.Context, + req *PerformPeekRequest, + res *PerformPeekResponse, + ) + PerformPublish( ctx context.Context, req *PerformPublishRequest, diff --git a/roomserver/api/output.go b/roomserver/api/output.go index d6c09f9e8d..d74a37b360 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -46,6 +46,9 @@ const ( // - Redact the event and set the corresponding `unsigned` fields to indicate it as redacted. // - Replace the event in the database. OutputTypeRedactedEvent OutputType = "redacted_event" + + // OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek + OutputTypeNewPeek OutputType = "new_peek" ) // An OutputEvent is an entry in the roomserver output kafka log. @@ -59,8 +62,10 @@ type OutputEvent struct { NewInviteEvent *OutputNewInviteEvent `json:"new_invite_event,omitempty"` // The content of event with type OutputTypeRetireInviteEvent RetireInviteEvent *OutputRetireInviteEvent `json:"retire_invite_event,omitempty"` - // The content of event with type OutputTypeRedactedEvent + // The content of event with type OutputTypeRedactedEvent RedactedEvent *OutputRedactedEvent `json:"redacted_event,omitempty"` + // The content of event with type OutputTypeNewPeek + NewPeek *OutputNewPeek `json:"new_peek,omitempty"` } // An OutputNewRoomEvent is written when the roomserver receives a new event. @@ -195,3 +200,11 @@ type OutputRedactedEvent struct { // The value of `unsigned.redacted_because` - the redaction event itself RedactedBecause gomatrixserverlib.HeaderedEvent } + +// An OutputNewPeek is written whenever a user starts peeking into a room +// using a given device. +type OutputNewPeek struct { + RoomID string + UserID string + DeviceID string +} diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 24e958bb4e..0c2d96a7dd 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -108,6 +108,20 @@ type PerformInviteResponse struct { Error *PerformError } +type PerformPeekRequest struct { + RoomIDOrAlias string `json:"room_id_or_alias"` + UserID string `json:"user_id"` + DeviceID string `json:"device_id"` + ServerNames []gomatrixserverlib.ServerName `json:"server_names"` +} + +type PerformPeekResponse struct { + // The room ID, populated on success. + RoomID string `json:"room_id"` + // If non-nil, the join request failed. Contains more information why it failed. + Error *PerformError +} + // PerformBackfillRequest is a request to PerformBackfill. type PerformBackfillRequest struct { // The room to backfill diff --git a/roomserver/internal/perform_peek.go b/roomserver/internal/perform_peek.go new file mode 100644 index 0000000000..2be224e3bb --- /dev/null +++ b/roomserver/internal/perform_peek.go @@ -0,0 +1,171 @@ +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + fsAPI "github.com/matrix-org/dendrite/federationsender/api" + "github.com/matrix-org/dendrite/internal/eventutil" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/sirupsen/logrus" +) + +// PerformPeek handles peeking into matrix rooms, including over federation by talking to the federationsender. +func (r *RoomserverInternalAPI) PerformPeek( + ctx context.Context, + req *api.PerformPeekRequest, + res *api.PerformPeekResponse, +) { + roomID, err := r.performPeek(ctx, req) + if err != nil { + perr, ok := err.(*api.PerformError) + if ok { + res.Error = perr + } else { + res.Error = &api.PerformError{ + Msg: err.Error(), + } + } + } + res.RoomID = roomID +} + +func (r *RoomserverInternalAPI) performPeek( + ctx context.Context, + req *api.PerformPeekRequest, +) (string, error) { + // FIXME: there's way too much duplication with performJoin + _, domain, err := gomatrixserverlib.SplitID('@', req.UserID) + if err != nil { + return "", &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("Supplied user ID %q in incorrect format", req.UserID), + } + } + if domain != r.Cfg.Matrix.ServerName { + return "", &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("User %q does not belong to this homeserver", req.UserID), + } + } + if strings.HasPrefix(req.RoomIDOrAlias, "!") { + return r.performPeekRoomByID(ctx, req) + } + if strings.HasPrefix(req.RoomIDOrAlias, "#") { + return r.performPeekRoomByAlias(ctx, req) + } + return "", &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("Room ID or alias %q is invalid", req.RoomIDOrAlias), + } +} + +func (r *RoomserverInternalAPI) performPeekRoomByAlias( + ctx context.Context, + req *api.PerformJoinRequest, +) (string, error) { + // Get the domain part of the room alias. + _, domain, err := gomatrixserverlib.SplitID('#', req.RoomIDOrAlias) + if err != nil { + return "", fmt.Errorf("Alias %q is not in the correct format", req.RoomIDOrAlias) + } + req.ServerNames = append(req.ServerNames, domain) + + // Check if this alias matches our own server configuration. If it + // doesn't then we'll need to try a federated peek. + var roomID string + if domain != r.Cfg.Matrix.ServerName { + // The alias isn't owned by us, so we will need to try peeking using + // a remote server. + dirReq := fsAPI.PerformDirectoryLookupRequest{ + RoomAlias: req.RoomIDOrAlias, // the room alias to lookup + ServerName: domain, // the server to ask + } + dirRes := fsAPI.PerformDirectoryLookupResponse{} + err = r.fsAPI.PerformDirectoryLookup(ctx, &dirReq, &dirRes) + if err != nil { + logrus.WithError(err).Errorf("error looking up alias %q", req.RoomIDOrAlias) + return "", fmt.Errorf("Looking up alias %q over federation failed: %w", req.RoomIDOrAlias, err) + } + roomID = dirRes.RoomID + req.ServerNames = append(req.ServerNames, dirRes.ServerNames...) + } else { + // Otherwise, look up if we know this room alias locally. + roomID, err = r.DB.GetRoomIDForAlias(ctx, req.RoomIDOrAlias) + if err != nil { + return "", fmt.Errorf("Lookup room alias %q failed: %w", req.RoomIDOrAlias, err) + } + } + + // If the room ID is empty then we failed to look up the alias. + if roomID == "" { + return "", fmt.Errorf("Alias %q not found", req.RoomIDOrAlias) + } + + // If we do, then pluck out the room ID and continue the peek. + req.RoomIDOrAlias = roomID + return r.performPeekRoomByID(ctx, req) +} + +func (r *RoomserverInternalAPI) performPeekRoomByID( + ctx context.Context, + req *api.PerformPeekRequest, +) (roomID string, err error) { + roomID = req.RoomIDOrAlias + + // Get the domain part of the room ID. + _, domain, err := gomatrixserverlib.SplitID('!', roomID) + if err != nil { + return "", &api.PerformError{ + Code: api.PerformErrorBadRequest, + Msg: fmt.Sprintf("Room ID %q is invalid: %s", roomID, err), + } + } + + // If the server name in the room ID isn't ours then it's a + // possible candidate for finding the room via federation. Add + // it to the list of servers to try. + if domain != r.Cfg.Matrix.ServerName { + req.ServerNames = append(req.ServerNames, domain) + } + + // TODO: handle federated peeks + + err := r.WriteOutputEvents(roomID, []api.OutputEvent{ + { + Type: api.OutputTypeNewPeek, + NewPeek: &api.OutputNewPeek{ + RoomID: roomID, + UserID: req.UserID, + DeviceID: req.DeviceID, + }, + }, + }) + if err != nil { + return + } + + // By this point, if req.RoomIDOrAlias contained an alias, then + // it will have been overwritten with a room ID by performPeekRoomByAlias. + // We should now include this in the response so that the CS API can + // return the right room ID. + return +} diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index bf231d0998..da656a4cf4 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -99,6 +99,8 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { return s.onNewInviteEvent(context.TODO(), *output.NewInviteEvent) case api.OutputTypeRetireInviteEvent: return s.onRetireInviteEvent(context.TODO(), *output.RetireInviteEvent) + case api.OutputTypeNewPeek: + return s.onNewPeek(context.TODO(), *output.NewPeek) case api.OutputTypeRedactedEvent: return s.onRedactEvent(context.TODO(), *output.RedactedEvent) default: @@ -218,6 +220,26 @@ func (s *OutputRoomEventConsumer) onRetireInviteEvent( return nil } +func (s *OutputRoomEventConsumer) onNewPeek( + ctx context.Context, msg api.OutputNewPeek, +) error { + sp, err := s.db.AddPeek(ctx, msg.RoomID, msg.UserID, msg.DeviceID) + if err != nil { + // panic rather than continue with an inconsistent database + log.WithFields(log.Fields{ + log.ErrorKey: err, + }).Panicf("roomserver output log: write peek failure") + return nil + } + // tell the notifier about the new peek so it knows to wake up new devices + s.notifier.OnNewPeek(msg.RoomID, msg.UserID, msg.DeviceID) + + // we need to wake up the users who might need to now be peeking into this room, + // so we send in a dummy event to trigger a wakeup + s.notifier.OnNewEvent(nil, msg.RoomID, nil, types.NewStreamToken(sp, 0, nil)) + return nil +} + func (s *OutputRoomEventConsumer) updateStateEvent(event gomatrixserverlib.HeaderedEvent) (gomatrixserverlib.HeaderedEvent, error) { if event.StateKey() == nil { return event, nil diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index a5e13b6745..9521229529 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -30,6 +30,8 @@ type Database interface { internal.PartitionStorer // AllJoinedUsersInRooms returns a map of room ID to a list of all joined user IDs. AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) + // AllPeekingDevicesInRooms returns a map of room ID to a list of all peeking devices. + AllPeekingDevicesInRooms(ctx context.Context) (map[string][]PeekingDevice, error) // Events lookups a list of event by their event ID. // Returns a list of events matching the requested IDs found in the database. // If an event is not found in the database then it will be omitted from the list. diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index ad0c1d9960..6f7efefb7a 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -120,6 +120,10 @@ func (d *Database) AllJoinedUsersInRooms(ctx context.Context) (map[string][]stri return d.CurrentRoomState.SelectJoinedUsers(ctx) } +func (d *Database) AllPeekingDevicesInRooms(ctx context.Context) (map[string][]PeekingDevice, error) { + return d.Peeks.SelectPeekingDevices(ctx) +} + func (d *Database) GetStateEvent( ctx context.Context, roomID, evType, stateKey string, ) (*gomatrixserverlib.HeaderedEvent, error) { @@ -187,6 +191,19 @@ func (d *Database) RetireInviteEvent( return } +// AddPeek tracks the fact that a user has started peeking. +// If the peek was successfully stored this returns the stream ID it was stored at. +// Returns an error if there was a problem communicating with the database. +func (d *Database) AddPeek( + ctx context.Context, roomID, userID, deviceID string, +) (sp types.StreamPosition, err error) { + _ = d.Writer.Do(nil, nil, func(_ *sql.Tx) error { + sp, err = d.Peeks.InsertPeek(ctx, nil, inviteEvent) + return nil + }) + return +} + // GetAccountDataInRange returns all account data for a given user inserted or // updated between two given positions // Returns a map following the format data[roomID] = []dataTypes diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go new file mode 100644 index 0000000000..76df6dee3b --- /dev/null +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -0,0 +1,151 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + "encoding/json" + + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/syncapi/storage/tables" + "github.com/matrix-org/dendrite/syncapi/types" + "github.com/matrix-org/gomatrixserverlib" +) + +const peeksSchema = ` +CREATE TABLE IF NOT EXISTS syncapi_peeks ( + id INTEGER PRIMARY KEY, + room_id TEXT NOT NULL, + user_id TEXT NOT NULL, + device_id TEXT NOT NULL, + -- When the peek was created in UNIX epoch ms. + creation_ts INTEGER NOT NULL, +); + +CREATE INDEX IF NOT EXISTS syncapi_peeks_room_id_idx ON syncapi_peeks(room_id); +CREATE INDEX IF NOT EXISTS syncapi_peeks_user_id_device_id_idx ON syncapi_peeks(user_Id, device_id); +` + +const insertPeekSQL = "" + + "INSERT INTO syncapi_peeks" + + " (id, room_id, user_id, device_id, creation_ts" + + " VALUES ($1, $2, $3, $4, $5)" + +const deletePeekSQL = "" + + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2 and device_id = $3" + +const selectPeeksSQL == "" + + "SELECT room_id FROM syncapi_peeks WHERE user_id = $1 and device_id = $2" + +const selectPeekingDevicesSQL == "" + + "SELECT room_id, user_id, device_id FROM syncapi_peeks" + +type peekStatements struct { + db *sql.DB + insertPeekStmt *sql.Stmt + deletePeekStmt *sql.Stmt + selectPeeksStmt *sql.Stmt + selectPeekingDevicesStmt *sql.Stmt +} + +func NewSqlitePeeksTable(db *sql.DB) (tables.Peeks, error) { + _, err := db.Exec(filterSchema) + if err != nil { + return nil, err + } + s := &peekStatements{ + db: db, + } + if s.insertPeekStmt, err = db.Prepare(insertPeekSQL); err != nil { + return nil, err + } + if s.deletePeekStmt, err = db.Prepare(deletePeekSQL); err != nil { + return nil, err + } + if s.selectPeeksStmt, err = db.Prepare(selectPeeksSQL); err != nil { + return nil, err + } + if s.selectPeekingDevicesStmt, err = db.Prepare(selectPeekingDevicesSQL); err != nil { + return nil, err + } + return s, nil +} + +func (s *peekStatements) InsertPeek( + ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string, +) (streamPos types.StreamPosition, err error) { + streamPos, err = s.streamIDStatements.nextStreamID(ctx, txn) + if err != nil { + return + } + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err = sqlutil.TxStmt(txn, s.insertPeekStmt).ExecContext(ctx, roomID, userID, deviceID, nowMilli) + return +} + +func (s *peekStatements) DeletePeek( + ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string, +) (streamPos types.StreamPosition, err error) { + streamPos, err = s.streamIDStatements.nextStreamID(ctx, txn) + if err != nil { + return + } + _, err = sqlutil.TxStmt(txn, s.deletePeekStmt).ExecContext(ctx, roomID, userID, deviceID) + return +} + +func (s *peekStatements) SelectPeeks( + ctx context.Context, txn *sql.Tx, userID, deviceID string, +) (roomIDs []string, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectPeeksStmt).QueryContext(ctx, userID, deviceID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectPeeks: rows.close() failed") + + for rows.Next() { + var roomID string + if err = rows.Scan(&roomId); err != nil { + return + } + roomIDs = append(roomIDs, roomID) + } + + return roomIDs, rows.Err() +} + +func (s *peekStatements) SelectPeekingDevices( + ctx context.Context, +) (peekingDevices map[string][]PeekingDevice, err error) { + rows, err := s.selectPeekingDevicesStmt.QueryContext(ctx) + if err != nil { + return nil, err + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectPeekingDevices: rows.close() failed") + + result := make(map[string][]PeekingDevice) + for rows.Next() { + var roomID, userID, deviceID string + if err := rows.Scan(&roomID, &userID, &deviceID); err != nil { + return nil, err + } + devices := result[roomID] + devices = append(devices, PeekingDevice{userID, deviceID}) + result[roomID] = devices + } + return result, nil +} diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 2ff229cbc1..9566b8b381 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -39,6 +39,13 @@ type Invites interface { SelectMaxInviteID(ctx context.Context, txn *sql.Tx) (id int64, err error) } +type Peeks interface { + InsertPeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) + DeletePeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) + SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []string, err error) + SelectPeekingDevices((ctxt context.Context) (peekingDevices map[string][]PeekingDevice, err error) +} + type Events interface { SelectStateInRange(ctx context.Context, txn *sql.Tx, r types.Range, stateFilter *gomatrixserverlib.StateFilter) (map[string]map[string]bool, map[string]types.StreamEvent, error) SelectMaxEventID(ctx context.Context, txn *sql.Tx) (id int64, err error) diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index df23a2f4a6..e5de78f6b3 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -33,6 +33,8 @@ import ( type Notifier struct { // A map of RoomID => Set : Must only be accessed by the OnNewEvent goroutine roomIDToJoinedUsers map[string]userIDSet + // A map of RoomID => Set : Must only be accessed by the OnNewEvent goroutine + roomIDToPeekingDevices map[string]PeekingDeviceSet // Protects currPos and userStreams. streamLock *sync.Mutex // The latest sync position @@ -48,11 +50,11 @@ type Notifier struct { // the joined users within each of them by calling Notifier.Load(*storage.SyncServerDatabase). func NewNotifier(pos types.StreamingToken) *Notifier { return &Notifier{ - currPos: pos, - roomIDToJoinedUsers: make(map[string]userIDSet), - userDeviceStreams: make(map[string]map[string]*UserDeviceStream), - streamLock: &sync.Mutex{}, - lastCleanUpTime: time.Now(), + currPos: pos, + roomIDToJoinedUsers: make(map[string]userIDSet), + roomIDToPeekingDevices: make(map[string]PeekingDeviceSet), + userDeviceStreams: make(map[string]map[string]*UserDeviceStream), + streamLock: &sync.Mutex{}, } } @@ -82,6 +84,8 @@ func (n *Notifier) OnNewEvent( if ev != nil { // Map this event's room_id to a list of joined users, and wake them up. usersToNotify := n.joinedUsers(ev.RoomID()) + // Map this event's room_id to a list of peeking devices, and wake them up. + peekingDevicesToNotify := n.PeekingDevices(ev.RoomID()) // If this is an invite, also add in the invitee to this list. if ev.Type() == "m.room.member" && ev.StateKey() != nil { targetUserID := *ev.StateKey() @@ -108,11 +112,11 @@ func (n *Notifier) OnNewEvent( } } - n.wakeupUsers(usersToNotify, latestPos) + n.wakeupUsers(usersToNotify, peekingDevicesToNotify, latestPos) } else if roomID != "" { - n.wakeupUsers(n.joinedUsers(roomID), latestPos) + n.wakeupUsers(n.joinedUsers(roomID), n.PeekingDevices(roomID), latestPos) } else if len(userIDs) > 0 { - n.wakeupUsers(userIDs, latestPos) + n.wakeupUsers(userIDs, nil, latestPos) } else { log.WithFields(log.Fields{ "posUpdate": posUpdate.String, @@ -120,6 +124,18 @@ func (n *Notifier) OnNewEvent( } } +func (n *Notifier) OnNewPeek( + roomID, userID, deviceID string, +) { + n.streamLock.Lock() + defer n.streamLock.Unlock() + + n.addPeekingDevice(roomID, userID, deviceID) + + // we don't wake up devices here given the roomserver consumer will do this shortly afterwards + // by calling OnNewEvent. +} + func (n *Notifier) OnNewSendToDevice( userID string, deviceIDs []string, posUpdate types.StreamingToken, @@ -139,7 +155,7 @@ func (n *Notifier) OnNewKeyChange( defer n.streamLock.Unlock() latestPos := n.currPos.WithUpdates(posUpdate) n.currPos = latestPos - n.wakeupUsers([]string{wakeUserID}, latestPos) + n.wakeupUsers([]string{wakeUserID}, nil, latestPos) } // GetListener returns a UserStreamListener that can be used to wait for @@ -169,6 +185,13 @@ func (n *Notifier) Load(ctx context.Context, db storage.Database) error { return err } n.setUsersJoinedToRooms(roomToUsers) + + roomToPeekingDevices, err := db.AllPeekingDevicesInRooms(ctx) + if err != nil { + return err + } + n.setPeekingDevices(roomToPeekingDevices) + return nil } @@ -195,9 +218,24 @@ func (n *Notifier) setUsersJoinedToRooms(roomIDToUserIDs map[string][]string) { } } +// setPeekingDevices marks the given devices as peeking in the given rooms, such that new events from +// these rooms will wake the given devices' /sync requests. This should be called prior to ANY calls to +// OnNewEvent (eg on startup) to prevent racing. +func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]PeekingDevices) { + // This is just the bulk form of addPeekingDevice + for roomID, peekingDevices := range roomIDToPeekingDevices { + if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { + n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + } + for _, peekingDevice := range peekingDevices { + n.roomIDToPeekingDevices[roomID].add(peekingDevice) + } + } +} + // wakeupUsers will wake up the sync strems for all of the devices for all of the -// specified user IDs. -func (n *Notifier) wakeupUsers(userIDs []string, newPos types.StreamingToken) { +// specified user IDs, and also the specified peekingDevices +func (n *Notifier) wakeupUsers(userIDs []string, peekingDevices []PeekingDevice, newPos types.StreamingToken) { for _, userID := range userIDs { for _, stream := range n.fetchUserStreams(userID) { if stream == nil { @@ -206,6 +244,15 @@ func (n *Notifier) wakeupUsers(userIDs []string, newPos types.StreamingToken) { stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream } } + + if peekingDevices != nil { + for _, peekingDevice := range peekingDevices { + // TODO: don't bother waking up for devices whose users we already woke up + if stream := n.fetchUserDeviceStream(peekingDevice.UserID, peekingDevice.DeviceID, false); stream != nil { + stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream + } + } + } } // wakeupUserDevice will wake up the sync stream for a specific user device. Other @@ -284,6 +331,34 @@ func (n *Notifier) joinedUsers(roomID string) (userIDs []string) { return n.roomIDToJoinedUsers[roomID].values() } + +// Not thread-safe: must be called on the OnNewEvent goroutine only +func (n *Notifier) addPeekingDevice(roomID, userID, deviceID string) { + if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { + n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + } + n.roomIDToPeekingDevices[roomID].add(PeekingDevice{deviceID, userID}) +} + +// Not thread-safe: must be called on the OnNewEvent goroutine only +func (n *Notifier) removePeekingDevice(roomID, userID, deviceID string) { + if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { + n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + } + // XXX: is this going to work as a key? + n.roomIDToPeekingDevices[roomID].remove(PeekingDevice{deviceID, userID}) +} + +// Not thread-safe: must be called on the OnNewEvent goroutine only +func (n *Notifier) PeekingDevices(roomID string) (peekingDevices []PeekingDevices) { + if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { + return + } + return n.roomIDToPeekingDevices[roomID].values() +} + + + // removeEmptyUserStreams iterates through the user stream map and removes any // that have been empty for a certain amount of time. This is a crude way of // ensuring that the userStreams map doesn't grow forver. diff --git a/syncapi/types/types.go b/syncapi/types/types.go index f3324800ff..0d3be2eff4 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -507,3 +507,27 @@ type SendToDeviceEvent struct { DeviceID string SentByToken *StreamingToken } + +// For tracking peeking devices + +type PeekingDevice struct { + ID string + UserID string +} + +type PeekingDeviceSet map[PeekingDevice]bool + +func (s PeekingDeviceSet) add(d PeekingDevice) { + s[d] = true +} + +func (s PeekingDeviceSet) remove(d PeekingDevice) { + delete(s, d) +} + +func (s PeekingDeviceSet) values() (vals []PeekingDevice) { + for d := range s { + vals = append(vals, d) + } + return +} From d7bdf71bef84ada254b4bada9be12b325236395f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 30 Aug 2020 17:56:44 +0300 Subject: [PATCH 02/68] make PeekingDeviceSet private --- syncapi/sync/notifier.go | 29 ++++++++++++++++++++++++----- syncapi/types/types.go | 18 ------------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index e5de78f6b3..f09c2b4d85 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -34,7 +34,7 @@ type Notifier struct { // A map of RoomID => Set : Must only be accessed by the OnNewEvent goroutine roomIDToJoinedUsers map[string]userIDSet // A map of RoomID => Set : Must only be accessed by the OnNewEvent goroutine - roomIDToPeekingDevices map[string]PeekingDeviceSet + roomIDToPeekingDevices map[string]peekingDeviceSet // Protects currPos and userStreams. streamLock *sync.Mutex // The latest sync position @@ -52,7 +52,7 @@ func NewNotifier(pos types.StreamingToken) *Notifier { return &Notifier{ currPos: pos, roomIDToJoinedUsers: make(map[string]userIDSet), - roomIDToPeekingDevices: make(map[string]PeekingDeviceSet), + roomIDToPeekingDevices: make(map[string]peekingDeviceSet), userDeviceStreams: make(map[string]map[string]*UserDeviceStream), streamLock: &sync.Mutex{}, } @@ -225,7 +225,7 @@ func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]Peeking // This is just the bulk form of addPeekingDevice for roomID, peekingDevices := range roomIDToPeekingDevices { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { - n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } for _, peekingDevice := range peekingDevices { n.roomIDToPeekingDevices[roomID].add(peekingDevice) @@ -335,7 +335,7 @@ func (n *Notifier) joinedUsers(roomID string) (userIDs []string) { // Not thread-safe: must be called on the OnNewEvent goroutine only func (n *Notifier) addPeekingDevice(roomID, userID, deviceID string) { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { - n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } n.roomIDToPeekingDevices[roomID].add(PeekingDevice{deviceID, userID}) } @@ -343,7 +343,7 @@ func (n *Notifier) addPeekingDevice(roomID, userID, deviceID string) { // Not thread-safe: must be called on the OnNewEvent goroutine only func (n *Notifier) removePeekingDevice(roomID, userID, deviceID string) { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { - n.roomIDToPeekingDevices[roomID] = make(PeekingDeviceSet) + n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } // XXX: is this going to work as a key? n.roomIDToPeekingDevices[roomID].remove(PeekingDevice{deviceID, userID}) @@ -404,3 +404,22 @@ func (s userIDSet) values() (vals []string) { } return } + +// A set of PeekingDevices, similar to userIDSet + +type peekingDeviceSet map[PeekingDevice]bool + +func (s peekingDeviceSet) add(d PeekingDevice) { + s[d] = true +} + +func (s peekingDeviceSet) remove(d PeekingDevice) { + delete(s, d) +} + +func (s peekingDeviceSet) values() (vals []PeekingDevice) { + for d := range s { + vals = append(vals, d) + } + return +} diff --git a/syncapi/types/types.go b/syncapi/types/types.go index 0d3be2eff4..fd28892f07 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -508,26 +508,8 @@ type SendToDeviceEvent struct { SentByToken *StreamingToken } -// For tracking peeking devices - type PeekingDevice struct { ID string UserID string } -type PeekingDeviceSet map[PeekingDevice]bool - -func (s PeekingDeviceSet) add(d PeekingDevice) { - s[d] = true -} - -func (s PeekingDeviceSet) remove(d PeekingDevice) { - delete(s, d) -} - -func (s PeekingDeviceSet) values() (vals []PeekingDevice) { - for d := range s { - vals = append(vals, d) - } - return -} From 9b79f9a88357bfebb57243205b7c15dd4b4303e2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 12:27:39 +0300 Subject: [PATCH 03/68] add server_name param --- clientapi/routing/peekroom.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/clientapi/routing/peekroom.go b/clientapi/routing/peekroom.go index 98983c60a9..17bbaef409 100644 --- a/clientapi/routing/peekroom.go +++ b/clientapi/routing/peekroom.go @@ -33,6 +33,17 @@ func PeekRoomByIDOrAlias( accountDB accounts.Database, roomIDOrAlias string, ) util.JSONResponse { + // Check to see if any ?server_name= query parameters were + // given in the request. + if serverNames, ok := req.URL.Query()["server_name"]; ok { + for _, serverName := range serverNames { + peekReq.ServerNames = append( + peekReq.ServerNames, + gomatrixserverlib.ServerName(serverName), + ) + } + } + // if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to // to call /peek and /state on the remote server. // TODO: in future we could skip this if we know we're already participating in the room, @@ -46,7 +57,7 @@ func PeekRoomByIDOrAlias( } peekRes := roomserverAPI.PerformPeekResponse{} - // Ask the roomserver to perform the join. + // Ask the roomserver to perform the peek. rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes) if peekRes.Error != nil { return peekRes.Error.JSONResponse() @@ -66,6 +77,6 @@ func PeekRoomByIDOrAlias( // TODO: Put the response struct somewhere internal. JSON: struct { RoomID string `json:"room_id"` - }{joinRes.RoomID}, + }{peekRes.RoomID}, } } From d343b8fb2c1aca4aeebd7051718244ba237e5f61 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 15:28:24 +0300 Subject: [PATCH 04/68] blind stab at adding a `peek` section to /sync --- docs/peeking.md | 2 +- syncapi/storage/shared/syncserver.go | 75 +++++++++++++++++++++++++- syncapi/storage/sqlite3/peeks_table.go | 26 ++++++--- syncapi/types/types.go | 8 ++- 4 files changed, 101 insertions(+), 10 deletions(-) diff --git a/docs/peeking.md b/docs/peeking.md index 35f1d9d834..78bd6f797d 100644 --- a/docs/peeking.md +++ b/docs/peeking.md @@ -6,7 +6,7 @@ Implementationwise, this means: * Users call `/peek` and `/unpeek` on the clientapi from a given device. * The clientapi delegates these via HTTP to the roomserver, which coordinates peeking in general for a given room * The roomserver writes an NewPeek event into the kafka log headed to the syncserver - * The syncserver tracks the existence of the local peek in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peeking` section of the /sync response. + * The syncserver tracks the existence of the local peek in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peek` section of the /sync response. Questions (given this is [my](https://github.com/ara4n) first time hacking on Dendrite): * The whole clientapi -> roomserver -> syncapi flow to initiate a peek seems very indirect. Is there a reason not to just let syncapi itself host the implementation of `/peek`? diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 257abf0800..cf8dd604c5 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -606,6 +606,8 @@ func (d *Database) IncrementalSync( } } + // TODO: handle EDUs in peeked rooms + err = d.addEDUDeltaToResponse( fromPos, toPos, joinedRoomIDs, res, ) @@ -742,6 +744,8 @@ func (d *Database) CompleteSync( return nil, fmt.Errorf("d.getResponseWithPDUsForCompleteSync: %w", err) } + // TODO: handle EDUs in peeked rooms + // Use a zero value SyncPosition for fromPos so all EDU states are added. err = d.addEDUDeltaToResponse( types.NewStreamToken(0, 0, nil), toPos, joinedRoomIDs, res, @@ -847,6 +851,14 @@ func (d *Database) addRoomDeltaToResponse( jr.Timeline.Limited = limited jr.State.Events = gomatrixserverlib.HeaderedToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync) res.Rooms.Join[delta.roomID] = *jr + case gomatrixserverlib.Peek: + jr := types.NewJoinResponse() + + jr.Timeline.PrevBatch = prevBatch.String() + jr.Timeline.Events = gomatrixserverlib.HeaderedToClientEvents(recentEvents, gomatrixserverlib.FormatSync) + jr.Timeline.Limited = limited + jr.State.Events = gomatrixserverlib.HeaderedToClientEvents(delta.stateEvents, gomatrixserverlib.FormatSync) + res.Rooms.Peek[delta.roomID] = *jr case gomatrixserverlib.Leave: fallthrough // transitions to leave are the same as ban case gomatrixserverlib.Ban: @@ -968,7 +980,7 @@ func (d *Database) getStateDeltas( // - Get all CURRENTLY joined rooms, and add them to 'joined' block. var deltas []stateDelta - // get all the state events ever between these two positions + // get all the state events ever (i.e. for all available rooms) between these two positions stateNeeded, eventMap, err := d.OutputEvents.SelectStateInRange(ctx, txn, r, stateFilter) if err != nil { return nil, nil, err @@ -978,6 +990,40 @@ func (d *Database) getStateDeltas( return nil, nil, err } + // find out which rooms this user is peeking, if any. + // We do this before joins so joins overwrite peeks + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.DeviceID) + if err != nil { + return nil, nil, err + } + + // add peek blocks + for _, peek := range peeks { + if peek.New { + // send full room state down instead of a delta + var s []types.StreamEvent + s, err = d.currentStateStreamEventsForRoom(ctx, txn, peek.RoomID, stateFilter) + if err != nil { + return nil, nil, err + } + state[roomID] = s + } + + deltas = append(deltas, stateDelta{ + membership: gomatrixserverlib.Peek, + stateEvents: d.StreamEventsToEvents(device, state[peek.RoomID]), + roomID: peek.RoomID, + }) + } + + if len(peeks) > 0 { + err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.DeviceID) + if err != nil { + return nil, nil, err + } + } + + // handle newly joined rooms and non-joined rooms for roomID, stateStreamEvents := range state { for _, ev := range stateStreamEvents { // TODO: Currently this will incorrectly add rooms which were ALREADY joined but they sent another no-op join event. @@ -1038,8 +1084,13 @@ func (d *Database) getStateDeltasForFullStateSync( return nil, nil, err } + peeks, err = d.Peeks.SelectPeeks(ctx, txn, userID, device,ID) + if err != nil { + return nil, nil, err + } + // Use a reasonable initial capacity - deltas := make([]stateDelta, 0, len(joinedRoomIDs)) + deltas := make([]stateDelta, 0, len(joinedRoomIDs) + len(peeks)) // Add full states for all joined rooms for _, joinedRoomID := range joinedRoomIDs { @@ -1054,6 +1105,26 @@ func (d *Database) getStateDeltasForFullStateSync( }) } + // Add full states for all peeking rooms + for _, peek := range peeks { + s, stateErr := d.currentStateStreamEventsForRoom(ctx, txn, peek.RoomID, stateFilter) + if stateErr != nil { + return nil, nil, stateErr + } + deltas = append(deltas, stateDelta{ + membership: gomatrixserverlib.Peek, + stateEvents: d.StreamEventsToEvents(device, s), + roomID: peek.RoomID, + }) + } + + if len(peeks) > 0 { + err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.DeviceID) + if err != nil { + return nil, nil, err + } + } + // Get all the state events ever between these two positions stateNeeded, eventMap, err := d.OutputEvents.SelectStateInRange(ctx, txn, r, stateFilter) if err != nil { diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 76df6dee3b..7ac329f662 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS syncapi_peeks ( room_id TEXT NOT NULL, user_id TEXT NOT NULL, device_id TEXT NOT NULL, + new BOOL NOT NULL DEFAULT true, -- When the peek was created in UNIX epoch ms. creation_ts INTEGER NOT NULL, ); @@ -49,11 +50,14 @@ const deletePeekSQL = "" + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2 and device_id = $3" const selectPeeksSQL == "" + - "SELECT room_id FROM syncapi_peeks WHERE user_id = $1 and device_id = $2" + "SELECT room_id, new FROM syncapi_peeks WHERE user_id = $1 and device_id = $2" const selectPeekingDevicesSQL == "" + "SELECT room_id, user_id, device_id FROM syncapi_peeks" +const markPeeksAsOldSQL == "" + + "UPDATE syncapi_peeks SET new=false WHERE user_id = $1 and device_id = $2" + type peekStatements struct { db *sql.DB insertPeekStmt *sql.Stmt @@ -82,6 +86,9 @@ func NewSqlitePeeksTable(db *sql.DB) (tables.Peeks, error) { if s.selectPeekingDevicesStmt, err = db.Prepare(selectPeekingDevicesSQL); err != nil { return nil, err } + if s.markPeeksAsOldStmt, err = db.Prepare(markPeeksAsOldSQL); err != nil { + return nil, err + } return s, nil } @@ -110,7 +117,7 @@ func (s *peekStatements) DeletePeek( func (s *peekStatements) SelectPeeks( ctx context.Context, txn *sql.Tx, userID, deviceID string, -) (roomIDs []string, err error) { +) (peeks []Peek, err error) { rows, err := sqlutil.TxStmt(txn, s.selectPeeksStmt).QueryContext(ctx, userID, deviceID) if err != nil { return @@ -118,14 +125,21 @@ func (s *peekStatements) SelectPeeks( defer internal.CloseAndLogIfError(ctx, rows, "SelectPeeks: rows.close() failed") for rows.Next() { - var roomID string - if err = rows.Scan(&roomId); err != nil { + peek = Peek{} + if err = rows.Scan(&peek.roomId, &peek.new); err != nil { return } - roomIDs = append(roomIDs, roomID) + peeks = append(peeks, peek) } - return roomIDs, rows.Err() + return peeks, rows.Err() +} + +func (s *peekStatements) MarkPeeksAsOld ( + ctx context.Context, txn *sql.Tx, userID, deviceID string, +) (err error) { + _, err := sqlutil.TxStmt(txn, s.markPeeksAsOldStmt).ExecContext(ctx, userID, deviceID) + return } func (s *peekStatements) SelectPeekingDevices( diff --git a/syncapi/types/types.go b/syncapi/types/types.go index fd28892f07..80a0109047 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -388,6 +388,7 @@ type Response struct { } `json:"presence,omitempty"` Rooms struct { Join map[string]JoinResponse `json:"join"` + Peek map[string]JoinResponse `json:"peek"` Invite map[string]InviteResponse `json:"invite"` Leave map[string]LeaveResponse `json:"leave"` } `json:"rooms"` @@ -407,6 +408,7 @@ func NewResponse() *Response { // Pre-initialise the maps. Synapse will return {} even if there are no rooms under a specific section, // so let's do the same thing. Bonus: this means we can't get dreaded 'assignment to entry in nil map' errors. res.Rooms.Join = make(map[string]JoinResponse) + res.Rooms.Peek = make(map[string]JoinResponse) res.Rooms.Invite = make(map[string]InviteResponse) res.Rooms.Leave = make(map[string]LeaveResponse) @@ -433,7 +435,7 @@ func (r *Response) IsEmpty() bool { len(r.ToDevice.Events) == 0 } -// JoinResponse represents a /sync response for a room which is under the 'join' key. +// JoinResponse represents a /sync response for a room which is under the 'join' or 'peek' key. type JoinResponse struct { State struct { Events []gomatrixserverlib.ClientEvent `json:"events"` @@ -513,3 +515,7 @@ type PeekingDevice struct { UserID string } +type Peek struct { + RoomID string + New boolean +} \ No newline at end of file From c4e5f60d71893a89981393e281eb3dc9f799c9c3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 16:12:09 +0300 Subject: [PATCH 05/68] make it build --- clientapi/routing/peekroom.go | 24 ++++++++++------------ roomserver/api/api_trace.go | 9 +++++++++ roomserver/internal/perform_peek.go | 9 +++------ roomserver/inthttp/client.go | 18 +++++++++++++++++ syncapi/storage/interface.go | 5 ++++- syncapi/storage/shared/syncserver.go | 15 +++++++------- syncapi/storage/sqlite3/peeks_table.go | 28 ++++++++++++++------------ syncapi/storage/sqlite3/syncserver.go | 5 +++++ syncapi/storage/tables/interface.go | 5 +++-- syncapi/sync/notifier.go | 20 +++++++++--------- syncapi/types/types.go | 2 +- 11 files changed, 87 insertions(+), 53 deletions(-) diff --git a/clientapi/routing/peekroom.go b/clientapi/routing/peekroom.go index 17bbaef409..d86ccd6b5c 100644 --- a/clientapi/routing/peekroom.go +++ b/clientapi/routing/peekroom.go @@ -17,8 +17,6 @@ package routing import ( "net/http" - "github.com/matrix-org/dendrite/clientapi/auth/authtypes" - "github.com/matrix-org/dendrite/clientapi/httputil" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/dendrite/userapi/storage/accounts" @@ -33,17 +31,6 @@ func PeekRoomByIDOrAlias( accountDB accounts.Database, roomIDOrAlias string, ) util.JSONResponse { - // Check to see if any ?server_name= query parameters were - // given in the request. - if serverNames, ok := req.URL.Query()["server_name"]; ok { - for _, serverName := range serverNames { - peekReq.ServerNames = append( - peekReq.ServerNames, - gomatrixserverlib.ServerName(serverName), - ) - } - } - // if this is a remote roomIDOrAlias, we have to ask the roomserver (or federation sender?) to // to call /peek and /state on the remote server. // TODO: in future we could skip this if we know we're already participating in the room, @@ -57,6 +44,17 @@ func PeekRoomByIDOrAlias( } peekRes := roomserverAPI.PerformPeekResponse{} + // Check to see if any ?server_name= query parameters were + // given in the request. + if serverNames, ok := req.URL.Query()["server_name"]; ok { + for _, serverName := range serverNames { + peekReq.ServerNames = append( + peekReq.ServerNames, + gomatrixserverlib.ServerName(serverName), + ) + } + } + // Ask the roomserver to perform the peek. rsAPI.PerformPeek(req.Context(), &peekReq, &peekRes) if peekRes.Error != nil { diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 9b53aa88cf..0e1b645e4f 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -38,6 +38,15 @@ func (t *RoomserverInternalAPITrace) PerformInvite( return t.Impl.PerformInvite(ctx, req, res) } +func (t *RoomserverInternalAPITrace) PerformPeek( + ctx context.Context, + req *PerformPeekRequest, + res *PerformPeekResponse, +) { + t.Impl.PerformPeek(ctx, req, res) + util.GetLogger(ctx).Infof("PerformPeek req=%+v res=%+v", js(req), js(res)) +} + func (t *RoomserverInternalAPITrace) PerformJoin( ctx context.Context, req *PerformJoinRequest, diff --git a/roomserver/internal/perform_peek.go b/roomserver/internal/perform_peek.go index 2be224e3bb..4f080737ef 100644 --- a/roomserver/internal/perform_peek.go +++ b/roomserver/internal/perform_peek.go @@ -16,13 +16,10 @@ package internal import ( "context" - "errors" "fmt" "strings" - "time" fsAPI "github.com/matrix-org/dendrite/federationsender/api" - "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" @@ -80,7 +77,7 @@ func (r *RoomserverInternalAPI) performPeek( func (r *RoomserverInternalAPI) performPeekRoomByAlias( ctx context.Context, - req *api.PerformJoinRequest, + req *api.PerformPeekRequest, ) (string, error) { // Get the domain part of the room alias. _, domain, err := gomatrixserverlib.SplitID('#', req.RoomIDOrAlias) @@ -149,7 +146,7 @@ func (r *RoomserverInternalAPI) performPeekRoomByID( // TODO: handle federated peeks - err := r.WriteOutputEvents(roomID, []api.OutputEvent{ + err = r.WriteOutputEvents(roomID, []api.OutputEvent{ { Type: api.OutputTypeNewPeek, NewPeek: &api.OutputNewPeek{ @@ -167,5 +164,5 @@ func (r *RoomserverInternalAPI) performPeekRoomByID( // it will have been overwritten with a room ID by performPeekRoomByAlias. // We should now include this in the response so that the CS API can // return the right room ID. - return + return roomID, nil; } diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index 1657bcdeb6..4ffc3c8bb8 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -26,6 +26,7 @@ const ( // Perform operations RoomserverPerformInvitePath = "/roomserver/performInvite" + RoomserverPerformPeekPath = "/roomserver/performPeek" RoomserverPerformJoinPath = "/roomserver/performJoin" RoomserverPerformLeavePath = "/roomserver/performLeave" RoomserverPerformBackfillPath = "/roomserver/performBackfill" @@ -179,6 +180,23 @@ func (h *httpRoomserverInternalAPI) PerformJoin( } } +func (h *httpRoomserverInternalAPI) PerformPeek( + ctx context.Context, + request *api.PerformPeekRequest, + response *api.PerformPeekResponse, +) { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformPeek") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverPerformPeekPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) + if err != nil { + response.Error = &api.PerformError{ + Msg: fmt.Sprintf("failed to communicate with roomserver: %s", err), + } + } +} + func (h *httpRoomserverInternalAPI) PerformLeave( ctx context.Context, request *api.PerformLeaveRequest, diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index 9521229529..69a92110e7 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -31,7 +31,7 @@ type Database interface { // AllJoinedUsersInRooms returns a map of room ID to a list of all joined user IDs. AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) // AllPeekingDevicesInRooms returns a map of room ID to a list of all peeking devices. - AllPeekingDevicesInRooms(ctx context.Context) (map[string][]PeekingDevice, error) + AllPeekingDevicesInRooms(ctx context.Context) (map[string][]types.PeekingDevice, error) // Events lookups a list of event by their event ID. // Returns a list of events matching the requested IDs found in the database. // If an event is not found in the database then it will be omitted from the list. @@ -83,6 +83,9 @@ type Database interface { // RetireInviteEvent removes an old invite event from the database. Returns the new position of the retired invite. // Returns an error if there was a problem communicating with the database. RetireInviteEvent(ctx context.Context, inviteEventID string) (types.StreamPosition, error) + // AddPeek adds a new peek to our DB for a given room by a given user's device. + // Returns an error if there was a problem communicating with the database. + AddPeek(ctx context.Context, RoomID, UserID, DeviceID string) (types.StreamPosition, error) // SetTypingTimeoutCallback sets a callback function that is called right after // a user is removed from the typing user list due to timeout. SetTypingTimeoutCallback(fn cache.TimeoutCallbackFn) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index cf8dd604c5..76bb15259d 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -39,6 +39,7 @@ type Database struct { DB *sql.DB Writer sqlutil.Writer Invites tables.Invites + Peeks tables.Peeks AccountData tables.AccountData OutputEvents tables.Events Topology tables.Topology @@ -120,7 +121,7 @@ func (d *Database) AllJoinedUsersInRooms(ctx context.Context) (map[string][]stri return d.CurrentRoomState.SelectJoinedUsers(ctx) } -func (d *Database) AllPeekingDevicesInRooms(ctx context.Context) (map[string][]PeekingDevice, error) { +func (d *Database) AllPeekingDevicesInRooms(ctx context.Context) (map[string][]types.PeekingDevice, error) { return d.Peeks.SelectPeekingDevices(ctx) } @@ -198,7 +199,7 @@ func (d *Database) AddPeek( ctx context.Context, roomID, userID, deviceID string, ) (sp types.StreamPosition, err error) { _ = d.Writer.Do(nil, nil, func(_ *sql.Tx) error { - sp, err = d.Peeks.InsertPeek(ctx, nil, inviteEvent) + sp, err = d.Peeks.InsertPeek(ctx, nil, roomID, userID, deviceID) return nil }) return @@ -992,7 +993,7 @@ func (d *Database) getStateDeltas( // find out which rooms this user is peeking, if any. // We do this before joins so joins overwrite peeks - peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.DeviceID) + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } @@ -1006,7 +1007,7 @@ func (d *Database) getStateDeltas( if err != nil { return nil, nil, err } - state[roomID] = s + state[peek.RoomID] = s } deltas = append(deltas, stateDelta{ @@ -1017,7 +1018,7 @@ func (d *Database) getStateDeltas( } if len(peeks) > 0 { - err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.DeviceID) + err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } @@ -1084,7 +1085,7 @@ func (d *Database) getStateDeltasForFullStateSync( return nil, nil, err } - peeks, err = d.Peeks.SelectPeeks(ctx, txn, userID, device,ID) + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } @@ -1119,7 +1120,7 @@ func (d *Database) getStateDeltasForFullStateSync( } if len(peeks) > 0 { - err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.DeviceID) + err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 7ac329f662..9218e0e051 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -17,13 +17,12 @@ package sqlite3 import ( "context" "database/sql" - "encoding/json" + "time" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/syncapi/storage/tables" "github.com/matrix-org/dendrite/syncapi/types" - "github.com/matrix-org/gomatrixserverlib" ) const peeksSchema = ` @@ -49,30 +48,33 @@ const insertPeekSQL = "" + const deletePeekSQL = "" + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2 and device_id = $3" -const selectPeeksSQL == "" + +const selectPeeksSQL = "" + "SELECT room_id, new FROM syncapi_peeks WHERE user_id = $1 and device_id = $2" -const selectPeekingDevicesSQL == "" + +const selectPeekingDevicesSQL = "" + "SELECT room_id, user_id, device_id FROM syncapi_peeks" -const markPeeksAsOldSQL == "" + +const markPeeksAsOldSQL = "" + "UPDATE syncapi_peeks SET new=false WHERE user_id = $1 and device_id = $2" type peekStatements struct { db *sql.DB + streamIDStatements *streamIDStatements insertPeekStmt *sql.Stmt deletePeekStmt *sql.Stmt selectPeeksStmt *sql.Stmt selectPeekingDevicesStmt *sql.Stmt + markPeeksAsOldStmt *sql.Stmt } -func NewSqlitePeeksTable(db *sql.DB) (tables.Peeks, error) { +func NewSqlitePeeksTable(db *sql.DB, streamID *streamIDStatements) (tables.Peeks, error) { _, err := db.Exec(filterSchema) if err != nil { return nil, err } s := &peekStatements{ db: db, + streamIDStatements: streamID, } if s.insertPeekStmt, err = db.Prepare(insertPeekSQL); err != nil { return nil, err @@ -117,7 +119,7 @@ func (s *peekStatements) DeletePeek( func (s *peekStatements) SelectPeeks( ctx context.Context, txn *sql.Tx, userID, deviceID string, -) (peeks []Peek, err error) { +) (peeks []types.Peek, err error) { rows, err := sqlutil.TxStmt(txn, s.selectPeeksStmt).QueryContext(ctx, userID, deviceID) if err != nil { return @@ -125,8 +127,8 @@ func (s *peekStatements) SelectPeeks( defer internal.CloseAndLogIfError(ctx, rows, "SelectPeeks: rows.close() failed") for rows.Next() { - peek = Peek{} - if err = rows.Scan(&peek.roomId, &peek.new); err != nil { + peek := types.Peek{} + if err = rows.Scan(&peek.RoomID, &peek.New); err != nil { return } peeks = append(peeks, peek) @@ -138,27 +140,27 @@ func (s *peekStatements) SelectPeeks( func (s *peekStatements) MarkPeeksAsOld ( ctx context.Context, txn *sql.Tx, userID, deviceID string, ) (err error) { - _, err := sqlutil.TxStmt(txn, s.markPeeksAsOldStmt).ExecContext(ctx, userID, deviceID) + _, err = sqlutil.TxStmt(txn, s.markPeeksAsOldStmt).ExecContext(ctx, userID, deviceID) return } func (s *peekStatements) SelectPeekingDevices( ctx context.Context, -) (peekingDevices map[string][]PeekingDevice, err error) { +) (peekingDevices map[string][]types.PeekingDevice, err error) { rows, err := s.selectPeekingDevicesStmt.QueryContext(ctx) if err != nil { return nil, err } defer internal.CloseAndLogIfError(ctx, rows, "SelectPeekingDevices: rows.close() failed") - result := make(map[string][]PeekingDevice) + result := make(map[string][]types.PeekingDevice) for rows.Next() { var roomID, userID, deviceID string if err := rows.Scan(&roomID, &userID, &deviceID); err != nil { return nil, err } devices := result[roomID] - devices = append(devices, PeekingDevice{userID, deviceID}) + devices = append(devices, types.PeekingDevice{userID, deviceID}) result[roomID] = devices } return result, nil diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index 81197bb765..5db9939a3e 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -75,6 +75,10 @@ func (d *SyncServerDatasource) prepare() (err error) { if err != nil { return err } + peeks, err := NewSqlitePeeksTable(d.db, &d.streamID) + if err != nil { + return err + } topology, err := NewSqliteTopologyTable(d.db) if err != nil { return err @@ -95,6 +99,7 @@ func (d *SyncServerDatasource) prepare() (err error) { DB: d.db, Writer: sqlutil.NewExclusiveWriter(), Invites: invites, + Peeks: peeks, AccountData: accountData, OutputEvents: events, BackwardExtremities: bwExtrem, diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 9566b8b381..b7281f11c5 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -42,8 +42,9 @@ type Invites interface { type Peeks interface { InsertPeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) - SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []string, err error) - SelectPeekingDevices((ctxt context.Context) (peekingDevices map[string][]PeekingDevice, err error) + SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []types.Peek, err error) + SelectPeekingDevices(ctxt context.Context) (peekingDevices map[string][]types.PeekingDevice, err error) + MarkPeeksAsOld(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (err error) } type Events interface { diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index f09c2b4d85..e6f7440e0f 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -221,7 +221,7 @@ func (n *Notifier) setUsersJoinedToRooms(roomIDToUserIDs map[string][]string) { // setPeekingDevices marks the given devices as peeking in the given rooms, such that new events from // these rooms will wake the given devices' /sync requests. This should be called prior to ANY calls to // OnNewEvent (eg on startup) to prevent racing. -func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]PeekingDevices) { +func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]types.PeekingDevice) { // This is just the bulk form of addPeekingDevice for roomID, peekingDevices := range roomIDToPeekingDevices { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { @@ -235,7 +235,7 @@ func (n *Notifier) setPeekingDevices(roomIDToPeekingDevices map[string][]Peeking // wakeupUsers will wake up the sync strems for all of the devices for all of the // specified user IDs, and also the specified peekingDevices -func (n *Notifier) wakeupUsers(userIDs []string, peekingDevices []PeekingDevice, newPos types.StreamingToken) { +func (n *Notifier) wakeupUsers(userIDs []string, peekingDevices []types.PeekingDevice, newPos types.StreamingToken) { for _, userID := range userIDs { for _, stream := range n.fetchUserStreams(userID) { if stream == nil { @@ -248,7 +248,7 @@ func (n *Notifier) wakeupUsers(userIDs []string, peekingDevices []PeekingDevice, if peekingDevices != nil { for _, peekingDevice := range peekingDevices { // TODO: don't bother waking up for devices whose users we already woke up - if stream := n.fetchUserDeviceStream(peekingDevice.UserID, peekingDevice.DeviceID, false); stream != nil { + if stream := n.fetchUserDeviceStream(peekingDevice.UserID, peekingDevice.ID, false); stream != nil { stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream } } @@ -337,7 +337,7 @@ func (n *Notifier) addPeekingDevice(roomID, userID, deviceID string) { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } - n.roomIDToPeekingDevices[roomID].add(PeekingDevice{deviceID, userID}) + n.roomIDToPeekingDevices[roomID].add(types.PeekingDevice{deviceID, userID}) } // Not thread-safe: must be called on the OnNewEvent goroutine only @@ -346,11 +346,11 @@ func (n *Notifier) removePeekingDevice(roomID, userID, deviceID string) { n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } // XXX: is this going to work as a key? - n.roomIDToPeekingDevices[roomID].remove(PeekingDevice{deviceID, userID}) + n.roomIDToPeekingDevices[roomID].remove(types.PeekingDevice{deviceID, userID}) } // Not thread-safe: must be called on the OnNewEvent goroutine only -func (n *Notifier) PeekingDevices(roomID string) (peekingDevices []PeekingDevices) { +func (n *Notifier) PeekingDevices(roomID string) (peekingDevices []types.PeekingDevice) { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { return } @@ -407,17 +407,17 @@ func (s userIDSet) values() (vals []string) { // A set of PeekingDevices, similar to userIDSet -type peekingDeviceSet map[PeekingDevice]bool +type peekingDeviceSet map[types.PeekingDevice]bool -func (s peekingDeviceSet) add(d PeekingDevice) { +func (s peekingDeviceSet) add(d types.PeekingDevice) { s[d] = true } -func (s peekingDeviceSet) remove(d PeekingDevice) { +func (s peekingDeviceSet) remove(d types.PeekingDevice) { delete(s, d) } -func (s peekingDeviceSet) values() (vals []PeekingDevice) { +func (s peekingDeviceSet) values() (vals []types.PeekingDevice) { for d := range s { vals = append(vals, d) } diff --git a/syncapi/types/types.go b/syncapi/types/types.go index 80a0109047..b9888a65f3 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -517,5 +517,5 @@ type PeekingDevice struct { type Peek struct { RoomID string - New boolean + New bool } \ No newline at end of file From d1e4d66126bdf500488d9014afdb5dbae23a1d6c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 17:35:23 +0300 Subject: [PATCH 06/68] make it launch --- syncapi/storage/sqlite3/peeks_table.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 9218e0e051..088f4007e1 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -33,16 +33,16 @@ CREATE TABLE IF NOT EXISTS syncapi_peeks ( device_id TEXT NOT NULL, new BOOL NOT NULL DEFAULT true, -- When the peek was created in UNIX epoch ms. - creation_ts INTEGER NOT NULL, + creation_ts INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS syncapi_peeks_room_id_idx ON syncapi_peeks(room_id); -CREATE INDEX IF NOT EXISTS syncapi_peeks_user_id_device_id_idx ON syncapi_peeks(user_Id, device_id); +CREATE INDEX IF NOT EXISTS syncapi_peeks_user_id_device_id_idx ON syncapi_peeks(user_id, device_id); ` const insertPeekSQL = "" + "INSERT INTO syncapi_peeks" + - " (id, room_id, user_id, device_id, creation_ts" + + " (id, room_id, user_id, device_id, creation_ts)" + " VALUES ($1, $2, $3, $4, $5)" const deletePeekSQL = "" + @@ -68,7 +68,7 @@ type peekStatements struct { } func NewSqlitePeeksTable(db *sql.DB, streamID *streamIDStatements) (tables.Peeks, error) { - _, err := db.Exec(filterSchema) + _, err := db.Exec(peeksSchema) if err != nil { return nil, err } From f006b37bf001ab0cbcb12cc7d680cc88f45f743e Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 18:16:07 +0300 Subject: [PATCH 07/68] add peeking to getResponseWithPDUsForCompleteSync --- syncapi/storage/shared/syncserver.go | 103 ++++++++++++++++++--------- 1 file changed, 68 insertions(+), 35 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 76bb15259d..90db145e50 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -644,7 +644,7 @@ func (d *Database) RedactEvent(ctx context.Context, redactedEventID string, reda // nolint:nakedret func (d *Database) getResponseWithPDUsForCompleteSync( ctx context.Context, res *types.Response, - userID string, + userID string, deviceID string, numRecentEventsPerRoom int, ) ( toPos types.StreamingToken, @@ -684,46 +684,30 @@ func (d *Database) getResponseWithPDUsForCompleteSync( // Build up a /sync response. Add joined rooms. for _, roomID := range joinedRoomIDs { - var stateEvents []gomatrixserverlib.HeaderedEvent - stateEvents, err = d.CurrentRoomState.SelectCurrentState(ctx, txn, roomID, &stateFilter) + var jr *types.JoinResponse + jr, err = d.getJoinResponseForCompleteSync( + ctx, txn, roomID, r, &stateFilter, numRecentEventsPerRoom, + ) if err != nil { return } - // TODO: When filters are added, we may need to call this multiple times to get enough events. - // See: https://github.com/matrix-org/synapse/blob/v0.19.3/synapse/handlers/sync.py#L316 - var recentStreamEvents []types.StreamEvent - var limited bool - recentStreamEvents, limited, err = d.OutputEvents.SelectRecentEvents( - ctx, txn, roomID, r, numRecentEventsPerRoom, true, true, + res.Rooms.Join[roomID] = *jr + } + + // Add peeked rooms. + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, deviceID) + if err != nil { + return + } + for _, peek := range peeks { + var jr *types.JoinResponse + jr, err = d.getJoinResponseForCompleteSync( + ctx, txn, peek.RoomID, r, &stateFilter, numRecentEventsPerRoom, ) if err != nil { return } - - // Retrieve the backward topology position, i.e. the position of the - // oldest event in the room's topology. - var prevBatchStr string - if len(recentStreamEvents) > 0 { - var backwardTopologyPos, backwardStreamPos types.StreamPosition - backwardTopologyPos, backwardStreamPos, err = d.Topology.SelectPositionInTopology(ctx, txn, recentStreamEvents[0].EventID()) - if err != nil { - return - } - prevBatch := types.NewTopologyToken(backwardTopologyPos, backwardStreamPos) - prevBatch.Decrement() - prevBatchStr = prevBatch.String() - } - - // We don't include a device here as we don't need to send down - // transaction IDs for complete syncs - recentEvents := d.StreamEventsToEvents(nil, recentStreamEvents) - stateEvents = removeDuplicates(stateEvents, recentEvents) - jr := types.NewJoinResponse() - jr.Timeline.PrevBatch = prevBatchStr - jr.Timeline.Events = gomatrixserverlib.HeaderedToClientEvents(recentEvents, gomatrixserverlib.FormatSync) - jr.Timeline.Limited = limited - jr.State.Events = gomatrixserverlib.HeaderedToClientEvents(stateEvents, gomatrixserverlib.FormatSync) - res.Rooms.Join[roomID] = *jr + res.Rooms.Peek[peek.RoomID] = *jr } if err = d.addInvitesToResponse(ctx, txn, userID, r, res); err != nil { @@ -734,12 +718,61 @@ func (d *Database) getResponseWithPDUsForCompleteSync( return //res, toPos, joinedRoomIDs, err } +func (d* Database) getJoinResponseForCompleteSync( + ctx context.Context, txn *sql.Tx, + roomID string, + r types.Range, + stateFilter *gomatrixserverlib.StateFilter, + numRecentEventsPerRoom int, +) (jr *types.JoinResponse, err error) { + var stateEvents []gomatrixserverlib.HeaderedEvent + stateEvents, err = d.CurrentRoomState.SelectCurrentState(ctx, txn, roomID, stateFilter) + if err != nil { + return + } + // TODO: When filters are added, we may need to call this multiple times to get enough events. + // See: https://github.com/matrix-org/synapse/blob/v0.19.3/synapse/handlers/sync.py#L316 + var recentStreamEvents []types.StreamEvent + var limited bool + recentStreamEvents, limited, err = d.OutputEvents.SelectRecentEvents( + ctx, txn, roomID, r, numRecentEventsPerRoom, true, true, + ) + if err != nil { + return + } + + // Retrieve the backward topology position, i.e. the position of the + // oldest event in the room's topology. + var prevBatchStr string + if len(recentStreamEvents) > 0 { + var backwardTopologyPos, backwardStreamPos types.StreamPosition + backwardTopologyPos, backwardStreamPos, err = d.Topology.SelectPositionInTopology(ctx, txn, recentStreamEvents[0].EventID()) + if err != nil { + return + } + prevBatch := types.NewTopologyToken(backwardTopologyPos, backwardStreamPos) + prevBatch.Decrement() + prevBatchStr = prevBatch.String() + } + + // We don't include a device here as we don't need to send down + // transaction IDs for complete syncs + recentEvents := d.StreamEventsToEvents(nil, recentStreamEvents) + stateEvents = removeDuplicates(stateEvents, recentEvents) + jr = types.NewJoinResponse() + jr.Timeline.PrevBatch = prevBatchStr + jr.Timeline.Events = gomatrixserverlib.HeaderedToClientEvents(recentEvents, gomatrixserverlib.FormatSync) + jr.Timeline.Limited = limited + jr.State.Events = gomatrixserverlib.HeaderedToClientEvents(stateEvents, gomatrixserverlib.FormatSync) + return jr, nil +} + func (d *Database) CompleteSync( ctx context.Context, res *types.Response, device userapi.Device, numRecentEventsPerRoom int, ) (*types.Response, error) { toPos, joinedRoomIDs, err := d.getResponseWithPDUsForCompleteSync( - ctx, res, device.UserID, numRecentEventsPerRoom, + ctx, res, device.UserID, device.ID, numRecentEventsPerRoom, ) if err != nil { return nil, fmt.Errorf("d.getResponseWithPDUsForCompleteSync: %w", err) From 6c3a896910cc8a2fc93d7fdcf7520e26336f2ed1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 18:58:10 +0300 Subject: [PATCH 08/68] cancel any peeks when we join a room --- syncapi/storage/shared/syncserver.go | 10 ++++++++++ syncapi/storage/sqlite3/peeks_table.go | 18 ++++++++++++++++++ syncapi/storage/tables/interface.go | 1 + syncapi/sync/notifier.go | 6 +++--- syncapi/types/types.go | 4 ++-- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 90db145e50..d8ae455eb5 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -1032,7 +1032,9 @@ func (d *Database) getStateDeltas( } // add peek blocks + peeking := make(map[string]bool) for _, peek := range peeks { + peeking[peek.RoomID] = true if peek.New { // send full room state down instead of a delta var s []types.StreamEvent @@ -1067,6 +1069,14 @@ func (d *Database) getStateDeltas( // the timeline. if membership := getMembershipFromEvent(&ev.Event, userID); membership != "" { if membership == gomatrixserverlib.Join { + if peeking[roomID] { + // we automatically cancel our peeks when we join a room + _, err = d.Peeks.DeletePeeks(ctx, txn, roomID, userID) + if err != nil { + return nil, nil, err + } + } + // send full room state down instead of a delta var s []types.StreamEvent s, err = d.currentStateStreamEventsForRoom(ctx, txn, roomID, stateFilter) diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 088f4007e1..7e8d4c69f3 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -48,6 +48,9 @@ const insertPeekSQL = "" + const deletePeekSQL = "" + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2 and device_id = $3" +const deletePeeksSQL = "" + + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2" + const selectPeeksSQL = "" + "SELECT room_id, new FROM syncapi_peeks WHERE user_id = $1 and device_id = $2" @@ -62,6 +65,7 @@ type peekStatements struct { streamIDStatements *streamIDStatements insertPeekStmt *sql.Stmt deletePeekStmt *sql.Stmt + deletePeeksStmt *sql.Stmt selectPeeksStmt *sql.Stmt selectPeekingDevicesStmt *sql.Stmt markPeeksAsOldStmt *sql.Stmt @@ -82,6 +86,9 @@ func NewSqlitePeeksTable(db *sql.DB, streamID *streamIDStatements) (tables.Peeks if s.deletePeekStmt, err = db.Prepare(deletePeekSQL); err != nil { return nil, err } + if s.deletePeeksStmt, err = db.Prepare(deletePeeksSQL); err != nil { + return nil, err + } if s.selectPeeksStmt, err = db.Prepare(selectPeeksSQL); err != nil { return nil, err } @@ -117,6 +124,17 @@ func (s *peekStatements) DeletePeek( return } +func (s *peekStatements) DeletePeeks( + ctx context.Context, txn *sql.Tx, roomID, userID string, +) (streamPos types.StreamPosition, err error) { + streamPos, err = s.streamIDStatements.nextStreamID(ctx, txn) + if err != nil { + return + } + _, err = sqlutil.TxStmt(txn, s.deletePeeksStmt).ExecContext(ctx, roomID, userID) + return +} + func (s *peekStatements) SelectPeeks( ctx context.Context, txn *sql.Tx, userID, deviceID string, ) (peeks []types.Peek, err error) { diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index b7281f11c5..3c6ee4bbcc 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -42,6 +42,7 @@ type Invites interface { type Peeks interface { InsertPeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) + DeletePeeks(ctx context.Context, txn *sql.Tx, roomID, userID string) (streamPos types.StreamPosition, err error) SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []types.Peek, err error) SelectPeekingDevices(ctxt context.Context) (peekingDevices map[string][]types.PeekingDevice, err error) MarkPeeksAsOld(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (err error) diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index e6f7440e0f..6130023237 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -248,7 +248,7 @@ func (n *Notifier) wakeupUsers(userIDs []string, peekingDevices []types.PeekingD if peekingDevices != nil { for _, peekingDevice := range peekingDevices { // TODO: don't bother waking up for devices whose users we already woke up - if stream := n.fetchUserDeviceStream(peekingDevice.UserID, peekingDevice.ID, false); stream != nil { + if stream := n.fetchUserDeviceStream(peekingDevice.UserID, peekingDevice.DeviceID, false); stream != nil { stream.Broadcast(newPos) // wake up all goroutines Wait()ing on this stream } } @@ -337,7 +337,7 @@ func (n *Notifier) addPeekingDevice(roomID, userID, deviceID string) { if _, ok := n.roomIDToPeekingDevices[roomID]; !ok { n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } - n.roomIDToPeekingDevices[roomID].add(types.PeekingDevice{deviceID, userID}) + n.roomIDToPeekingDevices[roomID].add(types.PeekingDevice{userID, deviceID}) } // Not thread-safe: must be called on the OnNewEvent goroutine only @@ -346,7 +346,7 @@ func (n *Notifier) removePeekingDevice(roomID, userID, deviceID string) { n.roomIDToPeekingDevices[roomID] = make(peekingDeviceSet) } // XXX: is this going to work as a key? - n.roomIDToPeekingDevices[roomID].remove(types.PeekingDevice{deviceID, userID}) + n.roomIDToPeekingDevices[roomID].remove(types.PeekingDevice{userID, deviceID}) } // Not thread-safe: must be called on the OnNewEvent goroutine only diff --git a/syncapi/types/types.go b/syncapi/types/types.go index b9888a65f3..23d88626cb 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -511,8 +511,8 @@ type SendToDeviceEvent struct { } type PeekingDevice struct { - ID string - UserID string + UserID string + DeviceID string } type Peek struct { From 7b38d4857f1ffe6661e0580e9d418ec11a727afb Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 23:28:46 +0300 Subject: [PATCH 09/68] spell out how to runoutside of docker if you want speed --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e3fc53e8bb..864a24e99d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,18 @@ matrixdotorg/sytest-dendrite:latest tests/50federation/40devicelists.pl ``` See [sytest.md](docs/sytest.md) for the full description of these flags. +You can try running sytest outside of docker for faster runs, but the dependencies can be temperamental +and we recommend using docker where possible. +``` +cd sytest +export PERL5LIB=$HOME/lib/perl5 +export PERL_MB_OPT=--install_base=$HOME +export PERL_MM_OPT=INSTALL_BASE=$HOME +./install-deps.pl + +./run-tests.pl -I Dendrite::Monolith -d $PATH_TO_DENDRITE_BINARIES +``` + Sometimes Sytest is testing the wrong thing or is flakey, so it will need to be patched. Ask on `#dendrite-dev:matrix.org` if you think this is the case for you and we'll be happy to help. From e5899843eadd93458cb4caad4f2d9129d9b70759 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Mon, 31 Aug 2020 23:28:55 +0300 Subject: [PATCH 10/68] fix SQL --- syncapi/storage/sqlite3/peeks_table.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 7e8d4c69f3..c013a6350e 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -42,8 +42,8 @@ CREATE INDEX IF NOT EXISTS syncapi_peeks_user_id_device_id_idx ON syncapi_peeks( const insertPeekSQL = "" + "INSERT INTO syncapi_peeks" + - " (id, room_id, user_id, device_id, creation_ts)" + - " VALUES ($1, $2, $3, $4, $5)" + " (room_id, user_id, device_id, creation_ts)" + + " VALUES ($1, $2, $3, $4)" const deletePeekSQL = "" + "DELETE FROM syncapi_peeks WHERE room_id = $1 AND user_id = $2 and device_id = $3" From 0bb2c2c4183f945bad7a0ae4166fc439486da20f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 00:24:23 +0300 Subject: [PATCH 11/68] remove unnecessary txn for SelectPeeks --- syncapi/storage/shared/syncserver.go | 6 +++--- syncapi/storage/sqlite3/peeks_table.go | 4 ++-- syncapi/storage/tables/interface.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index d8ae455eb5..d569313940 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -695,7 +695,7 @@ func (d *Database) getResponseWithPDUsForCompleteSync( } // Add peeked rooms. - peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, deviceID) + peeks, err := d.Peeks.SelectPeeks(ctx, userID, deviceID) if err != nil { return } @@ -1026,7 +1026,7 @@ func (d *Database) getStateDeltas( // find out which rooms this user is peeking, if any. // We do this before joins so joins overwrite peeks - peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) + peeks, err := d.Peeks.SelectPeeks(ctx, userID, device.ID) if err != nil { return nil, nil, err } @@ -1128,7 +1128,7 @@ func (d *Database) getStateDeltasForFullStateSync( return nil, nil, err } - peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) + peeks, err := d.Peeks.SelectPeeks(ctx, userID, device.ID) if err != nil { return nil, nil, err } diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index c013a6350e..8fb74341b9 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -136,9 +136,9 @@ func (s *peekStatements) DeletePeeks( } func (s *peekStatements) SelectPeeks( - ctx context.Context, txn *sql.Tx, userID, deviceID string, + ctx context.Context, userID, deviceID string, ) (peeks []types.Peek, err error) { - rows, err := sqlutil.TxStmt(txn, s.selectPeeksStmt).QueryContext(ctx, userID, deviceID) + rows, err := s.selectPeeksStmt.QueryContext(ctx, userID, deviceID) if err != nil { return } diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 3c6ee4bbcc..181d7b960a 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -43,7 +43,7 @@ type Peeks interface { InsertPeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeeks(ctx context.Context, txn *sql.Tx, roomID, userID string) (streamPos types.StreamPosition, err error) - SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []types.Peek, err error) + SelectPeeks(ctxt context.Context, userID, deviceID string) (peeks []types.Peek, err error) SelectPeekingDevices(ctxt context.Context) (peekingDevices map[string][]types.PeekingDevice, err error) MarkPeeksAsOld(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (err error) } From 86e9736ca3a3a40c5d18b412774a872f21d020f5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 18:04:55 +0300 Subject: [PATCH 12/68] fix s/join/peek/ cargocult fail --- 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 26061b5700..9e7970392c 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -102,7 +102,7 @@ func Setup( }), ).Methods(http.MethodPost, http.MethodOptions) r0mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Join, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) if err != nil { return util.ErrorResponse(err) From bfecc8e0e96560dd84286752db37769b92f2e6af Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 1 Sep 2020 19:10:57 +0100 Subject: [PATCH 13/68] HACK: Track goroutine IDs to determine when we write by the wrong thread To use: set `DENDRITE_TRACE_SQL=1` then grep for `unsafe` --- internal/sqlutil/trace.go | 47 +++++++- internal/sqlutil/writer.go | 2 + internal/sqlutil/writer_dummy.go | 4 + internal/sqlutil/writer_exclusive.go | 30 ++++- syncapi/storage/sqlite3/syncserver.go | 156 +++++++++++++++++++++++++- 5 files changed, 233 insertions(+), 6 deletions(-) diff --git a/internal/sqlutil/trace.go b/internal/sqlutil/trace.go index fbd983bec4..dd92daf684 100644 --- a/internal/sqlutil/trace.go +++ b/internal/sqlutil/trace.go @@ -31,16 +31,33 @@ import ( ) var tracingEnabled = os.Getenv("DENDRITE_TRACE_SQL") == "1" +var dbToWriter map[string]Writer +var CtxDBInstance = "db_instance" +var instCount = 0 type traceInterceptor struct { sqlmw.NullInterceptor + conn driver.Conn } func (in *traceInterceptor) StmtQueryContext(ctx context.Context, stmt driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) { startedAt := time.Now() rows, err := stmt.QueryContext(ctx, args) + key := ctx.Value(CtxDBInstance) + var safe string + if key != nil { + w := dbToWriter[key.(string)] + if w == nil { + safe = fmt.Sprintf("no writer for key %s", key) + } else { + safe = w.Safe() + } + } + if safe != "" { + logrus.Infof("unsafe: %s -- %s", safe, query) + } - logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).Debug("executed sql query ", query, " args: ", args) + logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).WithField("safe", safe).Debug("executed sql query ", query, " args: ", args) return rows, err } @@ -48,8 +65,21 @@ func (in *traceInterceptor) StmtQueryContext(ctx context.Context, stmt driver.St func (in *traceInterceptor) StmtExecContext(ctx context.Context, stmt driver.StmtExecContext, query string, args []driver.NamedValue) (driver.Result, error) { startedAt := time.Now() result, err := stmt.ExecContext(ctx, args) + key := ctx.Value(CtxDBInstance) + var safe string + if key != nil { + w := dbToWriter[key.(string)] + if w == nil { + safe = fmt.Sprintf("no writer for key %s", key) + } else { + safe = w.Safe() + } + } + if safe != "" { + logrus.Infof("unsafe: %s -- %s", safe, query) + } - logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).Debug("executed sql query ", query, " args: ", args) + logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).WithField("safe", safe).Debug("executed sql query ", query, " args: ", args) return result, err } @@ -75,6 +105,18 @@ func (in *traceInterceptor) RowsNext(c context.Context, rows driver.Rows, dest [ return err } +func OpenWithWriter(dbProperties *config.DatabaseOptions, w Writer) (*sql.DB, context.Context, error) { + db, err := Open(dbProperties) + if err != nil { + return nil, nil, err + } + instCount++ + ctxVal := fmt.Sprintf("%d", instCount) + dbToWriter[ctxVal] = w + ctx := context.WithValue(context.TODO(), CtxDBInstance, ctxVal) + return db, ctx, nil +} + // Open opens a database specified by its database driver name and a driver-specific data source name, // usually consisting of at least a database name and connection information. Includes tracing driver // if DENDRITE_TRACE_SQL=1 @@ -118,4 +160,5 @@ func Open(dbProperties *config.DatabaseOptions) (*sql.DB, error) { func init() { registerDrivers() + dbToWriter = make(map[string]Writer) } diff --git a/internal/sqlutil/writer.go b/internal/sqlutil/writer.go index 5d93fef4db..f966e250cf 100644 --- a/internal/sqlutil/writer.go +++ b/internal/sqlutil/writer.go @@ -43,4 +43,6 @@ type Writer interface { // Queue up one or more database write operations within the // provided function to be executed when it is safe to do so. Do(db *sql.DB, txn *sql.Tx, f func(txn *sql.Tx) error) error + + Safe() string } diff --git a/internal/sqlutil/writer_dummy.go b/internal/sqlutil/writer_dummy.go index f426c2bc3d..fbca3e773c 100644 --- a/internal/sqlutil/writer_dummy.go +++ b/internal/sqlutil/writer_dummy.go @@ -26,3 +26,7 @@ func (w *DummyWriter) Do(db *sql.DB, txn *sql.Tx, f func(txn *sql.Tx) error) err return f(txn) } } + +func (w *DummyWriter) Safe() string { + return "DummyWriter" +} diff --git a/internal/sqlutil/writer_exclusive.go b/internal/sqlutil/writer_exclusive.go index 002bc32cf3..933661958b 100644 --- a/internal/sqlutil/writer_exclusive.go +++ b/internal/sqlutil/writer_exclusive.go @@ -3,6 +3,10 @@ package sqlutil import ( "database/sql" "errors" + "fmt" + "runtime" + "strconv" + "strings" "go.uber.org/atomic" ) @@ -12,8 +16,9 @@ import ( // contend on database locks in, e.g. SQLite. Only one task will run // at a time on a given ExclusiveWriter. type ExclusiveWriter struct { - running atomic.Bool - todo chan transactionWriterTask + running atomic.Bool + todo chan transactionWriterTask + writerID int } func NewExclusiveWriter() Writer { @@ -30,6 +35,15 @@ type transactionWriterTask struct { wait chan error } +func (w *ExclusiveWriter) Safe() string { + a := goid() + b := w.writerID + if a == b { + return "" + } + return fmt.Sprintf("%v != %v", a, b) +} + // Do queues a task to be run by a TransactionWriter. The function // provided will be ran within a transaction as supplied by the // txn parameter if one is supplied, and if not, will take out a @@ -60,6 +74,7 @@ func (w *ExclusiveWriter) run() { if !w.running.CAS(false, true) { return } + w.writerID = goid() defer w.running.Store(false) for task := range w.todo { if task.db != nil && task.txn != nil { @@ -74,3 +89,14 @@ func (w *ExclusiveWriter) run() { close(task.wait) } } + +func goid() int { + var buf [64]byte + n := runtime.Stack(buf[:], false) + idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] + id, err := strconv.Atoi(idField) + if err != nil { + panic(fmt.Sprintf("cannot get goroutine id: %v", err)) + } + return id +} diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index f68bf737a2..135495a503 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -16,6 +16,7 @@ package sqlite3 import ( + "context" "database/sql" // Import the sqlite3 package @@ -24,7 +25,11 @@ import ( "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/syncapi/storage/shared" + "github.com/matrix-org/dendrite/syncapi/types" + userapi "github.com/matrix-org/dendrite/userapi/api" + "github.com/matrix-org/gomatrixserverlib" ) // SyncServerDatasource represents a sync server datasource which manages @@ -35,6 +40,7 @@ type SyncServerDatasource struct { writer sqlutil.Writer sqlutil.PartitionOffsetStatements streamID streamIDStatements + dbctx context.Context } // NewDatabase creates a new sync server database @@ -42,10 +48,12 @@ type SyncServerDatasource struct { func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, error) { var d SyncServerDatasource var err error - if d.db, err = sqlutil.Open(dbProperties); err != nil { + d.writer = sqlutil.NewExclusiveWriter() + d.db, d.dbctx, err = sqlutil.OpenWithWriter(dbProperties, d.writer) + if err != nil { return nil, err } - d.writer = sqlutil.NewExclusiveWriter() + if err = d.prepare(); err != nil { return nil, err } @@ -106,3 +114,147 @@ func (d *SyncServerDatasource) prepare() (err error) { } return nil } + +func (d *SyncServerDatasource) Events(ctx context.Context, eventIDs []string) ([]gomatrixserverlib.HeaderedEvent, error) { + return d.Database.Events(d.dbctx, eventIDs) +} +func (d *SyncServerDatasource) WriteEvent(ctx context.Context, ev *gomatrixserverlib.HeaderedEvent, addStateEvents []gomatrixserverlib.HeaderedEvent, + addStateEventIDs []string, removeStateEventIDs []string, transactionID *api.TransactionID, excludeFromSync bool) (types.StreamPosition, error) { + return d.Database.WriteEvent(d.dbctx, ev, addStateEvents, addStateEventIDs, removeStateEventIDs, transactionID, excludeFromSync) +} +func (d *SyncServerDatasource) AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) { + return d.Database.AllJoinedUsersInRooms(d.dbctx) +} + +func (d *SyncServerDatasource) GetStateEvent(ctx context.Context, roomID, evType, stateKey string) (*gomatrixserverlib.HeaderedEvent, error) { + return d.Database.GetStateEvent(d.dbctx, roomID, evType, stateKey) +} + +// GetStateEventsForRoom fetches the state events for a given room. +// Returns an empty slice if no state events could be found for this room. +// Returns an error if there was an issue with the retrieval. +func (d *SyncServerDatasource) GetStateEventsForRoom(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter) (stateEvents []gomatrixserverlib.HeaderedEvent, err error) { + return d.Database.GetStateEventsForRoom(d.dbctx, roomID, stateFilterPart) +} + +// SyncPosition returns the latest positions for syncing. +func (d *SyncServerDatasource) SyncPosition(ctx context.Context) (types.StreamingToken, error) { + return d.Database.SyncPosition(d.dbctx) +} + +func (d *SyncServerDatasource) IncrementalSync(ctx context.Context, res *types.Response, device userapi.Device, fromPos, toPos types.StreamingToken, numRecentEventsPerRoom int, wantFullState bool) (*types.Response, error) { + return d.Database.IncrementalSync(d.dbctx, res, device, fromPos, toPos, numRecentEventsPerRoom, wantFullState) +} + +// CompleteSync returns a complete /sync API response for the given user. A response object +// must be provided for CompleteSync to populate - it will not create one. +func (d *SyncServerDatasource) CompleteSync(ctx context.Context, res *types.Response, device userapi.Device, numRecentEventsPerRoom int) (*types.Response, error) { + return d.Database.CompleteSync(d.dbctx, res, device, numRecentEventsPerRoom) +} + +// GetAccountDataInRange returns all account data for a given user inserted or +// updated between two given positions +// Returns a map following the format data[roomID] = []dataTypes +// If no data is retrieved, returns an empty map +// If there was an issue with the retrieval, returns an error +func (d *SyncServerDatasource) GetAccountDataInRange(ctx context.Context, userID string, r types.Range, accountDataFilterPart *gomatrixserverlib.EventFilter) (map[string][]string, error) { + return d.Database.GetAccountDataInRange(d.dbctx, userID, r, accountDataFilterPart) +} + +// UpsertAccountData keeps track of new or updated account data, by saving the type +// of the new/updated data, and the user ID and room ID the data is related to (empty) +// room ID means the data isn't specific to any room) +// If no data with the given type, user ID and room ID exists in the database, +// creates a new row, else update the existing one +// Returns an error if there was an issue with the upsert +func (d *SyncServerDatasource) UpsertAccountData(ctx context.Context, userID, roomID, dataType string) (types.StreamPosition, error) { + return d.Database.UpsertAccountData(d.dbctx, userID, roomID, dataType) +} + +// AddInviteEvent stores a new invite event for a user. +// If the invite was successfully stored this returns the stream ID it was stored at. +// Returns an error if there was a problem communicating with the database. +func (d *SyncServerDatasource) AddInviteEvent(ctx context.Context, inviteEvent gomatrixserverlib.HeaderedEvent) (types.StreamPosition, error) { + return d.Database.AddInviteEvent(d.dbctx, inviteEvent) +} + +// RetireInviteEvent removes an old invite event from the database. Returns the new position of the retired invite. +// Returns an error if there was a problem communicating with the database. +func (d *SyncServerDatasource) RetireInviteEvent(ctx context.Context, inviteEventID string) (types.StreamPosition, error) { + return d.Database.RetireInviteEvent(d.dbctx, inviteEventID) +} + +// GetEventsInStreamingRange retrieves all of the events on a given ordering using the given extremities and limit. +func (d *SyncServerDatasource) GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) { + return d.Database.GetEventsInStreamingRange(d.dbctx, from, to, roomID, limit, backwardOrdering) +} + +// GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit. +func (d *SyncServerDatasource) GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) { + return d.Database.GetEventsInTopologicalRange(d.dbctx, from, to, roomID, limit, backwardOrdering) +} + +// EventPositionInTopology returns the depth and stream position of the given event. +func (d *SyncServerDatasource) EventPositionInTopology(ctx context.Context, eventID string) (types.TopologyToken, error) { + return d.Database.EventPositionInTopology(d.dbctx, eventID) +} + +// BackwardExtremitiesForRoom returns a map of backwards extremity event ID to a list of its prev_events. +func (d *SyncServerDatasource) BackwardExtremitiesForRoom(ctx context.Context, roomID string) (backwardExtremities map[string][]string, err error) { + return d.Database.BackwardExtremitiesForRoom(d.dbctx, roomID) +} + +func (d *SyncServerDatasource) MaxTopologicalPosition(ctx context.Context, roomID string) (types.TopologyToken, error) { + return d.Database.MaxTopologicalPosition(d.dbctx, roomID) +} + +// SendToDeviceUpdatesForSync returns a list of send-to-device updates. It returns three lists: +// - "events": a list of send-to-device events that should be included in the sync +// - "changes": a list of send-to-device events that should be updated in the database by +// CleanSendToDeviceUpdates +// - "deletions": a list of send-to-device events which have been confirmed as sent and +// can be deleted altogether by CleanSendToDeviceUpdates +// The token supplied should be the current requested sync token, e.g. from the "since" +// parameter. +func (d *SyncServerDatasource) SendToDeviceUpdatesForSync(ctx context.Context, userID, deviceID string, token types.StreamingToken) (events []types.SendToDeviceEvent, changes []types.SendToDeviceNID, deletions []types.SendToDeviceNID, err error) { + return d.Database.SendToDeviceUpdatesForSync(d.dbctx, userID, deviceID, token) +} + +// StoreNewSendForDeviceMessage stores a new send-to-device event for a user's device. +func (d *SyncServerDatasource) StoreNewSendForDeviceMessage(ctx context.Context, streamPos types.StreamPosition, userID, deviceID string, event gomatrixserverlib.SendToDeviceEvent) (types.StreamPosition, error) { + return d.Database.StoreNewSendForDeviceMessage(d.dbctx, streamPos, userID, deviceID, event) +} + +// CleanSendToDeviceUpdates will update or remove any send-to-device updates based on the +// result to a previous call to SendDeviceUpdatesForSync. This is separate as it allows +// SendToDeviceUpdatesForSync to be called multiple times if needed (e.g. before and after +// starting to wait for an incremental sync with timeout). +// The token supplied should be the current requested sync token, e.g. from the "since" +// parameter. +func (d *SyncServerDatasource) CleanSendToDeviceUpdates(ctx context.Context, toUpdate, toDelete []types.SendToDeviceNID, token types.StreamingToken) (err error) { + return d.Database.CleanSendToDeviceUpdates(d.dbctx, toUpdate, toDelete, token) +} + +// SendToDeviceUpdatesWaiting returns true if there are send-to-device updates waiting to be sent. +func (d *SyncServerDatasource) SendToDeviceUpdatesWaiting(ctx context.Context, userID, deviceID string) (bool, error) { + return d.Database.SendToDeviceUpdatesWaiting(d.dbctx, userID, deviceID) +} + +// GetFilter looks up the filter associated with a given local user and filter ID. +// Returns a filter structure. Otherwise returns an error if no such filter exists +// or if there was an error talking to the database. +func (d *SyncServerDatasource) GetFilter(ctx context.Context, localpart string, filterID string) (*gomatrixserverlib.Filter, error) { + return d.Database.GetFilter(d.dbctx, localpart, filterID) +} + +// PutFilter puts the passed filter into the database. +// Returns the filterID as a string. Otherwise returns an error if something +// goes wrong. +func (d *SyncServerDatasource) PutFilter(ctx context.Context, localpart string, filter *gomatrixserverlib.Filter) (string, error) { + return d.Database.PutFilter(d.dbctx, localpart, filter) +} + +// RedactEvent wipes an event in the database and sets the unsigned.redacted_because key to the redaction event +func (d *SyncServerDatasource) RedactEvent(ctx context.Context, redactedEventID string, redactedBecause *gomatrixserverlib.HeaderedEvent) error { + return d.Database.RedactEvent(d.dbctx, redactedEventID, redactedBecause) +} From 7bf2a27319cf9b2bd5106c5e115c0a00166aa218 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 1 Sep 2020 19:17:01 +0100 Subject: [PATCH 14/68] Track partition offsets and only log unsafe for non-selects --- internal/sqlutil/trace.go | 4 ++-- syncapi/storage/sqlite3/syncserver.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/sqlutil/trace.go b/internal/sqlutil/trace.go index dd92daf684..c3892aac52 100644 --- a/internal/sqlutil/trace.go +++ b/internal/sqlutil/trace.go @@ -53,7 +53,7 @@ func (in *traceInterceptor) StmtQueryContext(ctx context.Context, stmt driver.St safe = w.Safe() } } - if safe != "" { + if safe != "" && !strings.HasPrefix(query, "SELECT ") { logrus.Infof("unsafe: %s -- %s", safe, query) } @@ -75,7 +75,7 @@ func (in *traceInterceptor) StmtExecContext(ctx context.Context, stmt driver.Stm safe = w.Safe() } } - if safe != "" { + if safe != "" && !strings.HasPrefix(query, "SELECT ") { logrus.Infof("unsafe: %s -- %s", safe, query) } diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index 135495a503..c7530d3cc2 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -258,3 +258,16 @@ func (d *SyncServerDatasource) PutFilter(ctx context.Context, localpart string, func (d *SyncServerDatasource) RedactEvent(ctx context.Context, redactedEventID string, redactedBecause *gomatrixserverlib.HeaderedEvent) error { return d.Database.RedactEvent(d.dbctx, redactedEventID, redactedBecause) } + +func (d *SyncServerDatasource) PartitionOffsets( + ctx context.Context, topic string, +) ([]sqlutil.PartitionOffset, error) { + return d.PartitionOffsetStatements.PartitionOffsets(d.dbctx, topic) +} + +// SetPartitionOffset implements PartitionStorer +func (d *SyncServerDatasource) SetPartitionOffset( + ctx context.Context, topic string, partition int32, offset int64, +) error { + return d.PartitionOffsetStatements.SetPartitionOffset(d.dbctx, topic, partition, offset) +} From fcdb90c91bb04636bcbb5016a3d197478c3622e3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 1 Sep 2020 19:21:33 +0100 Subject: [PATCH 15/68] Put redactions in the writer goroutine --- syncapi/storage/shared/syncserver.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 3388473ae6..47a472bfa8 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -587,7 +587,10 @@ func (d *Database) RedactEvent(ctx context.Context, redactedEventID string, reda } newEvent := ev.Headered(redactedBecause.RoomVersion) - return d.OutputEvents.UpdateEventJSON(ctx, &newEvent) + err = d.Writer.Do(nil, nil, func(txn *sql.Tx) error { + return d.OutputEvents.UpdateEventJSON(ctx, &newEvent) + }) + return err } // getResponseWithPDUsForCompleteSync creates a response and adds all PDUs needed From 6410b702ce3ef51086a5b4ff16961debdcf6d2f8 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 1 Sep 2020 19:27:21 +0100 Subject: [PATCH 16/68] Update filters on writer goroutine --- syncapi/storage/shared/syncserver.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 47a472bfa8..255fe6b583 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -525,7 +525,13 @@ func (d *Database) GetFilter( func (d *Database) PutFilter( ctx context.Context, localpart string, filter *gomatrixserverlib.Filter, ) (string, error) { - return d.Filter.InsertFilter(ctx, filter, localpart) + var filterID string + var err error + err = d.Writer.Do(nil, nil, func(txn *sql.Tx) error { + filterID, err = d.Filter.InsertFilter(ctx, filter, localpart) + return err + }) + return filterID, err } func (d *Database) IncrementalSync( From 5d7f688fa7368d0e8cc7c71228d36b642e8f7012 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 21:30:03 +0100 Subject: [PATCH 17/68] wrap peek storage in goid hack --- syncapi/storage/sqlite3/syncserver.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index 9f41b9111b..b6d485352c 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -264,6 +264,18 @@ func (d *SyncServerDatasource) RedactEvent(ctx context.Context, redactedEventID return d.Database.RedactEvent(d.dbctx, redactedEventID, redactedBecause) } +// AllPeekingDevicesInRooms returns a map of room ID to a list of all peeking devices. +func (d *SyncServerDatasource) AllPeekingDevicesInRooms(ctx context.Context) (map[string][]types.PeekingDevice, error) { + return d.Database.AllPeekingDevicesInRooms(d.dbctx) +} + +// AddPeek adds a new peek to our DB for a given room by a given user's device. +// Returns an error if there was a problem communicating with the database. +func (d *SyncServerDatasource) AddPeek(ctx context.Context, roomID, userID, deviceID string) (types.StreamPosition, error) { + return d.Database.AddPeek(d.dbctx, roomID, userID, deviceID) +} + + func (d *SyncServerDatasource) PartitionOffsets( ctx context.Context, topic string, ) ([]sqlutil.PartitionOffset, error) { From 6424117852534ebbd47a36c589df2125631fc18e Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 21:31:04 +0100 Subject: [PATCH 18/68] use exclusive writer, and MarkPeeksAsOld more efficiently --- syncapi/storage/shared/syncserver.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 1b3d6bd8f1..718162b36f 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -1005,7 +1005,7 @@ func (d *Database) getStateDeltas( } // find out which rooms this user is peeking, if any. - // We do this before joins so joins overwrite peeks + // We do this before joins so any peeks get overwritten peeks, err := d.Peeks.SelectPeeks(ctx, userID, device.ID) if err != nil { return nil, nil, err @@ -1013,6 +1013,7 @@ func (d *Database) getStateDeltas( // add peek blocks peeking := make(map[string]bool) + newPeeks := false for _, peek := range peeks { peeking[peek.RoomID] = true if peek.New { @@ -1023,6 +1024,7 @@ func (d *Database) getStateDeltas( return nil, nil, err } state[peek.RoomID] = s + newPeeks = true } deltas = append(deltas, stateDelta{ @@ -1032,8 +1034,10 @@ func (d *Database) getStateDeltas( }) } - if len(peeks) > 0 { - err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) + if newPeeks { + err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error { + return d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) + }) if err != nil { return nil, nil, err } @@ -1051,7 +1055,11 @@ func (d *Database) getStateDeltas( if membership == gomatrixserverlib.Join { if peeking[roomID] { // we automatically cancel our peeks when we join a room - _, err = d.Peeks.DeletePeeks(ctx, txn, roomID, userID) + err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error { + // XXX: is it correct that we're discarding the streamid here? + _, err = d.Peeks.DeletePeeks(ctx, txn, roomID, userID) + return err + }) if err != nil { return nil, nil, err } @@ -1130,7 +1138,11 @@ func (d *Database) getStateDeltasForFullStateSync( } // Add full states for all peeking rooms + newPeeks := false for _, peek := range peeks { + if peek.New { + newPeeks = true + } s, stateErr := d.currentStateStreamEventsForRoom(ctx, txn, peek.RoomID, stateFilter) if stateErr != nil { return nil, nil, stateErr @@ -1142,8 +1154,10 @@ func (d *Database) getStateDeltasForFullStateSync( }) } - if len(peeks) > 0 { - err := d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) + if newPeeks { + err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error { + return d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) + }) if err != nil { return nil, nil, err } From 85bce11964b77ee6ccce35fd5676c8de40acbac2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 21:59:11 +0100 Subject: [PATCH 19/68] don't log ascii in binary at sql trace... --- internal/sqlutil/trace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sqlutil/trace.go b/internal/sqlutil/trace.go index c3892aac52..376e6f4945 100644 --- a/internal/sqlutil/trace.go +++ b/internal/sqlutil/trace.go @@ -96,7 +96,7 @@ func (in *traceInterceptor) RowsNext(c context.Context, rows driver.Rows, dest [ b := strings.Builder{} for i, val := range dest { - b.WriteString(fmt.Sprintf("%v", val)) + b.WriteString(fmt.Sprintf("%q", val)) if i+1 <= len(dest)-1 { b.WriteString(" | ") } From 75b91ac9e5d2eedd805c12581de40580fcfb9984 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 1 Sep 2020 21:59:35 +0100 Subject: [PATCH 20/68] strip out empty roomd deltas --- syncapi/storage/shared/syncserver.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 718162b36f..1d2fa12fc4 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -856,6 +856,12 @@ func (d *Database) addRoomDeltaToResponse( return err } + // XXX: should we ever get this far if we have no recent events or state in this room? + // in practice we do for peeks, but possibly not joins? + if len(recentEvents) == 0 && len(delta.stateEvents) == 0 { + return nil + } + switch delta.membership { case gomatrixserverlib.Join: jr := types.NewJoinResponse() From b6cc4417cc0e5bf19f3a871a2664445b08ac55d3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 2 Sep 2020 10:07:49 +0100 Subject: [PATCH 21/68] re-add txn to SelectPeeks --- syncapi/storage/shared/syncserver.go | 6 +++--- syncapi/storage/sqlite3/peeks_table.go | 4 ++-- syncapi/storage/tables/interface.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 1d2fa12fc4..f11b00b333 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -675,7 +675,7 @@ func (d *Database) getResponseWithPDUsForCompleteSync( } // Add peeked rooms. - peeks, err := d.Peeks.SelectPeeks(ctx, userID, deviceID) + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, deviceID) if err != nil { return } @@ -1012,7 +1012,7 @@ func (d *Database) getStateDeltas( // find out which rooms this user is peeking, if any. // We do this before joins so any peeks get overwritten - peeks, err := d.Peeks.SelectPeeks(ctx, userID, device.ID) + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } @@ -1122,7 +1122,7 @@ func (d *Database) getStateDeltasForFullStateSync( return nil, nil, err } - peeks, err := d.Peeks.SelectPeeks(ctx, userID, device.ID) + peeks, err := d.Peeks.SelectPeeks(ctx, txn, userID, device.ID) if err != nil { return nil, nil, err } diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index 8fb74341b9..c013a6350e 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -136,9 +136,9 @@ func (s *peekStatements) DeletePeeks( } func (s *peekStatements) SelectPeeks( - ctx context.Context, userID, deviceID string, + ctx context.Context, txn *sql.Tx, userID, deviceID string, ) (peeks []types.Peek, err error) { - rows, err := s.selectPeeksStmt.QueryContext(ctx, userID, deviceID) + rows, err := sqlutil.TxStmt(txn, s.selectPeeksStmt).QueryContext(ctx, userID, deviceID) if err != nil { return } diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 3ca374e497..1f8663b9d7 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -43,7 +43,7 @@ type Peeks interface { InsertPeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeek(ctx context.Context, txn *sql.Tx, roomID, userID, deviceID string) (streamPos types.StreamPosition, err error) DeletePeeks(ctx context.Context, txn *sql.Tx, roomID, userID string) (streamPos types.StreamPosition, err error) - SelectPeeks(ctxt context.Context, userID, deviceID string) (peeks []types.Peek, err error) + SelectPeeks(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (peeks []types.Peek, err error) SelectPeekingDevices(ctxt context.Context) (peekingDevices map[string][]types.PeekingDevice, err error) MarkPeeksAsOld(ctxt context.Context, txn *sql.Tx, userID, deviceID string) (err error) } From f6af6569454c5aab0a588201300f861eee60157b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 2 Sep 2020 10:08:10 +0100 Subject: [PATCH 22/68] re-add accidentally deleted field --- syncapi/sync/notifier.go | 1 + 1 file changed, 1 insertion(+) diff --git a/syncapi/sync/notifier.go b/syncapi/sync/notifier.go index 6130023237..fc8482037e 100644 --- a/syncapi/sync/notifier.go +++ b/syncapi/sync/notifier.go @@ -55,6 +55,7 @@ func NewNotifier(pos types.StreamingToken) *Notifier { roomIDToPeekingDevices: make(map[string]peekingDeviceSet), userDeviceStreams: make(map[string]map[string]*UserDeviceStream), streamLock: &sync.Mutex{}, + lastCleanUpTime: time.Now(), } } From eda84cd915e7e9bb5481e495d2caba17c32d59f9 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 3 Sep 2020 22:15:30 +0100 Subject: [PATCH 23/68] reject peeks for non-worldreadable rooms --- roomserver/internal/perform_peek.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/roomserver/internal/perform_peek.go b/roomserver/internal/perform_peek.go index 4f080737ef..670752b0a4 100644 --- a/roomserver/internal/perform_peek.go +++ b/roomserver/internal/perform_peek.go @@ -16,12 +16,14 @@ package internal import ( "context" + "encoding/json" "fmt" "strings" fsAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) @@ -144,6 +146,30 @@ func (r *RoomserverInternalAPI) performPeekRoomByID( req.ServerNames = append(req.ServerNames, domain) } + // If this room isn't world_readable, we reject. + // XXX: would be nicer to call this with NIDs + // XXX: we should probably factor out history_visibility checks into a common utility method somewhere + // which handles the default value etc. + var worldReadable = false + ev, err := r.DB.GetStateEvent(ctx, roomID, "m.room.history_visibility", "") + if ev != nil { + content := map[string]string{} + if err = json.Unmarshal(ev.Content(), &content); err != nil { + util.GetLogger(ctx).WithError(err).Error("json.Unmarshal for history visibility failed") + return + } + if visibility, ok := content["history_visibility"]; ok { + worldReadable = visibility == "world_readable" + } + } + + if !worldReadable { + return "", &api.PerformError{ + Code: api.PerformErrorNotAllowed, + Msg: "Room is not world-readable", + } + } + // TODO: handle federated peeks err = r.WriteOutputEvents(roomID, []api.OutputEvent{ From da3742c8e2fff5974b2443d175ee05df94552477 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 3 Sep 2020 22:39:12 +0100 Subject: [PATCH 24/68] move perform_peek --- roomserver/internal/{ => perform}/perform_peek.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename roomserver/internal/{ => perform}/perform_peek.go (100%) diff --git a/roomserver/internal/perform_peek.go b/roomserver/internal/perform/perform_peek.go similarity index 100% rename from roomserver/internal/perform_peek.go rename to roomserver/internal/perform/perform_peek.go From 3c5e079b870be502fee4cf715290628403769ddf Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 3 Sep 2020 23:06:14 +0100 Subject: [PATCH 25/68] fix package --- roomserver/internal/perform/perform_peek.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 670752b0a4..7c073d60ef 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package internal +package perform import ( "context" From 994cc18b9c02163df3169098eea6877f59ba3493 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 3 Sep 2020 23:18:39 +0100 Subject: [PATCH 26/68] correctly refactor perform_peek --- roomserver/internal/api.go | 8 +++++++ roomserver/internal/perform/perform_peek.go | 25 ++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index bdea650ea2..b5ef59352f 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -22,6 +22,7 @@ type RoomserverInternalAPI struct { *query.Queryer *perform.Inviter *perform.Joiner + *perform.Peeker *perform.Leaver *perform.Publisher *perform.Backfiller @@ -81,6 +82,13 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen FSAPI: r.fsAPI, Inputer: r.Inputer, } + r.Peeker = &perform.Peeker{ + ServerName: r.Cfg.Matrix.ServerName, + Cfg: r.Cfg, + DB: r.DB, + FSAPI: r.fsAPI, + Inputer: r.Inputer, + } r.Leaver = &perform.Leaver{ Cfg: r.Cfg, DB: r.DB, diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 7c073d60ef..7a212f9afd 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -20,15 +20,28 @@ import ( "fmt" "strings" + "github.com/matrix-org/dendrite/internal/config" fsAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/internal/input" + "github.com/matrix-org/dendrite/roomserver/storage" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/sirupsen/logrus" ) +type Peeker struct { + ServerName gomatrixserverlib.ServerName + Cfg *config.RoomServer + FSAPI fsAPI.FederationSenderInternalAPI + DB storage.Database + + Inputer *input.Inputer +} + + // PerformPeek handles peeking into matrix rooms, including over federation by talking to the federationsender. -func (r *RoomserverInternalAPI) PerformPeek( +func (r *Peeker) PerformPeek( ctx context.Context, req *api.PerformPeekRequest, res *api.PerformPeekResponse, @@ -47,7 +60,7 @@ func (r *RoomserverInternalAPI) PerformPeek( res.RoomID = roomID } -func (r *RoomserverInternalAPI) performPeek( +func (r *Peeker) performPeek( ctx context.Context, req *api.PerformPeekRequest, ) (string, error) { @@ -77,7 +90,7 @@ func (r *RoomserverInternalAPI) performPeek( } } -func (r *RoomserverInternalAPI) performPeekRoomByAlias( +func (r *Peeker) performPeekRoomByAlias( ctx context.Context, req *api.PerformPeekRequest, ) (string, error) { @@ -99,7 +112,7 @@ func (r *RoomserverInternalAPI) performPeekRoomByAlias( ServerName: domain, // the server to ask } dirRes := fsAPI.PerformDirectoryLookupResponse{} - err = r.fsAPI.PerformDirectoryLookup(ctx, &dirReq, &dirRes) + err = r.FSAPI.PerformDirectoryLookup(ctx, &dirReq, &dirRes) if err != nil { logrus.WithError(err).Errorf("error looking up alias %q", req.RoomIDOrAlias) return "", fmt.Errorf("Looking up alias %q over federation failed: %w", req.RoomIDOrAlias, err) @@ -124,7 +137,7 @@ func (r *RoomserverInternalAPI) performPeekRoomByAlias( return r.performPeekRoomByID(ctx, req) } -func (r *RoomserverInternalAPI) performPeekRoomByID( +func (r *Peeker) performPeekRoomByID( ctx context.Context, req *api.PerformPeekRequest, ) (roomID string, err error) { @@ -172,7 +185,7 @@ func (r *RoomserverInternalAPI) performPeekRoomByID( // TODO: handle federated peeks - err = r.WriteOutputEvents(roomID, []api.OutputEvent{ + err = r.Inputer.WriteOutputEvents(roomID, []api.OutputEvent{ { Type: api.OutputTypeNewPeek, NewPeek: &api.OutputNewPeek{ From c1f1fcdc67a92eb990904b49217ad523a352817b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 4 Sep 2020 01:38:22 +0100 Subject: [PATCH 27/68] WIP of implementing MSC2444 --- federationsender/api/api.go | 16 ++ federationsender/internal/perform.go | 152 +++++++++++++++- federationsender/internal/perform/join.go | 15 +- federationsender/storage/interface.go | 5 + federationsender/storage/shared/storage.go | 20 ++ .../storage/sqlite3/remote_peeks_table.go | 171 ++++++++++++++++++ federationsender/storage/sqlite3/storage.go | 5 + federationsender/storage/tables/interface.go | 8 + federationsender/types/types.go | 9 + roomserver/internal/perform/perform_peek.go | 22 ++- 10 files changed, 418 insertions(+), 5 deletions(-) create mode 100644 federationsender/storage/sqlite3/remote_peeks_table.go diff --git a/federationsender/api/api.go b/federationsender/api/api.go index 655d1d1035..bdc894ed17 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -57,6 +57,12 @@ type FederationSenderInternalAPI interface { request *PerformJoinRequest, response *PerformJoinResponse, ) + // Handle an instruction to peek a room on a remote server. + PerformJoin( + ctx context.Context, + request *PerformPeekRequest, + response *PerformPeekResponse, + ) // Handle an instruction to make_leave & send_leave with a remote server. PerformLeave( ctx context.Context, @@ -105,6 +111,16 @@ type PerformJoinResponse struct { LastError *gomatrix.HTTPError } +type PerformPeekRequest struct { + RoomID string `json:"room_id"` + // The sorted list of servers to try. Servers will be tried sequentially, after de-duplication. + ServerNames types.ServerNames `json:"server_names"` +} + +type PerformPeekResponse struct { + LastError *gomatrix.HTTPError +} + type PerformLeaveRequest struct { RoomID string `json:"room_id"` UserID string `json:"user_id"` diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index da8d41a74e..0a2ff824a6 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -186,7 +186,7 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( // Check that the send_join response was valid. joinCtx := perform.JoinContext(r.federation, r.keyRing) if err = joinCtx.CheckSendJoinResponse( - ctx, event, serverName, respMakeJoin, respSendJoin, + ctx, event, serverName, respSendJoin, ); err != nil { return fmt.Errorf("joinCtx.CheckSendJoinResponse: %w", err) } @@ -206,6 +206,156 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( return nil } +// PerformPeekRequest implements api.FederationSenderInternalAPI +func (r *FederationSenderInternalAPI) PerformPeek( + ctx context.Context, + request *api.PerformPeekRequest, + response *api.PerformPeekResponse, +) (err error) { + // Look up the supported room versions. + var supportedVersions []gomatrixserverlib.RoomVersion + for version := range version.SupportedRoomVersions() { + supportedVersions = append(supportedVersions, version) + } + + // Deduplicate the server names we were provided but keep the ordering + // as this encodes useful information about which servers are most likely + // to respond. + seenSet := make(map[gomatrixserverlib.ServerName]bool) + var uniqueList []gomatrixserverlib.ServerName + for _, srv := range request.ServerNames { + if seenSet[srv] { + continue + } + seenSet[srv] = true + uniqueList = append(uniqueList, srv) + } + request.ServerNames = uniqueList + + // Try each server that we were provided until we land on one that + // successfully completes the peek + var lastErr error + for _, serverName := range request.ServerNames { + if err := r.performPeekUsingServer( + ctx, + request.RoomID, + serverName, + supportedVersions, + ); err != nil { + logrus.WithError(err).WithFields(logrus.Fields{ + "server_name": serverName, + "room_id": request.RoomID, + }).Warnf("Failed to peek room through server") + lastErr = err + continue + } + + // We're all good. + return + } + + // If we reach here then we didn't complete a peek for some reason. + var httpErr gomatrix.HTTPError + if ok := errors.As(lastErr, &httpErr); ok { + httpErr.Message = string(httpErr.Contents) + // Clear the wrapped error, else serialising to JSON (in polylith mode) will fail + httpErr.WrappedError = nil + response.LastError = &httpErr + } else { + response.LastError = &gomatrix.HTTPError{ + Code: 0, + WrappedError: nil, + Message: lastErr.Error(), + } + } + + logrus.Errorf( + "failed to peek room %q through %d server(s): last error %s", + request.RoomID, len(request.ServerNames), lastErr, + ) +} + +func (r *FederationSenderInternalAPI) performPeekUsingServer( + ctx context.Context, + roomID string, + serverName gomatrixserverlib.ServerName, + supportedVersions []gomatrixserverlib.RoomVersion, +) error { + // check whether we're peeking already to try to avoid needlessly + // re-peeking on the server. we don't need a transaction for this, + // given this is a nice-to-have. + remotePeek, err := r.db.GetRemotePeek(ctx, serverName, roomID) + if err != nil { + return err + } + renewing := false + if remotePeek != nil { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + if (nowMilli > remotePeek.RenewedTimestamp + remotePeek.RenewalInterval) { + logrus.Infof("stale remote peek to %s for %s already exists; renewing", serverName, roomID) + renewing = true + } + else { + logrus.Infof("live remote peek to %s for %s already exists", serverName, roomID) + return nil + } + } + + // create a unique ID for this peek. + // for now we just use the room ID again. In future, if we ever + // support concurrent peeks to the same room with different filters + // then we would need to disambiguate further. + peekID := roomID + + // Try to perform a /peek using the information supplied in the + // request. + respPeek, err := r.federation.Peek( + ctx, + serverName, + roomID, + peekID, + supportedVersions, + ) + if err != nil { + r.statistics.ForServer(serverName).Failure() + return fmt.Errorf("r.federation.MakePeek: %w", err) + } + r.statistics.ForServer(serverName).Success() + + // Work out if we support the room version that has been supplied in + // the make_peek response. + if respPeek.RoomVersion == "" { + respPeek.RoomVersion = gomatrixserverlib.RoomVersionV1 + } + if _, err = respPeek.RoomVersion.EventFormat(); err != nil { + return fmt.Errorf("respPeek.RoomVersion.EventFormat: %w", err) + } + + // If we've got this far, the remote server is peeking. + if renewing { + if err = r.db.RenewRemotePeek(ctx, serverName, roomID, respPeek.RenewalInterval); err != nil { + return err + } + } + else { + if err = r.db.AddRemotePeek(ctx, serverName, roomID, respPeek.RenewalInterval); err != nil { + return err + } + } + + respState := respPeek.ToRespState() + // Send the newly returned state to the roomserver to update our local view. + if err = roomserverAPI.SendEventWithState( + ctx, r.rsAPI, + &respState, + event.Headered(respPeek.RoomVersion), nil, + ); err != nil { + return fmt.Errorf("r.producer.SendEventWithState: %w", err) + } + + return nil +} + // PerformLeaveRequest implements api.FederationSenderInternalAPI func (r *FederationSenderInternalAPI) PerformLeave( ctx context.Context, diff --git a/federationsender/internal/perform/join.go b/federationsender/internal/perform/join.go index 9a505d15b0..22840c90d2 100644 --- a/federationsender/internal/perform/join.go +++ b/federationsender/internal/perform/join.go @@ -1,3 +1,17 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package perform import ( @@ -28,7 +42,6 @@ func (r joinContext) CheckSendJoinResponse( ctx context.Context, event gomatrixserverlib.Event, server gomatrixserverlib.ServerName, - respMakeJoin gomatrixserverlib.RespMakeJoin, respSendJoin gomatrixserverlib.RespSendJoin, ) error { // A list of events that we have retried, if they were not included in diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index 734b368fe8..f95db13cc0 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -53,4 +53,9 @@ type Database interface { AddServerToBlacklist(serverName gomatrixserverlib.ServerName) error RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) + + AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error + RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error + GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID string) (types.RemotePeek, error) + GetRemotePeeks(roomID string) ([]types.RemotePeek, error) } diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 4e34725902..f2c4ab0986 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -163,3 +163,23 @@ func (d *Database) RemoveServerFromBlacklist(serverName gomatrixserverlib.Server func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) { return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName) } + +func (d *Database) AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.RemotePeeks.InsertRemotePeek(context.TODO(), txn, serverName, roomID, renewalInterval) + }) +} + +func (d *Database) RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.RemotePeeks.RenewRemotePeek(context.TODO(), txn, serverName, roomID, renewalInterval) + }) +} + +func (d *Database) GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID string) (types.RemotePeek, error) { + return d.RemotePeeks.SelectRemotePeek(context.TODO(), serverName, roomID) +} + +func (d *Database) GetRemotePeeks(roomID string) ([]types.RemotePeek, error) { + return d.RemotePeeks.SelectRemotePeeks(context.TODO(), roomID) +} diff --git a/federationsender/storage/sqlite3/remote_peeks_table.go b/federationsender/storage/sqlite3/remote_peeks_table.go new file mode 100644 index 0000000000..bcb952e45b --- /dev/null +++ b/federationsender/storage/sqlite3/remote_peeks_table.go @@ -0,0 +1,171 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const remotePeeksSchema = ` +CREATE TABLE IF NOT EXISTS federationsender_remote_peeks ( + room_id TEXT NOT NULL, + server_name TEXT NOT NULL, + creation_ts INTEGER NOT NULL, + renewed_ts INTEGER NOT NULL, + renewal_interval INTEGER NOT NULL, + UNIQUE (room_id, server_name) +); +` + +const insertRemotePeekSQL = "" + + "INSERT INTO federationsender_remote_peeks (room_id, server_name, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5)" + +const selectRemotePeekSQL = "" + + "SELECT room_id, server_name, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2" + +const selectRemotePeeksSQL = "" + + "SELECT room_id, server_name, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1" + +const renewRemotePeekSQL = "" + + "UPDATE federationsender_remote_peeks SET renewed_ts=$3, renewal_interval=$4 WHERE room_id = $1 and server_name = $2" + +const deleteRemotePeekSQL = "" + + "DELETE FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2" + +const deleteRemotePeeksSQL = "" + + "DELETE FROM federationsender_remote_peeks WHERE room_id = $1" + +type remotePeeksStatements struct { + db *sql.DB + insertRemotePeekStmt *sql.Stmt + selectRemotePeekStmt *sql.Stmt + selectRemotePeeksStmt *sql.Stmt + renewRemotePeekStmt *sql.Stmt + deleteRemotePeekStmt *sql.Stmt + deleteRemotePeeksStmt *sql.Stmt +} + +func NewSQLiteRemotePeeksTable(db *sql.DB) (s *remotePeeksStatements, err error) { + s = &remotePeeksStatements{ + db: db, + } + _, err = db.Exec(remotePeeksSchema) + if err != nil { + return + } + + if s.insertRemotePeekStmt, err = db.Prepare(insertRemotePeekSQL); err != nil { + return + } + if s.selectRemotePeekStmt, err = db.Prepare(selectRemotePeekSQL); err != nil { + return + } + if s.selectRemotePeeksStmt, err = db.Prepare(selectRemotePeeksSQL); err != nil { + return + } + if s.renewRemotePeekStmt, err = db.Prepare(renewRemotePeekSQL); err != nil { + return + } + if s.deleteRemotePeeksStmt, err = db.Prepare(deleteRemotePeeksSQL); err != nil { + return + } + if s.deleteRemotePeekStmt, err = db.Prepare(deleteRemotePeekSQL); err != nil { + return + } + return +} + +func (s *remotePeeksStatements) InsertRemotePeek( + ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + stmt := sqlutil.TxStmt(txn, s.insertRemotePeekStmt) + _, err := stmt.ExecContext(ctx, roomID, serverName, nowMilli, nowMilli, renewalInterval) + return +} + +func (s *remotePeeksStatements) RenewRemotePeek( + ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err := sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, roomID, serverName, nowMilli, renewalInterval) + return +} + + +func (s *remotePeeksStatements) SelectRemotePeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID string, +) (remotePeek types.RemotePeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectRemotePeek: rows.close() failed") + remotePeek := types.RemotePeek{} + if err = rows.Scan( + &remotePeek.RoomID, + &remotePeek.ServerName, + &remotePeek.CreationTimestamp, + &remotePeek.RenewTimestamp, + &remotePeek.RenewalInterval, + ); err != nil { + return + } + return remotePeek, rows.Err() +} + +func (s *remotePeeksStatements) SelectRemotePeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (remotePeeks []types.RemotePeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectRemotePeeks: rows.close() failed") + + for rows.Next() { + remotePeek := types.RemotePeek{} + if err = rows.Scan( + &remotePeek.RoomID, + &remotePeek.ServerName, + &remotePeek.CreationTimestamp, + &remotePeek.RenewTimestamp, + &remotePeek.RenewalInterval, + ); err != nil { + return + } + remotePeeks = append(remotePeeks, remotePeek) + } + + return remotePeeks, rows.Err() +} + +func (s *remotePeeksStatements) DeleteRemotePeek( + ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, +) (err error) { + _, err := sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName) + return +} + +func (s *remotePeeksStatements) DeleteRemotePeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (err error) { + _, err := sqlutil.TxStmt(txn, s.deleteRemotePeeksStmt).ExecContext(ctx, roomID) + return +} diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index ba467f026a..c0317b25d3 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -65,6 +65,10 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { if err != nil { return nil, err } + remotePeeks, err := NewSQLiteRemotePeeksTable(d.db) + if err != nil { + return nil, err + } d.Database = shared.Database{ DB: d.db, Writer: d.writer, @@ -74,6 +78,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { FederationSenderQueueJSON: queueJSON, FederationSenderRooms: rooms, FederationSenderBlacklist: blacklist, + FederationSenderRemotePeeks: remotePeeks, } if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil { return nil, err diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index c6f8a2d52f..61c9208b4f 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -67,3 +67,11 @@ type FederationSenderBlacklist interface { SelectBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) (bool, error) DeleteBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) error } + +type FederationSenderRemotePeeks interface { + InsertRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int) (err error) + RenewRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int) (err error) + SelectRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (remotePeeks []types.RemotePeek, err error) + DeleteRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName) (err error) + DeleteRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) +} diff --git a/federationsender/types/types.go b/federationsender/types/types.go index 398d326777..e8825d7787 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -49,3 +49,12 @@ func (e EventIDMismatchError) Error() string { e.DatabaseID, e.RoomServerID, ) } + +type RemotePeek { + PeekID string + RoomID string + ServerName gomatrixserverlib.ServerName + CreatedTimestamp UnixMs + RenewedTimestamp UnixMs + RenewalInterval UnixMs +} \ No newline at end of file diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 7a212f9afd..8f61ba1eb8 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -152,11 +152,27 @@ func (r *Peeker) performPeekRoomByID( } } - // If the server name in the room ID isn't ours then it's a - // possible candidate for finding the room via federation. Add - // it to the list of servers to try. + // handle federated peeks if domain != r.Cfg.Matrix.ServerName { + // If the server name in the room ID isn't ours then it's a + // possible candidate for finding the room via federation. Add + // it to the list of servers to try. req.ServerNames = append(req.ServerNames, domain) + + // Try peeking by all of the supplied server names. + fedReq := fsAPI.PerformPeekRequest{ + RoomID: req.RoomIDOrAlias, // the room ID to try and peek + ServerNames: req.ServerNames, // the servers to try peeking via + } + fedRes := fsAPI.PerformPeekResponse{} + r.FSAPI.PerformPeek(ctx, &fedReq, &fedRes) + if fedRes.LastError != nil { + return &api.PerformError{ + Code: api.PerformErrRemote, + Msg: fedRes.LastError.Message, + RemoteCode: fedRes.LastError.Code, + } + } } // If this room isn't world_readable, we reject. From 4ca2cf49f8f213ac1efbad5a5a9c616a5fa8aa3d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 4 Sep 2020 01:44:38 +0100 Subject: [PATCH 28/68] typo --- federationsender/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federationsender/api/api.go b/federationsender/api/api.go index bdc894ed17..1dec0aa864 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -58,7 +58,7 @@ type FederationSenderInternalAPI interface { response *PerformJoinResponse, ) // Handle an instruction to peek a room on a remote server. - PerformJoin( + PerformPeek( ctx context.Context, request *PerformPeekRequest, response *PerformPeekResponse, From 98cf8986a43390d9001d69e2ab129b4cec44ddaf Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 19:44:09 +0100 Subject: [PATCH 29/68] Revert "Merge branch 'kegan/HACK-goid-sqlite-db-is-locked' into matthew/peeking" This reverts commit 3cebd8dbfbccdf82b7930b7b6eda92095ca6ef41, reversing changes made to ed4b3a58a7855acc43530693cc855b439edf9c7c. --- internal/sqlutil/trace.go | 47 +------ internal/sqlutil/writer.go | 2 - internal/sqlutil/writer_dummy.go | 4 - internal/sqlutil/writer_exclusive.go | 30 +---- syncapi/storage/sqlite3/syncserver.go | 181 +------------------------- 5 files changed, 6 insertions(+), 258 deletions(-) diff --git a/internal/sqlutil/trace.go b/internal/sqlutil/trace.go index 376e6f4945..235296388c 100644 --- a/internal/sqlutil/trace.go +++ b/internal/sqlutil/trace.go @@ -31,33 +31,16 @@ import ( ) var tracingEnabled = os.Getenv("DENDRITE_TRACE_SQL") == "1" -var dbToWriter map[string]Writer -var CtxDBInstance = "db_instance" -var instCount = 0 type traceInterceptor struct { sqlmw.NullInterceptor - conn driver.Conn } func (in *traceInterceptor) StmtQueryContext(ctx context.Context, stmt driver.StmtQueryContext, query string, args []driver.NamedValue) (driver.Rows, error) { startedAt := time.Now() rows, err := stmt.QueryContext(ctx, args) - key := ctx.Value(CtxDBInstance) - var safe string - if key != nil { - w := dbToWriter[key.(string)] - if w == nil { - safe = fmt.Sprintf("no writer for key %s", key) - } else { - safe = w.Safe() - } - } - if safe != "" && !strings.HasPrefix(query, "SELECT ") { - logrus.Infof("unsafe: %s -- %s", safe, query) - } - logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).WithField("safe", safe).Debug("executed sql query ", query, " args: ", args) + logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).Debug("executed sql query ", query, " args: ", args) return rows, err } @@ -65,21 +48,8 @@ func (in *traceInterceptor) StmtQueryContext(ctx context.Context, stmt driver.St func (in *traceInterceptor) StmtExecContext(ctx context.Context, stmt driver.StmtExecContext, query string, args []driver.NamedValue) (driver.Result, error) { startedAt := time.Now() result, err := stmt.ExecContext(ctx, args) - key := ctx.Value(CtxDBInstance) - var safe string - if key != nil { - w := dbToWriter[key.(string)] - if w == nil { - safe = fmt.Sprintf("no writer for key %s", key) - } else { - safe = w.Safe() - } - } - if safe != "" && !strings.HasPrefix(query, "SELECT ") { - logrus.Infof("unsafe: %s -- %s", safe, query) - } - logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).WithField("safe", safe).Debug("executed sql query ", query, " args: ", args) + logrus.WithField("duration", time.Since(startedAt)).WithField(logrus.ErrorKey, err).Debug("executed sql query ", query, " args: ", args) return result, err } @@ -105,18 +75,6 @@ func (in *traceInterceptor) RowsNext(c context.Context, rows driver.Rows, dest [ return err } -func OpenWithWriter(dbProperties *config.DatabaseOptions, w Writer) (*sql.DB, context.Context, error) { - db, err := Open(dbProperties) - if err != nil { - return nil, nil, err - } - instCount++ - ctxVal := fmt.Sprintf("%d", instCount) - dbToWriter[ctxVal] = w - ctx := context.WithValue(context.TODO(), CtxDBInstance, ctxVal) - return db, ctx, nil -} - // Open opens a database specified by its database driver name and a driver-specific data source name, // usually consisting of at least a database name and connection information. Includes tracing driver // if DENDRITE_TRACE_SQL=1 @@ -160,5 +118,4 @@ func Open(dbProperties *config.DatabaseOptions) (*sql.DB, error) { func init() { registerDrivers() - dbToWriter = make(map[string]Writer) } diff --git a/internal/sqlutil/writer.go b/internal/sqlutil/writer.go index f966e250cf..5d93fef4db 100644 --- a/internal/sqlutil/writer.go +++ b/internal/sqlutil/writer.go @@ -43,6 +43,4 @@ type Writer interface { // Queue up one or more database write operations within the // provided function to be executed when it is safe to do so. Do(db *sql.DB, txn *sql.Tx, f func(txn *sql.Tx) error) error - - Safe() string } diff --git a/internal/sqlutil/writer_dummy.go b/internal/sqlutil/writer_dummy.go index fbca3e773c..f426c2bc3d 100644 --- a/internal/sqlutil/writer_dummy.go +++ b/internal/sqlutil/writer_dummy.go @@ -26,7 +26,3 @@ func (w *DummyWriter) Do(db *sql.DB, txn *sql.Tx, f func(txn *sql.Tx) error) err return f(txn) } } - -func (w *DummyWriter) Safe() string { - return "DummyWriter" -} diff --git a/internal/sqlutil/writer_exclusive.go b/internal/sqlutil/writer_exclusive.go index 933661958b..002bc32cf3 100644 --- a/internal/sqlutil/writer_exclusive.go +++ b/internal/sqlutil/writer_exclusive.go @@ -3,10 +3,6 @@ package sqlutil import ( "database/sql" "errors" - "fmt" - "runtime" - "strconv" - "strings" "go.uber.org/atomic" ) @@ -16,9 +12,8 @@ import ( // contend on database locks in, e.g. SQLite. Only one task will run // at a time on a given ExclusiveWriter. type ExclusiveWriter struct { - running atomic.Bool - todo chan transactionWriterTask - writerID int + running atomic.Bool + todo chan transactionWriterTask } func NewExclusiveWriter() Writer { @@ -35,15 +30,6 @@ type transactionWriterTask struct { wait chan error } -func (w *ExclusiveWriter) Safe() string { - a := goid() - b := w.writerID - if a == b { - return "" - } - return fmt.Sprintf("%v != %v", a, b) -} - // Do queues a task to be run by a TransactionWriter. The function // provided will be ran within a transaction as supplied by the // txn parameter if one is supplied, and if not, will take out a @@ -74,7 +60,6 @@ func (w *ExclusiveWriter) run() { if !w.running.CAS(false, true) { return } - w.writerID = goid() defer w.running.Store(false) for task := range w.todo { if task.db != nil && task.txn != nil { @@ -89,14 +74,3 @@ func (w *ExclusiveWriter) run() { close(task.wait) } } - -func goid() int { - var buf [64]byte - n := runtime.Stack(buf[:], false) - idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] - id, err := strconv.Atoi(idField) - if err != nil { - panic(fmt.Sprintf("cannot get goroutine id: %v", err)) - } - return id -} diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index b6d485352c..2cbab26e4f 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -16,7 +16,6 @@ package sqlite3 import ( - "context" "database/sql" // Import the sqlite3 package @@ -25,11 +24,7 @@ import ( "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/syncapi/storage/shared" - "github.com/matrix-org/dendrite/syncapi/types" - userapi "github.com/matrix-org/dendrite/userapi/api" - "github.com/matrix-org/gomatrixserverlib" ) // SyncServerDatasource represents a sync server datasource which manages @@ -40,7 +35,6 @@ type SyncServerDatasource struct { writer sqlutil.Writer sqlutil.PartitionOffsetStatements streamID streamIDStatements - dbctx context.Context } // NewDatabase creates a new sync server database @@ -48,12 +42,10 @@ type SyncServerDatasource struct { func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, error) { var d SyncServerDatasource var err error - d.writer = sqlutil.NewExclusiveWriter() - d.db, d.dbctx, err = sqlutil.OpenWithWriter(dbProperties, d.writer) - if err != nil { + if d.db, err = sqlutil.Open(dbProperties); err != nil { return nil, err } - + d.writer = sqlutil.NewExclusiveWriter() if err = d.prepare(); err != nil { return nil, err } @@ -119,172 +111,3 @@ func (d *SyncServerDatasource) prepare() (err error) { } return nil } - -func (d *SyncServerDatasource) Events(ctx context.Context, eventIDs []string) ([]gomatrixserverlib.HeaderedEvent, error) { - return d.Database.Events(d.dbctx, eventIDs) -} -func (d *SyncServerDatasource) WriteEvent(ctx context.Context, ev *gomatrixserverlib.HeaderedEvent, addStateEvents []gomatrixserverlib.HeaderedEvent, - addStateEventIDs []string, removeStateEventIDs []string, transactionID *api.TransactionID, excludeFromSync bool) (types.StreamPosition, error) { - return d.Database.WriteEvent(d.dbctx, ev, addStateEvents, addStateEventIDs, removeStateEventIDs, transactionID, excludeFromSync) -} -func (d *SyncServerDatasource) AllJoinedUsersInRooms(ctx context.Context) (map[string][]string, error) { - return d.Database.AllJoinedUsersInRooms(d.dbctx) -} - -func (d *SyncServerDatasource) GetStateEvent(ctx context.Context, roomID, evType, stateKey string) (*gomatrixserverlib.HeaderedEvent, error) { - return d.Database.GetStateEvent(d.dbctx, roomID, evType, stateKey) -} - -// GetStateEventsForRoom fetches the state events for a given room. -// Returns an empty slice if no state events could be found for this room. -// Returns an error if there was an issue with the retrieval. -func (d *SyncServerDatasource) GetStateEventsForRoom(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter) (stateEvents []gomatrixserverlib.HeaderedEvent, err error) { - return d.Database.GetStateEventsForRoom(d.dbctx, roomID, stateFilterPart) -} - -// SyncPosition returns the latest positions for syncing. -func (d *SyncServerDatasource) SyncPosition(ctx context.Context) (types.StreamingToken, error) { - return d.Database.SyncPosition(d.dbctx) -} - -func (d *SyncServerDatasource) IncrementalSync(ctx context.Context, res *types.Response, device userapi.Device, fromPos, toPos types.StreamingToken, numRecentEventsPerRoom int, wantFullState bool) (*types.Response, error) { - return d.Database.IncrementalSync(d.dbctx, res, device, fromPos, toPos, numRecentEventsPerRoom, wantFullState) -} - -// CompleteSync returns a complete /sync API response for the given user. A response object -// must be provided for CompleteSync to populate - it will not create one. -func (d *SyncServerDatasource) CompleteSync(ctx context.Context, res *types.Response, device userapi.Device, numRecentEventsPerRoom int) (*types.Response, error) { - return d.Database.CompleteSync(d.dbctx, res, device, numRecentEventsPerRoom) -} - -// GetAccountDataInRange returns all account data for a given user inserted or -// updated between two given positions -// Returns a map following the format data[roomID] = []dataTypes -// If no data is retrieved, returns an empty map -// If there was an issue with the retrieval, returns an error -func (d *SyncServerDatasource) GetAccountDataInRange(ctx context.Context, userID string, r types.Range, accountDataFilterPart *gomatrixserverlib.EventFilter) (map[string][]string, error) { - return d.Database.GetAccountDataInRange(d.dbctx, userID, r, accountDataFilterPart) -} - -// UpsertAccountData keeps track of new or updated account data, by saving the type -// of the new/updated data, and the user ID and room ID the data is related to (empty) -// room ID means the data isn't specific to any room) -// If no data with the given type, user ID and room ID exists in the database, -// creates a new row, else update the existing one -// Returns an error if there was an issue with the upsert -func (d *SyncServerDatasource) UpsertAccountData(ctx context.Context, userID, roomID, dataType string) (types.StreamPosition, error) { - return d.Database.UpsertAccountData(d.dbctx, userID, roomID, dataType) -} - -// AddInviteEvent stores a new invite event for a user. -// If the invite was successfully stored this returns the stream ID it was stored at. -// Returns an error if there was a problem communicating with the database. -func (d *SyncServerDatasource) AddInviteEvent(ctx context.Context, inviteEvent gomatrixserverlib.HeaderedEvent) (types.StreamPosition, error) { - return d.Database.AddInviteEvent(d.dbctx, inviteEvent) -} - -// RetireInviteEvent removes an old invite event from the database. Returns the new position of the retired invite. -// Returns an error if there was a problem communicating with the database. -func (d *SyncServerDatasource) RetireInviteEvent(ctx context.Context, inviteEventID string) (types.StreamPosition, error) { - return d.Database.RetireInviteEvent(d.dbctx, inviteEventID) -} - -// GetEventsInStreamingRange retrieves all of the events on a given ordering using the given extremities and limit. -func (d *SyncServerDatasource) GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) { - return d.Database.GetEventsInStreamingRange(d.dbctx, from, to, roomID, limit, backwardOrdering) -} - -// GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit. -func (d *SyncServerDatasource) GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) { - return d.Database.GetEventsInTopologicalRange(d.dbctx, from, to, roomID, limit, backwardOrdering) -} - -// EventPositionInTopology returns the depth and stream position of the given event. -func (d *SyncServerDatasource) EventPositionInTopology(ctx context.Context, eventID string) (types.TopologyToken, error) { - return d.Database.EventPositionInTopology(d.dbctx, eventID) -} - -// BackwardExtremitiesForRoom returns a map of backwards extremity event ID to a list of its prev_events. -func (d *SyncServerDatasource) BackwardExtremitiesForRoom(ctx context.Context, roomID string) (backwardExtremities map[string][]string, err error) { - return d.Database.BackwardExtremitiesForRoom(d.dbctx, roomID) -} - -func (d *SyncServerDatasource) MaxTopologicalPosition(ctx context.Context, roomID string) (types.TopologyToken, error) { - return d.Database.MaxTopologicalPosition(d.dbctx, roomID) -} - -// SendToDeviceUpdatesForSync returns a list of send-to-device updates. It returns three lists: -// - "events": a list of send-to-device events that should be included in the sync -// - "changes": a list of send-to-device events that should be updated in the database by -// CleanSendToDeviceUpdates -// - "deletions": a list of send-to-device events which have been confirmed as sent and -// can be deleted altogether by CleanSendToDeviceUpdates -// The token supplied should be the current requested sync token, e.g. from the "since" -// parameter. -func (d *SyncServerDatasource) SendToDeviceUpdatesForSync(ctx context.Context, userID, deviceID string, token types.StreamingToken) (events []types.SendToDeviceEvent, changes []types.SendToDeviceNID, deletions []types.SendToDeviceNID, err error) { - return d.Database.SendToDeviceUpdatesForSync(d.dbctx, userID, deviceID, token) -} - -// StoreNewSendForDeviceMessage stores a new send-to-device event for a user's device. -func (d *SyncServerDatasource) StoreNewSendForDeviceMessage(ctx context.Context, streamPos types.StreamPosition, userID, deviceID string, event gomatrixserverlib.SendToDeviceEvent) (types.StreamPosition, error) { - return d.Database.StoreNewSendForDeviceMessage(d.dbctx, streamPos, userID, deviceID, event) -} - -// CleanSendToDeviceUpdates will update or remove any send-to-device updates based on the -// result to a previous call to SendDeviceUpdatesForSync. This is separate as it allows -// SendToDeviceUpdatesForSync to be called multiple times if needed (e.g. before and after -// starting to wait for an incremental sync with timeout). -// The token supplied should be the current requested sync token, e.g. from the "since" -// parameter. -func (d *SyncServerDatasource) CleanSendToDeviceUpdates(ctx context.Context, toUpdate, toDelete []types.SendToDeviceNID, token types.StreamingToken) (err error) { - return d.Database.CleanSendToDeviceUpdates(d.dbctx, toUpdate, toDelete, token) -} - -// SendToDeviceUpdatesWaiting returns true if there are send-to-device updates waiting to be sent. -func (d *SyncServerDatasource) SendToDeviceUpdatesWaiting(ctx context.Context, userID, deviceID string) (bool, error) { - return d.Database.SendToDeviceUpdatesWaiting(d.dbctx, userID, deviceID) -} - -// GetFilter looks up the filter associated with a given local user and filter ID. -// Returns a filter structure. Otherwise returns an error if no such filter exists -// or if there was an error talking to the database. -func (d *SyncServerDatasource) GetFilter(ctx context.Context, localpart string, filterID string) (*gomatrixserverlib.Filter, error) { - return d.Database.GetFilter(d.dbctx, localpart, filterID) -} - -// PutFilter puts the passed filter into the database. -// Returns the filterID as a string. Otherwise returns an error if something -// goes wrong. -func (d *SyncServerDatasource) PutFilter(ctx context.Context, localpart string, filter *gomatrixserverlib.Filter) (string, error) { - return d.Database.PutFilter(d.dbctx, localpart, filter) -} - -// RedactEvent wipes an event in the database and sets the unsigned.redacted_because key to the redaction event -func (d *SyncServerDatasource) RedactEvent(ctx context.Context, redactedEventID string, redactedBecause *gomatrixserverlib.HeaderedEvent) error { - return d.Database.RedactEvent(d.dbctx, redactedEventID, redactedBecause) -} - -// AllPeekingDevicesInRooms returns a map of room ID to a list of all peeking devices. -func (d *SyncServerDatasource) AllPeekingDevicesInRooms(ctx context.Context) (map[string][]types.PeekingDevice, error) { - return d.Database.AllPeekingDevicesInRooms(d.dbctx) -} - -// AddPeek adds a new peek to our DB for a given room by a given user's device. -// Returns an error if there was a problem communicating with the database. -func (d *SyncServerDatasource) AddPeek(ctx context.Context, roomID, userID, deviceID string) (types.StreamPosition, error) { - return d.Database.AddPeek(d.dbctx, roomID, userID, deviceID) -} - - -func (d *SyncServerDatasource) PartitionOffsets( - ctx context.Context, topic string, -) ([]sqlutil.PartitionOffset, error) { - return d.PartitionOffsetStatements.PartitionOffsets(d.dbctx, topic) -} - -// SetPartitionOffset implements PartitionStorer -func (d *SyncServerDatasource) SetPartitionOffset( - ctx context.Context, topic string, partition int32, offset int64, -) error { - return d.PartitionOffsetStatements.SetPartitionOffset(d.dbctx, topic, partition, offset) -} From baee97bff7b61cba075fa11e087731ffc0ad42de Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 21:04:33 +0100 Subject: [PATCH 30/68] (almost) make it build --- federationsender/api/api.go | 2 +- federationsender/internal/perform.go | 20 +++++++------- federationsender/inthttp/client.go | 14 ++++++++++ federationsender/storage/interface.go | 6 ++--- federationsender/storage/shared/storage.go | 15 ++++++----- .../storage/sqlite3/remote_peeks_table.go | 27 ++++++++++--------- federationsender/storage/tables/interface.go | 7 ++--- federationsender/types/types.go | 5 +++- 8 files changed, 60 insertions(+), 36 deletions(-) diff --git a/federationsender/api/api.go b/federationsender/api/api.go index 343d8301ba..dc08567236 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -62,7 +62,7 @@ type FederationSenderInternalAPI interface { ctx context.Context, request *PerformPeekRequest, response *PerformPeekResponse, - ) + ) error // Handle an instruction to make_leave & send_leave with a remote server. PerformLeave( ctx context.Context, diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 5d887beeef..92dfa89a55 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -212,7 +212,7 @@ func (r *FederationSenderInternalAPI) PerformPeek( ctx context.Context, request *api.PerformPeekRequest, response *api.PerformPeekResponse, -) (err error) { +) error { // Look up the supported room versions. var supportedVersions []gomatrixserverlib.RoomVersion for version := range version.SupportedRoomVersions() { @@ -252,7 +252,7 @@ func (r *FederationSenderInternalAPI) PerformPeek( } // We're all good. - return + return nil } // If we reach here then we didn't complete a peek for some reason. @@ -274,6 +274,8 @@ func (r *FederationSenderInternalAPI) PerformPeek( "failed to peek room %q through %d server(s): last error %s", request.RoomID, len(request.ServerNames), lastErr, ) + + return lastErr } func (r *FederationSenderInternalAPI) performPeekUsingServer( @@ -282,10 +284,16 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( serverName gomatrixserverlib.ServerName, supportedVersions []gomatrixserverlib.RoomVersion, ) error { + // create a unique ID for this peek. + // for now we just use the room ID again. In future, if we ever + // support concurrent peeks to the same room with different filters + // then we would need to disambiguate further. + peekID := roomID + // check whether we're peeking already to try to avoid needlessly // re-peeking on the server. we don't need a transaction for this, // given this is a nice-to-have. - remotePeek, err := r.db.GetRemotePeek(ctx, serverName, roomID) + remotePeek, err := r.db.GetRemotePeek(ctx, roomID, serverName, peekID) if err != nil { return err } @@ -302,12 +310,6 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( } } - // create a unique ID for this peek. - // for now we just use the room ID again. In future, if we ever - // support concurrent peeks to the same room with different filters - // then we would need to disambiguate further. - peekID := roomID - // Try to perform a /peek using the information supplied in the // request. respPeek, err := r.federation.Peek( diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index 5bfe6089dd..88e9e3d920 100644 --- a/federationsender/inthttp/client.go +++ b/federationsender/inthttp/client.go @@ -20,6 +20,7 @@ const ( FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest" FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest" FederationSenderPerformInviteRequestPath = "/federationsender/performInviteRequest" + FederationSenderPerformPeekRequestPath = "/federationsender/performPeekRequest" FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" @@ -72,6 +73,19 @@ func (h *httpFederationSenderInternalAPI) PerformInvite( return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } +// Handle starting a peek on a remote server. +func (h *httpFederationSenderInternalAPI) PerformPeek( + ctx context.Context, + request *api.PerformPeekRequest, + response *api.PerformPeekResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformPeekRequest") + defer span.Finish() + + apiURL := h.federationSenderURL + FederationSenderPerformPeekRequestPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + func (h *httpFederationSenderInternalAPI) PerformServersAlive( ctx context.Context, request *api.PerformServersAliveRequest, diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index f95db13cc0..75de07f4da 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -54,8 +54,8 @@ type Database interface { RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) - AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error - RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error - GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID string) (types.RemotePeek, error) + AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string) (types.RemotePeek, error) GetRemotePeeks(roomID string) ([]types.RemotePeek, error) } diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 8de66cc71e..4fa66ab0a3 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -35,6 +35,7 @@ type Database struct { FederationSenderJoinedHosts tables.FederationSenderJoinedHosts FederationSenderRooms tables.FederationSenderRooms FederationSenderBlacklist tables.FederationSenderBlacklist + FederationSenderRemotePeeks tables.FederationSenderRemotePeeks } // An Receipt contains the NIDs of a call to GetNextTransactionPDUs/EDUs. @@ -164,22 +165,22 @@ func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName) } -func (d *Database) AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error { +func (d *Database) AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.RemotePeeks.InsertRemotePeek(context.TODO(), txn, serverName, roomID, renewalInterval) + return d.FederationSenderRemotePeeks.InsertRemotePeek(context.TODO(), txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID string, renewalInterval int) error { +func (d *Database) RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.RemotePeeks.RenewRemotePeek(context.TODO(), txn, serverName, roomID, renewalInterval) + return d.FederationSenderRemotePeeks.RenewRemotePeek(context.TODO(), txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID string) (types.RemotePeek, error) { - return d.RemotePeeks.SelectRemotePeek(context.TODO(), serverName, roomID) +func (d *Database) GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string) (types.RemotePeek, error) { + return d.FederationSenderRemotePeeks.SelectRemotePeek(context.TODO(), serverName, roomID, peekID) } func (d *Database) GetRemotePeeks(roomID string) ([]types.RemotePeek, error) { - return d.RemotePeeks.SelectRemotePeeks(context.TODO(), roomID) + return d.FederationSenderRemotePeeks.SelectRemotePeeks(context.TODO(), roomID) } diff --git a/federationsender/storage/sqlite3/remote_peeks_table.go b/federationsender/storage/sqlite3/remote_peeks_table.go index bcb952e45b..5b05cded1b 100644 --- a/federationsender/storage/sqlite3/remote_peeks_table.go +++ b/federationsender/storage/sqlite3/remote_peeks_table.go @@ -26,24 +26,25 @@ const remotePeeksSchema = ` CREATE TABLE IF NOT EXISTS federationsender_remote_peeks ( room_id TEXT NOT NULL, server_name TEXT NOT NULL, + peek_id TEXT NOT NULL, creation_ts INTEGER NOT NULL, renewed_ts INTEGER NOT NULL, renewal_interval INTEGER NOT NULL, - UNIQUE (room_id, server_name) + UNIQUE (room_id, server_name, peek_id) ); ` const insertRemotePeekSQL = "" + - "INSERT INTO federationsender_remote_peeks (room_id, server_name, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5)" + "INSERT INTO federationsender_remote_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" const selectRemotePeekSQL = "" + - "SELECT room_id, server_name, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2" + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" const selectRemotePeeksSQL = "" + - "SELECT room_id, server_name, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1" + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1" const renewRemotePeekSQL = "" + - "UPDATE federationsender_remote_peeks SET renewed_ts=$3, renewal_interval=$4 WHERE room_id = $1 and server_name = $2" + "UPDATE federationsender_remote_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" const deleteRemotePeekSQL = "" + "DELETE FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2" @@ -92,25 +93,25 @@ func NewSQLiteRemotePeeksTable(db *sql.DB) (s *remotePeeksStatements, err error) } func (s *remotePeeksStatements) InsertRemotePeek( - ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) stmt := sqlutil.TxStmt(txn, s.insertRemotePeekStmt) - _, err := stmt.ExecContext(ctx, roomID, serverName, nowMilli, nowMilli, renewalInterval) + _, err := stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) return } func (s *remotePeeksStatements) RenewRemotePeek( - ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - _, err := sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, roomID, serverName, nowMilli, renewalInterval) + _, err := sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) return } func (s *remotePeeksStatements) SelectRemotePeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID string, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, ) (remotePeek types.RemotePeek, err error) { rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) if err != nil { @@ -121,6 +122,7 @@ func (s *remotePeeksStatements) SelectRemotePeek( if err = rows.Scan( &remotePeek.RoomID, &remotePeek.ServerName, + &remotePeek.PeekID, &remotePeek.CreationTimestamp, &remotePeek.RenewTimestamp, &remotePeek.RenewalInterval, @@ -144,6 +146,7 @@ func (s *remotePeeksStatements) SelectRemotePeeks( if err = rows.Scan( &remotePeek.RoomID, &remotePeek.ServerName, + &remotePeek.PeekID, &remotePeek.CreationTimestamp, &remotePeek.RenewTimestamp, &remotePeek.RenewalInterval, @@ -157,9 +160,9 @@ func (s *remotePeeksStatements) SelectRemotePeeks( } func (s *remotePeeksStatements) DeleteRemotePeek( - ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, ) (err error) { - _, err := sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName) + _, err := sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName, peekID) return } diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index 61c9208b4f..85a6ba98bd 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -69,9 +69,10 @@ type FederationSenderBlacklist interface { } type FederationSenderRemotePeeks interface { - InsertRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int) (err error) - RenewRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName, renewalInterval int) (err error) + InsertRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) + RenewRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) + SelectRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (remotePeek types.RemotePeek, err error) SelectRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (remotePeeks []types.RemotePeek, err error) - DeleteRemotePeek(ctx context.Context, txn *sql.Tx, roomID string, serverName gomatrixserverlib.ServerName) (err error) + DeleteRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) DeleteRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) } diff --git a/federationsender/types/types.go b/federationsender/types/types.go index e8825d7787..08182262a5 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -50,7 +50,10 @@ func (e EventIDMismatchError) Error() string { ) } -type RemotePeek { +// UnixMs is the milliseconds since the Unix epoch +type UnixMs int64 + +type RemotePeek struct { PeekID string RoomID string ServerName gomatrixserverlib.ServerName From 4ef6a3c759806e5f6c9a213c119b8711295a65e2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 21:06:51 +0100 Subject: [PATCH 31/68] clean up bad merge --- syncapi/storage/shared/syncserver.go | 38 ---------------------------- 1 file changed, 38 deletions(-) diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 695556bf6f..94580adb8b 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -1075,18 +1075,6 @@ func (d *Database) getStateDeltas( // the timeline. if membership := getMembershipFromEvent(&ev.Event, userID); membership != "" { if membership == gomatrixserverlib.Join { - if peeking[roomID] { - // we automatically cancel our peeks when we join a room - err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error { - // XXX: is it correct that we're discarding the streamid here? - _, err = d.Peeks.DeletePeeks(ctx, txn, roomID, userID) - return err - }) - if err != nil { - return nil, nil, err - } - } - // send full room state down instead of a delta var s []types.StreamEvent s, err = d.currentStateStreamEventsForRoom(ctx, txn, roomID, stateFilter) @@ -1157,32 +1145,6 @@ func (d *Database) getStateDeltasForFullStateSync( } } - // Add full states for all peeking rooms - newPeeks := false - for _, peek := range peeks { - if peek.New { - newPeeks = true - } - s, stateErr := d.currentStateStreamEventsForRoom(ctx, txn, peek.RoomID, stateFilter) - if stateErr != nil { - return nil, nil, stateErr - } - deltas = append(deltas, stateDelta{ - membership: gomatrixserverlib.Peek, - stateEvents: d.StreamEventsToEvents(device, s), - roomID: peek.RoomID, - }) - } - - if newPeeks { - err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error { - return d.Peeks.MarkPeeksAsOld(ctx, txn, userID, device.ID) - }) - if err != nil { - return nil, nil, err - } - } - // Get all the state events ever between these two positions stateNeeded, eventMap, err := d.OutputEvents.SelectStateInRange(ctx, txn, r, stateFilter) if err != nil { From 65e59a1af961928dd2ec4a31707acf471f2dff0c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 22:31:32 +0100 Subject: [PATCH 32/68] support SendEventWithState with optional event --- roomserver/api/wrapper.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index 82a4a57195..2e0e0273f3 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -42,10 +42,11 @@ func SendEvents( // 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 +// marked as `true` in haveEventIDs. The event itself is optional in case +// hou just want to write outliers to the roomserver. func SendEventWithState( ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, - event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, + event *gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, ) error { outliers, err := state.Events() if err != nil { @@ -69,13 +70,15 @@ func SendEventWithState( stateEventIDs[i] = state.StateEvents[i].EventID() } - ires = append(ires, InputRoomEvent{ - Kind: KindNew, - Event: event, - AuthEventIDs: event.AuthEventIDs(), - HasState: true, - StateEventIDs: stateEventIDs, - }) + if event != nil { + ires = append(ires, InputRoomEvent{ + Kind: KindNew, + Event: *event, + AuthEventIDs: event.AuthEventIDs(), + HasState: true, + StateEventIDs: stateEventIDs, + }) + } return SendInputRoomEvents(ctx, rsAPI, ires) } From a5c0521c3ff2468ae06fa088deea7404999e3598 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 22:31:56 +0100 Subject: [PATCH 33/68] fix build & lint --- federationapi/routing/routing.go | 2 +- federationapi/routing/send.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 71a09d4218..b41ecadbbc 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -221,7 +221,7 @@ func Setup( queryVars := httpReq.URL.Query() remoteVersions := []gomatrixserverlib.RoomVersion{} if vers, ok := queryVars["ver"]; ok { - // The remote side supplied a ?=ver so use that to build up the list + // The remote side supplied a ?ver= so use that to build up the list // of supported room versions for _, v := range vers { remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersion(v)) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 9def7c3c38..ca4473f778 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -458,7 +458,8 @@ func (t *txnReq) processEventWithMissingState(ctx context.Context, e gomatrixser // pass the event along with the state to the roomserver using a background context so we don't // needlessly expire - return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, e.Headered(roomVersion), t.haveEventIDs()) + headeredEvent := e.Headered(roomVersion) + return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, &headeredEvent, t.haveEventIDs()) } // lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event) From df29509e7e74119707b2a16508d71d13e64fc800 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 11 Sep 2020 22:39:50 +0100 Subject: [PATCH 34/68] fix build & lint --- federationsender/api/api.go | 2 +- federationsender/internal/perform.go | 21 ++++++------ federationsender/storage/interface.go | 9 +++--- federationsender/storage/shared/storage.go | 16 +++++----- .../storage/sqlite3/remote_peeks_table.go | 32 +++++++++++-------- federationsender/storage/tables/interface.go | 2 +- federationsender/types/types.go | 17 ++++------ 7 files changed, 51 insertions(+), 48 deletions(-) diff --git a/federationsender/api/api.go b/federationsender/api/api.go index dc08567236..04ac634625 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -114,7 +114,7 @@ type PerformJoinResponse struct { type PerformPeekRequest struct { RoomID string `json:"room_id"` // The sorted list of servers to try. Servers will be tried sequentially, after de-duplication. - ServerNames types.ServerNames `json:"server_names"` + ServerNames types.ServerNames `json:"server_names"` } type PerformPeekResponse struct { diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 92dfa89a55..14df6c8619 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -186,7 +186,7 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( // Check that the send_join response was valid. joinCtx := perform.JoinContext(r.federation, r.keyRing) respState, err := joinCtx.CheckSendJoinResponse( - ctx, event, serverName, respMakeJoin, respSendJoin, + ctx, event, serverName, respSendJoin, ) if err != nil { return fmt.Errorf("joinCtx.CheckSendJoinResponse: %w", err) @@ -195,10 +195,11 @@ 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. + headeredEvent := event.Headered(respMakeJoin.RoomVersion) if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, respState, - event.Headered(respMakeJoin.RoomVersion), + &headeredEvent, nil, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) @@ -293,18 +294,17 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( // check whether we're peeking already to try to avoid needlessly // re-peeking on the server. we don't need a transaction for this, // given this is a nice-to-have. - remotePeek, err := r.db.GetRemotePeek(ctx, roomID, serverName, peekID) + remotePeek, err := r.db.GetRemotePeek(ctx, serverName, roomID, peekID) if err != nil { return err } renewing := false if remotePeek != nil { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - if (nowMilli > remotePeek.RenewedTimestamp + remotePeek.RenewalInterval) { + if nowMilli > remotePeek.RenewedTimestamp+remotePeek.RenewalInterval { logrus.Infof("stale remote peek to %s for %s already exists; renewing", serverName, roomID) renewing = true - } - else { + } else { logrus.Infof("live remote peek to %s for %s already exists", serverName, roomID) return nil } @@ -336,12 +336,11 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( // If we've got this far, the remote server is peeking. if renewing { - if err = r.db.RenewRemotePeek(ctx, serverName, roomID, respPeek.RenewalInterval); err != nil { + if err = r.db.RenewRemotePeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { return err } - } - else { - if err = r.db.AddRemotePeek(ctx, serverName, roomID, respPeek.RenewalInterval); err != nil { + } else { + if err = r.db.AddRemotePeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { return err } } @@ -351,7 +350,7 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, &respState, - event.Headered(respPeek.RoomVersion), nil, + nil, nil, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) } diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index 75de07f4da..49d3937bf9 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -50,12 +50,13 @@ type Database interface { GetPendingPDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) GetPendingEDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) + // XXX: why don't these have contexts passed in? AddServerToBlacklist(serverName gomatrixserverlib.ServerName) error RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) - AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error - RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error - GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string) (types.RemotePeek, error) - GetRemotePeeks(roomID string) ([]types.RemotePeek, error) + AddRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + RenewRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + GetRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.RemotePeek, error) + GetRemotePeeks(ctx context.Context, roomID string) ([]types.RemotePeek, error) } diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 4fa66ab0a3..4985c5eda6 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -165,22 +165,22 @@ func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName) } -func (d *Database) AddRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) AddRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.FederationSenderRemotePeeks.InsertRemotePeek(context.TODO(), txn, serverName, roomID, peekID, renewalInterval) + return d.FederationSenderRemotePeeks.InsertRemotePeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) RenewRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) RenewRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.FederationSenderRemotePeeks.RenewRemotePeek(context.TODO(), txn, serverName, roomID, peekID, renewalInterval) + return d.FederationSenderRemotePeeks.RenewRemotePeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) GetRemotePeek(serverName gomatrixserverlib.ServerName, roomID, peekID string) (types.RemotePeek, error) { - return d.FederationSenderRemotePeeks.SelectRemotePeek(context.TODO(), serverName, roomID, peekID) +func (d *Database) GetRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.RemotePeek, error) { + return d.FederationSenderRemotePeeks.SelectRemotePeek(ctx, nil, serverName, roomID, peekID) } -func (d *Database) GetRemotePeeks(roomID string) ([]types.RemotePeek, error) { - return d.FederationSenderRemotePeeks.SelectRemotePeeks(context.TODO(), roomID) +func (d *Database) GetRemotePeeks(ctx context.Context, roomID string) ([]types.RemotePeek, error) { + return d.FederationSenderRemotePeeks.SelectRemotePeeks(ctx, nil, roomID) } diff --git a/federationsender/storage/sqlite3/remote_peeks_table.go b/federationsender/storage/sqlite3/remote_peeks_table.go index 5b05cded1b..19eef880c3 100644 --- a/federationsender/storage/sqlite3/remote_peeks_table.go +++ b/federationsender/storage/sqlite3/remote_peeks_table.go @@ -17,7 +17,10 @@ package sqlite3 import ( "context" "database/sql" + "time" + "github.com/matrix-org/dendrite/federationsender/types" + "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/gomatrixserverlib" ) @@ -97,7 +100,7 @@ func (s *remotePeeksStatements) InsertRemotePeek( ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) stmt := sqlutil.TxStmt(txn, s.insertRemotePeekStmt) - _, err := stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) + _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) return } @@ -105,31 +108,34 @@ func (s *remotePeeksStatements) RenewRemotePeek( ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - _, err := sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) + _, err = sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) return } - func (s *remotePeeksStatements) SelectRemotePeek( ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, -) (remotePeek types.RemotePeek, err error) { +) (*types.RemotePeek, error) { rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) if err != nil { - return + return nil, err } defer internal.CloseAndLogIfError(ctx, rows, "SelectRemotePeek: rows.close() failed") remotePeek := types.RemotePeek{} - if err = rows.Scan( + err = rows.Scan( &remotePeek.RoomID, &remotePeek.ServerName, &remotePeek.PeekID, &remotePeek.CreationTimestamp, - &remotePeek.RenewTimestamp, + &remotePeek.RenewedTimestamp, &remotePeek.RenewalInterval, - ); err != nil { - return + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err } - return remotePeek, rows.Err() + return &remotePeek, rows.Err() } func (s *remotePeeksStatements) SelectRemotePeeks( @@ -148,7 +154,7 @@ func (s *remotePeeksStatements) SelectRemotePeeks( &remotePeek.ServerName, &remotePeek.PeekID, &remotePeek.CreationTimestamp, - &remotePeek.RenewTimestamp, + &remotePeek.RenewedTimestamp, &remotePeek.RenewalInterval, ); err != nil { return @@ -162,13 +168,13 @@ func (s *remotePeeksStatements) SelectRemotePeeks( func (s *remotePeeksStatements) DeleteRemotePeek( ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, ) (err error) { - _, err := sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName, peekID) + _, err = sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName, peekID) return } func (s *remotePeeksStatements) DeleteRemotePeeks( ctx context.Context, txn *sql.Tx, roomID string, ) (err error) { - _, err := sqlutil.TxStmt(txn, s.deleteRemotePeeksStmt).ExecContext(ctx, roomID) + _, err = sqlutil.TxStmt(txn, s.deleteRemotePeeksStmt).ExecContext(ctx, roomID) return } diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index 85a6ba98bd..e35eac0fb7 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -71,7 +71,7 @@ type FederationSenderBlacklist interface { type FederationSenderRemotePeeks interface { InsertRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) RenewRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) - SelectRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (remotePeek types.RemotePeek, err error) + SelectRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (remotePeek *types.RemotePeek, err error) SelectRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (remotePeeks []types.RemotePeek, err error) DeleteRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) DeleteRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) diff --git a/federationsender/types/types.go b/federationsender/types/types.go index 08182262a5..d593277964 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -50,14 +50,11 @@ func (e EventIDMismatchError) Error() string { ) } -// UnixMs is the milliseconds since the Unix epoch -type UnixMs int64 - type RemotePeek struct { - PeekID string - RoomID string - ServerName gomatrixserverlib.ServerName - CreatedTimestamp UnixMs - RenewedTimestamp UnixMs - RenewalInterval UnixMs -} \ No newline at end of file + PeekID string + RoomID string + ServerName gomatrixserverlib.ServerName + CreationTimestamp int64 + RenewedTimestamp int64 + RenewalInterval int64 +} From 410ac724a6ee91115f997068b597f22f35bf77de Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 00:23:57 +0100 Subject: [PATCH 35/68] reinstate federated peeks in the roomserver (doh) --- roomserver/internal/perform/perform_peek.go | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index ab6d17b037..3d0919356e 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -151,11 +151,27 @@ func (r *Peeker) performPeekRoomByID( } } - // If the server name in the room ID isn't ours then it's a - // possible candidate for finding the room via federation. Add - // it to the list of servers to try. + // handle federated peeks if domain != r.Cfg.Matrix.ServerName { + // If the server name in the room ID isn't ours then it's a + // possible candidate for finding the room via federation. Add + // it to the list of servers to try. req.ServerNames = append(req.ServerNames, domain) + + // Try peeking by all of the supplied server names. + fedReq := fsAPI.PerformPeekRequest{ + RoomID: req.RoomIDOrAlias, // the room ID to try and peek + ServerNames: req.ServerNames, // the servers to try peeking via + } + fedRes := fsAPI.PerformPeekResponse{} + r.FSAPI.PerformPeek(ctx, &fedReq, &fedRes) + if fedRes.LastError != nil { + return "", &api.PerformError{ + Code: api.PerformErrRemote, + Msg: fedRes.LastError.Message, + RemoteCode: fedRes.LastError.Code, + } + } } // If this room isn't world_readable, we reject. From f8bb4487df1a5ee81c0f1cb879d8b7af2a101839 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 00:24:22 +0100 Subject: [PATCH 36/68] fix sql thinko --- federationsender/storage/sqlite3/remote_peeks_table.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/federationsender/storage/sqlite3/remote_peeks_table.go b/federationsender/storage/sqlite3/remote_peeks_table.go index 19eef880c3..0abadc8dff 100644 --- a/federationsender/storage/sqlite3/remote_peeks_table.go +++ b/federationsender/storage/sqlite3/remote_peeks_table.go @@ -115,13 +115,9 @@ func (s *remotePeeksStatements) RenewRemotePeek( func (s *remotePeeksStatements) SelectRemotePeek( ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, ) (*types.RemotePeek, error) { - rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) - if err != nil { - return nil, err - } - defer internal.CloseAndLogIfError(ctx, rows, "SelectRemotePeek: rows.close() failed") + row := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryRowContext(ctx, roomID) remotePeek := types.RemotePeek{} - err = rows.Scan( + err := row.Scan( &remotePeek.RoomID, &remotePeek.ServerName, &remotePeek.PeekID, @@ -135,7 +131,7 @@ func (s *remotePeeksStatements) SelectRemotePeek( if err != nil { return nil, err } - return &remotePeek, rows.Err() + return &remotePeek, nil } func (s *remotePeeksStatements) SelectRemotePeeks( From fff18454b1d3b99aa7f079e8752a601849a17de5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 01:23:32 +0100 Subject: [PATCH 37/68] todo for authenticating state returned by /peek --- federationsender/internal/perform.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 14df6c8619..a31b1cf34e 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -334,6 +334,9 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( return fmt.Errorf("respPeek.RoomVersion.EventFormat: %w", err) } + // TODO: authenticate the state returned (check its auth events etc) + // the equivalent of CheckSendJoinResponse() + // If we've got this far, the remote server is peeking. if renewing { if err = r.db.RenewRemotePeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { From c6a2604edcc392731b5a153a51279dde9dddedea Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 01:23:57 +0100 Subject: [PATCH 38/68] support returning current state from QueryStateAndAuthChain --- roomserver/api/query.go | 2 +- roomserver/internal/query/query.go | 37 +++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/roomserver/api/query.go b/roomserver/api/query.go index 67a217c82a..a80fe684cb 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -177,7 +177,7 @@ type QueryStateAndAuthChainRequest struct { // The room ID to query the state in. RoomID string `json:"room_id"` // The list of prev events for the event. Used to calculate the state at - // the event + // the event. If empty, assumes current state. PrevEventIDs []string `json:"prev_event_ids"` // The list of auth events for the event. Used to calculate the auth chain AuthEventIDs []string `json:"auth_event_ids"` diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index b34ae7701f..6402102983 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -334,10 +334,41 @@ func (r *Queryer) QueryStateAndAuthChain( response.RoomExists = true response.RoomVersion = info.RoomVersion - stateEvents, err := r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) - if err != nil { - return err + var stateEvents []gomatrixserverlib.Event + if len(request.PrevEventIDs) > 0 { + stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) + if err != nil { + return err + } + } else { + // no PrevEventIDs or AuthEventIDs were provided, so return current state instead. + + // XXX: is this right? + roomState := state.NewStateResolution(r.DB, *info) + // no need to resolve state again later + request.ResolveState = false + + var currentStateSnapshotNID types.StateSnapshotNID + _, currentStateSnapshotNID, _, err = + r.DB.LatestEventIDs(ctx, info.RoomNID) + if err != nil { + return err + } + + var stateEntries []types.StateEntry + stateEntries, err = roomState.LoadStateAtSnapshot( + ctx, currentStateSnapshotNID, + ) + if err != nil { + return err + } + + stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries) + if err != nil { + return err + } } + response.PrevEventsExist = true // add the auth event IDs for the current state events too From 803647be5675e1a7e4bf56daa12755681af026f8 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 01:24:28 +0100 Subject: [PATCH 39/68] handle SS /peek --- federationapi/routing/peek.go | 112 +++++++++++++++++++++++++++++++ federationapi/routing/routing.go | 36 +++++++++- 2 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 federationapi/routing/peek.go diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go new file mode 100644 index 0000000000..12df15448e --- /dev/null +++ b/federationapi/routing/peek.go @@ -0,0 +1,112 @@ +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routing + +import ( + "context" + "net/http" + + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +// Peek implements the /peek API +func Peek( + httpReq *http.Request, + request *gomatrixserverlib.FederationRequest, + cfg *config.FederationAPI, + rsAPI api.RoomserverInternalAPI, + roomID, peekID string, + remoteVersions []gomatrixserverlib.RoomVersion, +) util.JSONResponse { + verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} + verRes := api.QueryRoomVersionForRoomResponse{} + if err := rsAPI.QueryRoomVersionForRoom(httpReq.Context(), &verReq, &verRes); err != nil { + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: jsonerror.InternalServerError(), + } + } + + // Check that the room that the remote side is trying to join is actually + // one of the room versions that they listed in their supported ?ver= in + // the peek URL. + remoteSupportsVersion := false + for _, v := range remoteVersions { + if v == verRes.RoomVersion { + remoteSupportsVersion = true + break + } + } + // If it isn't, stop trying to join the room. + if !remoteSupportsVersion { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.IncompatibleRoomVersion(verRes.RoomVersion), + } + } + + // TODO: Check history visibility + + state, err := getCurrentState(httpReq.Context(), request, rsAPI, roomID) + if err != nil { + return *err + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: map[string]interface{}{ + "state": state, + "room_version": verRes.RoomVersion, + }, + } +} + + +func getCurrentState( + ctx context.Context, + request *gomatrixserverlib.FederationRequest, + rsAPI api.RoomserverInternalAPI, + roomID string, +) (*gomatrixserverlib.RespPeek, *util.JSONResponse) { + var response api.QueryStateAndAuthChainResponse + err := rsAPI.QueryStateAndAuthChain( + ctx, + &api.QueryStateAndAuthChainRequest{ + RoomID: roomID, + PrevEventIDs: []string{}, + AuthEventIDs: []string{}, + }, + &response, + ) + if err != nil { + resErr := util.ErrorResponse(err) + return nil, &resErr + } + + if !response.RoomExists { + return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil} + } + + return &gomatrixserverlib.RespPeek{ + StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), + AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), + RoomVersion: response.RoomVersion, + RenewalInterval: 60 * 60 * 1000 * 1000, // one hour + }, nil +} diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index b41ecadbbc..805ef4ab8d 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -207,7 +207,37 @@ func Setup( }, )).Methods(http.MethodGet) - v1fedmux.Handle("/make_join/{roomID}/{eventID}", httputil.MakeFedAPI( + v1fedmux.Handle("/peek/{roomID}/{peekID}", httputil.MakeFedAPI( + "federation_peek", cfg.Matrix.ServerName, keys, wakeup, + func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse { + if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) { + return util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Forbidden by server ACLs"), + } + } + roomID := vars["roomID"] + peekID := vars["peekID"] + queryVars := httpReq.URL.Query() + remoteVersions := []gomatrixserverlib.RoomVersion{} + if vers, ok := queryVars["ver"]; ok { + // The remote side supplied a ?ver= so use that to build up the list + // of supported room versions + for _, v := range vers { + remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersion(v)) + } + } else { + // The remote side didn't supply a ?ver= so just assume that they only + // support room version 1 + remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1) + } + return Peek( + httpReq, request, cfg, rsAPI, roomID, peekID, remoteVersions, + ) + }, + )).Methods(http.MethodPut, http.MethodDelete) + + v1fedmux.Handle("/make_join/{roomID}/{userID}", httputil.MakeFedAPI( "federation_make_join", cfg.Matrix.ServerName, keys, wakeup, func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest, vars map[string]string) util.JSONResponse { if roomserverAPI.IsServerBannedFromRoom(httpReq.Context(), rsAPI, vars["roomID"], request.Origin()) { @@ -217,7 +247,7 @@ func Setup( } } roomID := vars["roomID"] - eventID := vars["eventID"] + userID := vars["userID"] queryVars := httpReq.URL.Query() remoteVersions := []gomatrixserverlib.RoomVersion{} if vers, ok := queryVars["ver"]; ok { @@ -233,7 +263,7 @@ func Setup( remoteVersions = append(remoteVersions, gomatrixserverlib.RoomVersionV1) } return MakeJoin( - httpReq, request, cfg, rsAPI, roomID, eventID, remoteVersions, + httpReq, request, cfg, rsAPI, roomID, userID, remoteVersions, ) }, )).Methods(http.MethodGet) From 0ae0d1144660a08a5af5a2fbf857db91b9b4319b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 22:45:00 +0100 Subject: [PATCH 40/68] reimplement SS /peek to prod the RS to tell the FS about the peek --- federationapi/routing/peek.go | 62 ++++++++------------- federationsender/consumers/roomserver.go | 23 ++++++++ roomserver/api/api.go | 8 ++- roomserver/api/api_trace.go | 10 ++++ roomserver/api/output.go | 12 ++++ roomserver/api/perform.go | 19 +++++++ roomserver/api/query.go | 2 +- roomserver/internal/api.go | 6 ++ roomserver/internal/perform/perform_peek.go | 2 - roomserver/internal/query/query.go | 41 ++------------ roomserver/inthttp/client.go | 26 +++++++-- roomserver/inthttp/server.go | 13 +++++ 12 files changed, 140 insertions(+), 84 deletions(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index 12df15448e..423f847310 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -15,7 +15,6 @@ package routing import ( - "context" "net/http" "github.com/matrix-org/dendrite/clientapi/jsonerror" @@ -25,7 +24,7 @@ import ( "github.com/matrix-org/util" ) -// Peek implements the /peek API +// Peek implements the SS /peek API func Peek( httpReq *http.Request, request *gomatrixserverlib.FederationRequest, @@ -34,6 +33,8 @@ func Peek( roomID, peekID string, remoteVersions []gomatrixserverlib.RoomVersion, ) util.JSONResponse { + // TODO: check if we're just refreshing an existing peek. Somehow. + verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} verRes := api.QueryRoomVersionForRoomResponse{} if err := rsAPI.QueryRoomVersionForRoom(httpReq.Context(), &verReq, &verRes); err != nil { @@ -43,7 +44,7 @@ func Peek( } } - // Check that the room that the remote side is trying to join is actually + // Check that the room that the peeking server is trying to peek is actually // one of the room versions that they listed in their supported ?ver= in // the peek URL. remoteSupportsVersion := false @@ -53,7 +54,7 @@ func Peek( break } } - // If it isn't, stop trying to join the room. + // If it isn't, stop trying to peek the room. if !remoteSupportsVersion { return util.JSONResponse{ Code: http.StatusBadRequest, @@ -63,50 +64,33 @@ func Peek( // TODO: Check history visibility - state, err := getCurrentState(httpReq.Context(), request, rsAPI, roomID) - if err != nil { - return *err - } - - return util.JSONResponse{ - Code: http.StatusOK, - JSON: map[string]interface{}{ - "state": state, - "room_version": verRes.RoomVersion, - }, - } -} - - -func getCurrentState( - ctx context.Context, - request *gomatrixserverlib.FederationRequest, - rsAPI api.RoomserverInternalAPI, - roomID string, -) (*gomatrixserverlib.RespPeek, *util.JSONResponse) { - var response api.QueryStateAndAuthChainResponse - err := rsAPI.QueryStateAndAuthChain( - ctx, - &api.QueryStateAndAuthChainRequest{ + var response api.PerformHandleRemotePeekResponse + err := rsAPI.PerformHandleRemotePeek( + httpReq.Context(), + &api.PerformHandleRemotePeekRequest{ RoomID: roomID, - PrevEventIDs: []string{}, - AuthEventIDs: []string{}, + PeekID: peekID, + ServerName: request.Origin(), }, &response, ) if err != nil { resErr := util.ErrorResponse(err) - return nil, &resErr + return resErr } if !response.RoomExists { - return nil, &util.JSONResponse{Code: http.StatusNotFound, JSON: nil} + return util.JSONResponse{Code: http.StatusNotFound, JSON: nil} } - return &gomatrixserverlib.RespPeek{ - StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), - AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), - RoomVersion: response.RoomVersion, - RenewalInterval: 60 * 60 * 1000 * 1000, // one hour - }, nil + return util.JSONResponse{ + Code: http.StatusOK, + JSON: gomatrixserverlib.RespPeek{ + StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), + AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), + RoomVersion: response.RoomVersion, + RenewalInterval: 60 * 60 * 1000 * 1000, // one hour + }, + } } + diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index efeb53fa6b..1f16621f05 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -97,6 +97,14 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { }).Panicf("roomserver output log: write room event failure") return nil } + case api.OutputTypeNewRemotePeek: + if err := s.processRemotePeek(*output.NewRemotePeek); err != nil { + log.WithFields(log.Fields{ + "event": output.NewRemotePeek, + log.ErrorKey: err, + }).Panicf("roomserver output log: remote peek event failure") + return nil + } default: log.WithField("type", output.Type).Debug( "roomserver output log: ignoring unknown output type", @@ -107,6 +115,12 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { return nil } +// processMessage updates the list of currently joined hosts in the room +// and then sends the event to the hosts that were joined before the event. +func (s *OutputRoomEventConsumer) processRemotePeek(orp api.OutputNewRemotePeek) error { + return nil +} + // processMessage updates the list of currently joined hosts in the room // and then sends the event to the hosts that were joined before the event. func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) error { @@ -150,6 +164,15 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err return err } + // TODO: track what hosts are peeking (federationsender_received_peeks) + // TODO: rename federationsender_remote_peeks as federationsender_sent_peeks + + // TOOD: add peeking hosts to the joinedHosts list + + // TODO: do housekeeping to evict unrenewed peeking hosts + + // TODO: implement query to let the fedapi check whether a given peek is live or not + // Send the event. return s.queues.SendEvent( &ore.Event, gomatrixserverlib.ServerName(ore.SendAsServer), joinedHostsAtEvent, diff --git a/roomserver/api/api.go b/roomserver/api/api.go index eecefe3225..08a269a82a 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -48,6 +48,12 @@ type RoomserverInternalAPI interface { res *PerformPublishResponse, ) + PerformHandleRemotePeek( + ctx context.Context, + req *PerformHandleRemotePeekRequest, + res *PerformHandleRemotePeekResponse, + ) error + QueryPublishedRooms( ctx context.Context, req *QueryPublishedRoomsRequest, @@ -181,4 +187,4 @@ type RoomserverInternalAPI interface { req *RemoveRoomAliasRequest, response *RemoveRoomAliasResponse, ) error -} +} \ No newline at end of file diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 6433093078..4be566d4f7 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -75,6 +75,16 @@ func (t *RoomserverInternalAPITrace) PerformPublish( util.GetLogger(ctx).Infof("PerformPublish req=%+v res=%+v", js(req), js(res)) } +func (t *RoomserverInternalAPITrace) PerformHandleRemotePeek( + ctx context.Context, + req *PerformHandleRemotePeekRequest, + res *PerformHandleRemotePeekResponse, +) error { + err := t.Impl.PerformHandleRemotePeek(ctx, req, res) + util.GetLogger(ctx).Infof("PerformHandleRemotePeek req=%+v res=%+v", js(req), js(res)) + return err +} + func (t *RoomserverInternalAPITrace) QueryPublishedRooms( ctx context.Context, req *QueryPublishedRoomsRequest, diff --git a/roomserver/api/output.go b/roomserver/api/output.go index 013ebdc833..4163cc2d0b 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -49,6 +49,9 @@ const ( // OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek OutputTypeNewPeek OutputType = "new_peek" + + // OutputTypeNewRemotePeek indicates that the kafka event is an OutputNewRemotePeek + OutputTypeNewRemotePeek OutputType = "new_remote_peek" ) // An OutputEvent is an entry in the roomserver output kafka log. @@ -66,6 +69,8 @@ type OutputEvent struct { RedactedEvent *OutputRedactedEvent `json:"redacted_event,omitempty"` // The content of event with type OutputTypeNewPeek NewPeek *OutputNewPeek `json:"new_peek,omitempty"` + // The content of event with type OutputTypeNewRemotePeek + NewRemotePeek *OutputNewRemotePeek `json:"new_remote_peek,omitempty"` } // An OutputNewRoomEvent is written when the roomserver receives a new event. @@ -208,3 +213,10 @@ type OutputNewPeek struct { UserID string DeviceID string } + +// An OutputNewRemotePeek is written whenever a server starts peeking into a room +type OutputNewRemotePeek struct { + RoomID string + PeekID string + ServerName gomatrixserverlib.ServerName +} diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 0c2d96a7dd..a8967dac15 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -159,3 +159,22 @@ type PerformPublishResponse struct { // If non-nil, the publish request failed. Contains more information why it failed. Error *PerformError } + +type PerformHandleRemotePeekRequest struct { + UserID string `json:"user_id"` + RoomID string `json:"room_id"` + PeekID string `json:"peek_id"` + ServerName gomatrixserverlib.ServerName `json:"server_name"` +} + +type PerformHandleRemotePeekResponse struct { + // Does the room exist on this roomserver? + // If the room doesn't exist this will be false and StateEvents will be empty. + RoomExists bool `json:"room_exists"` + // The room version of the room. + RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"` + // The current state and auth chain events. + // The lists will be in an arbitrary order. + StateEvents []gomatrixserverlib.HeaderedEvent `json:"state_events"` + AuthChainEvents []gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"` +} \ No newline at end of file diff --git a/roomserver/api/query.go b/roomserver/api/query.go index a80fe684cb..0af0307498 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -177,7 +177,7 @@ type QueryStateAndAuthChainRequest struct { // The room ID to query the state in. RoomID string `json:"room_id"` // The list of prev events for the event. Used to calculate the state at - // the event. If empty, assumes current state. + // the event. PrevEventIDs []string `json:"prev_event_ids"` // The list of auth events for the event. Used to calculate the auth chain AuthEventIDs []string `json:"auth_event_ids"` diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 8dc1a170b6..e4f645c1e1 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -23,6 +23,7 @@ type RoomserverInternalAPI struct { *perform.Inviter *perform.Joiner *perform.Peeker + *perform.HandleRemotePeeker *perform.Leaver *perform.Publisher *perform.Backfiller @@ -91,6 +92,10 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen FSAPI: r.fsAPI, Inputer: r.Inputer, } + r.HandleRemotePeeker = &perform.HandleRemotePeeker{ + DB: r.DB, + Inputer: r.Inputer, + } r.Leaver = &perform.Leaver{ Cfg: r.Cfg, DB: r.DB, @@ -137,3 +142,4 @@ func (r *RoomserverInternalAPI) PerformLeave( } return r.WriteOutputEvents(req.RoomID, outputEvents) } + diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 3d0919356e..8fdb23bf77 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -198,8 +198,6 @@ func (r *Peeker) performPeekRoomByID( } } - // TODO: handle federated peeks - err = r.Inputer.WriteOutputEvents(roomID, []api.OutputEvent{ { Type: api.OutputTypeNewPeek, diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index 6402102983..4fddcdfa46 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -335,38 +335,9 @@ func (r *Queryer) QueryStateAndAuthChain( response.RoomVersion = info.RoomVersion var stateEvents []gomatrixserverlib.Event - if len(request.PrevEventIDs) > 0 { - stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) - if err != nil { - return err - } - } else { - // no PrevEventIDs or AuthEventIDs were provided, so return current state instead. - - // XXX: is this right? - roomState := state.NewStateResolution(r.DB, *info) - // no need to resolve state again later - request.ResolveState = false - - var currentStateSnapshotNID types.StateSnapshotNID - _, currentStateSnapshotNID, _, err = - r.DB.LatestEventIDs(ctx, info.RoomNID) - if err != nil { - return err - } - - var stateEntries []types.StateEntry - stateEntries, err = roomState.LoadStateAtSnapshot( - ctx, currentStateSnapshotNID, - ) - if err != nil { - return err - } - - stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries) - if err != nil { - return err - } + stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) + if err != nil { + return err } response.PrevEventsExist = true @@ -379,7 +350,7 @@ func (r *Queryer) QueryStateAndAuthChain( } authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe - authEvents, err := getAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) + authEvents, err := GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) if err != nil { return err } @@ -428,11 +399,11 @@ func (r *Queryer) loadStateAtEventIDs(ctx context.Context, roomInfo types.RoomIn type eventsFromIDs func(context.Context, []string) ([]types.Event, error) -// getAuthChain fetches the auth chain for the given auth events. An auth chain +// GetAuthChain fetches the auth chain for the given auth events. An auth chain // is the list of all events that are referenced in the auth_events section, and // all their auth_events, recursively. The returned set of events contain the // given events. Will *not* error if we don't have all auth events. -func getAuthChain( +func GetAuthChain( ctx context.Context, fn eventsFromIDs, authEventIDs []string, ) ([]gomatrixserverlib.Event, error) { // List of event IDs to fetch. On each pass, these events will be requested diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index 1ff1fc82bb..b013bb2c76 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -25,12 +25,13 @@ const ( RoomserverInputRoomEventsPath = "/roomserver/inputRoomEvents" // Perform operations - RoomserverPerformInvitePath = "/roomserver/performInvite" - RoomserverPerformPeekPath = "/roomserver/performPeek" - RoomserverPerformJoinPath = "/roomserver/performJoin" - RoomserverPerformLeavePath = "/roomserver/performLeave" - RoomserverPerformBackfillPath = "/roomserver/performBackfill" - RoomserverPerformPublishPath = "/roomserver/performPublish" + RoomserverPerformInvitePath = "/roomserver/performInvite" + RoomserverPerformPeekPath = "/roomserver/performPeek" + RoomserverPerformJoinPath = "/roomserver/performJoin" + RoomserverPerformLeavePath = "/roomserver/performLeave" + RoomserverPerformBackfillPath = "/roomserver/performBackfill" + RoomserverPerformPublishPath = "/roomserver/performPublish" + RoomserverPerformHandleRemotePeekPath = "/roomserver/performHandleRemotePeek" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" @@ -203,6 +204,19 @@ func (h *httpRoomserverInternalAPI) PerformPeek( } } +func (h *httpRoomserverInternalAPI) PerformHandleRemotePeek( + ctx context.Context, + request *api.PerformHandleRemotePeekRequest, + response *api.PerformHandleRemotePeekResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformHandleRemotePeek") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverPerformHandleRemotePeekPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + +// XXX: why do some of these Perform's return errors, and others don't? func (h *httpRoomserverInternalAPI) PerformLeave( ctx context.Context, request *api.PerformLeaveRequest, diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index 5816d4d82c..032e2b6e47 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -74,6 +74,19 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + internalAPIMux.Handle(RoomserverPerformHandleRemotePeekPath, + httputil.MakeInternalAPI("performHandleRemotePeek", func(req *http.Request) util.JSONResponse { + var request api.PerformHandleRemotePeekRequest + var response api.PerformHandleRemotePeekResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + if err := r.PerformHandleRemotePeek(req.Context(), &request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(RoomserverPerformPublishPath, httputil.MakeInternalAPI("performPublish", func(req *http.Request) util.JSONResponse { var request api.PerformPublishRequest From 4e96e62923779157a05182f9346392a90b512130 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 22:59:25 +0100 Subject: [PATCH 41/68] rename RemotePeeks as OutboundPeeks --- federationsender/consumers/roomserver.go | 2 +- federationsender/internal/perform.go | 14 +- federationsender/storage/interface.go | 8 +- federationsender/storage/shared/storage.go | 34 ++-- .../storage/sqlite3/remote_peeks_table.go | 176 ------------------ federationsender/storage/sqlite3/storage.go | 20 +- federationsender/storage/tables/interface.go | 14 +- federationsender/types/types.go | 2 +- 8 files changed, 47 insertions(+), 223 deletions(-) delete mode 100644 federationsender/storage/sqlite3/remote_peeks_table.go diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 1f16621f05..16cab62011 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -167,7 +167,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err // TODO: track what hosts are peeking (federationsender_received_peeks) // TODO: rename federationsender_remote_peeks as federationsender_sent_peeks - // TOOD: add peeking hosts to the joinedHosts list + // TODO: add peeking hosts to the joinedHosts list // TODO: do housekeeping to evict unrenewed peeking hosts diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index a31b1cf34e..db84caaf35 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -294,18 +294,18 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( // check whether we're peeking already to try to avoid needlessly // re-peeking on the server. we don't need a transaction for this, // given this is a nice-to-have. - remotePeek, err := r.db.GetRemotePeek(ctx, serverName, roomID, peekID) + outboundPeek, err := r.db.GetOutboundPeek(ctx, serverName, roomID, peekID) if err != nil { return err } renewing := false - if remotePeek != nil { + if outboundPeek != nil { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - if nowMilli > remotePeek.RenewedTimestamp+remotePeek.RenewalInterval { - logrus.Infof("stale remote peek to %s for %s already exists; renewing", serverName, roomID) + if nowMilli > outboundPeek.RenewedTimestamp + outboundPeek.RenewalInterval { + logrus.Infof("stale outbound peek to %s for %s already exists; renewing", serverName, roomID) renewing = true } else { - logrus.Infof("live remote peek to %s for %s already exists", serverName, roomID) + logrus.Infof("live outbound peek to %s for %s already exists", serverName, roomID) return nil } } @@ -339,11 +339,11 @@ func (r *FederationSenderInternalAPI) performPeekUsingServer( // If we've got this far, the remote server is peeking. if renewing { - if err = r.db.RenewRemotePeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { + if err = r.db.RenewOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { return err } } else { - if err = r.db.AddRemotePeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { + if err = r.db.AddOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { return err } } diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index 49d3937bf9..a5bad57652 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -55,8 +55,8 @@ type Database interface { RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) - AddRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error - RenewRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error - GetRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.RemotePeek, error) - GetRemotePeeks(ctx context.Context, roomID string) ([]types.RemotePeek, error) + AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + GetOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.OutboundPeek, error) + GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error) } diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 4985c5eda6..7f2dea0158 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -27,15 +27,15 @@ import ( ) type Database struct { - DB *sql.DB - Writer sqlutil.Writer - FederationSenderQueuePDUs tables.FederationSenderQueuePDUs - FederationSenderQueueEDUs tables.FederationSenderQueueEDUs - FederationSenderQueueJSON tables.FederationSenderQueueJSON - FederationSenderJoinedHosts tables.FederationSenderJoinedHosts - FederationSenderRooms tables.FederationSenderRooms - FederationSenderBlacklist tables.FederationSenderBlacklist - FederationSenderRemotePeeks tables.FederationSenderRemotePeeks + DB *sql.DB + Writer sqlutil.Writer + FederationSenderQueuePDUs tables.FederationSenderQueuePDUs + FederationSenderQueueEDUs tables.FederationSenderQueueEDUs + FederationSenderQueueJSON tables.FederationSenderQueueJSON + FederationSenderJoinedHosts tables.FederationSenderJoinedHosts + FederationSenderRooms tables.FederationSenderRooms + FederationSenderBlacklist tables.FederationSenderBlacklist + FederationSenderOutboundPeeks tables.FederationSenderOutboundPeeks } // An Receipt contains the NIDs of a call to GetNextTransactionPDUs/EDUs. @@ -165,22 +165,22 @@ func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName) } -func (d *Database) AddRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.FederationSenderRemotePeeks.InsertRemotePeek(ctx, txn, serverName, roomID, peekID, renewalInterval) + return d.FederationSenderOutboundPeeks.InsertOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) RenewRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - return d.FederationSenderRemotePeeks.RenewRemotePeek(ctx, txn, serverName, roomID, peekID, renewalInterval) + return d.FederationSenderOutboundPeeks.RenewOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) GetRemotePeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.RemotePeek, error) { - return d.FederationSenderRemotePeeks.SelectRemotePeek(ctx, nil, serverName, roomID, peekID) +func (d *Database) GetOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.OutboundPeek, error) { + return d.FederationSenderOutboundPeeks.SelectOutboundPeek(ctx, nil, serverName, roomID, peekID) } -func (d *Database) GetRemotePeeks(ctx context.Context, roomID string) ([]types.RemotePeek, error) { - return d.FederationSenderRemotePeeks.SelectRemotePeeks(ctx, nil, roomID) +func (d *Database) GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error) { + return d.FederationSenderOutboundPeeks.SelectOutboundPeeks(ctx, nil, roomID) } diff --git a/federationsender/storage/sqlite3/remote_peeks_table.go b/federationsender/storage/sqlite3/remote_peeks_table.go deleted file mode 100644 index 0abadc8dff..0000000000 --- a/federationsender/storage/sqlite3/remote_peeks_table.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2020 The Matrix.org Foundation C.I.C. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package sqlite3 - -import ( - "context" - "database/sql" - "time" - - "github.com/matrix-org/dendrite/federationsender/types" - "github.com/matrix-org/dendrite/internal" - "github.com/matrix-org/dendrite/internal/sqlutil" - "github.com/matrix-org/gomatrixserverlib" -) - -const remotePeeksSchema = ` -CREATE TABLE IF NOT EXISTS federationsender_remote_peeks ( - room_id TEXT NOT NULL, - server_name TEXT NOT NULL, - peek_id TEXT NOT NULL, - creation_ts INTEGER NOT NULL, - renewed_ts INTEGER NOT NULL, - renewal_interval INTEGER NOT NULL, - UNIQUE (room_id, server_name, peek_id) -); -` - -const insertRemotePeekSQL = "" + - "INSERT INTO federationsender_remote_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" - -const selectRemotePeekSQL = "" + - "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" - -const selectRemotePeeksSQL = "" + - "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_remote_peeks WHERE room_id = $1" - -const renewRemotePeekSQL = "" + - "UPDATE federationsender_remote_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" - -const deleteRemotePeekSQL = "" + - "DELETE FROM federationsender_remote_peeks WHERE room_id = $1 and server_name = $2" - -const deleteRemotePeeksSQL = "" + - "DELETE FROM federationsender_remote_peeks WHERE room_id = $1" - -type remotePeeksStatements struct { - db *sql.DB - insertRemotePeekStmt *sql.Stmt - selectRemotePeekStmt *sql.Stmt - selectRemotePeeksStmt *sql.Stmt - renewRemotePeekStmt *sql.Stmt - deleteRemotePeekStmt *sql.Stmt - deleteRemotePeeksStmt *sql.Stmt -} - -func NewSQLiteRemotePeeksTable(db *sql.DB) (s *remotePeeksStatements, err error) { - s = &remotePeeksStatements{ - db: db, - } - _, err = db.Exec(remotePeeksSchema) - if err != nil { - return - } - - if s.insertRemotePeekStmt, err = db.Prepare(insertRemotePeekSQL); err != nil { - return - } - if s.selectRemotePeekStmt, err = db.Prepare(selectRemotePeekSQL); err != nil { - return - } - if s.selectRemotePeeksStmt, err = db.Prepare(selectRemotePeeksSQL); err != nil { - return - } - if s.renewRemotePeekStmt, err = db.Prepare(renewRemotePeekSQL); err != nil { - return - } - if s.deleteRemotePeeksStmt, err = db.Prepare(deleteRemotePeeksSQL); err != nil { - return - } - if s.deleteRemotePeekStmt, err = db.Prepare(deleteRemotePeekSQL); err != nil { - return - } - return -} - -func (s *remotePeeksStatements) InsertRemotePeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, -) (err error) { - nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - stmt := sqlutil.TxStmt(txn, s.insertRemotePeekStmt) - _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) - return -} - -func (s *remotePeeksStatements) RenewRemotePeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, -) (err error) { - nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - _, err = sqlutil.TxStmt(txn, s.renewRemotePeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) - return -} - -func (s *remotePeeksStatements) SelectRemotePeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, -) (*types.RemotePeek, error) { - row := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryRowContext(ctx, roomID) - remotePeek := types.RemotePeek{} - err := row.Scan( - &remotePeek.RoomID, - &remotePeek.ServerName, - &remotePeek.PeekID, - &remotePeek.CreationTimestamp, - &remotePeek.RenewedTimestamp, - &remotePeek.RenewalInterval, - ) - if err == sql.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, err - } - return &remotePeek, nil -} - -func (s *remotePeeksStatements) SelectRemotePeeks( - ctx context.Context, txn *sql.Tx, roomID string, -) (remotePeeks []types.RemotePeek, err error) { - rows, err := sqlutil.TxStmt(txn, s.selectRemotePeeksStmt).QueryContext(ctx, roomID) - if err != nil { - return - } - defer internal.CloseAndLogIfError(ctx, rows, "SelectRemotePeeks: rows.close() failed") - - for rows.Next() { - remotePeek := types.RemotePeek{} - if err = rows.Scan( - &remotePeek.RoomID, - &remotePeek.ServerName, - &remotePeek.PeekID, - &remotePeek.CreationTimestamp, - &remotePeek.RenewedTimestamp, - &remotePeek.RenewalInterval, - ); err != nil { - return - } - remotePeeks = append(remotePeeks, remotePeek) - } - - return remotePeeks, rows.Err() -} - -func (s *remotePeeksStatements) DeleteRemotePeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, -) (err error) { - _, err = sqlutil.TxStmt(txn, s.deleteRemotePeekStmt).ExecContext(ctx, roomID, serverName, peekID) - return -} - -func (s *remotePeeksStatements) DeleteRemotePeeks( - ctx context.Context, txn *sql.Tx, roomID string, -) (err error) { - _, err = sqlutil.TxStmt(txn, s.deleteRemotePeeksStmt).ExecContext(ctx, roomID) - return -} diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index c0317b25d3..35ac10cedb 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -65,20 +65,20 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { if err != nil { return nil, err } - remotePeeks, err := NewSQLiteRemotePeeksTable(d.db) + outboundPeeks, err := NewSQLiteOutboundPeeksTable(d.db) if err != nil { return nil, err } d.Database = shared.Database{ - DB: d.db, - Writer: d.writer, - FederationSenderJoinedHosts: joinedHosts, - FederationSenderQueuePDUs: queuePDUs, - FederationSenderQueueEDUs: queueEDUs, - FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, - FederationSenderBlacklist: blacklist, - FederationSenderRemotePeeks: remotePeeks, + DB: d.db, + Writer: d.writer, + FederationSenderJoinedHosts: joinedHosts, + FederationSenderQueuePDUs: queuePDUs, + FederationSenderQueueEDUs: queueEDUs, + FederationSenderQueueJSON: queueJSON, + FederationSenderRooms: rooms, + FederationSenderBlacklist: blacklist, + FederationSenderOutboundPeeks: outboundPeeks, } if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil { return nil, err diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index e35eac0fb7..e4654f8a99 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -68,11 +68,11 @@ type FederationSenderBlacklist interface { DeleteBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) error } -type FederationSenderRemotePeeks interface { - InsertRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) - RenewRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) - SelectRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (remotePeek *types.RemotePeek, err error) - SelectRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (remotePeeks []types.RemotePeek, err error) - DeleteRemotePeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) - DeleteRemotePeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) +type FederationSenderOutboundPeeks interface { + InsertOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) + RenewOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) + SelectOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (outboundPeek *types.OutboundPeek, err error) + SelectOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (outboundPeeks []types.OutboundPeek, err error) + DeleteOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) + DeleteOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) } diff --git a/federationsender/types/types.go b/federationsender/types/types.go index d593277964..34390fac1c 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -50,7 +50,7 @@ func (e EventIDMismatchError) Error() string { ) } -type RemotePeek struct { +type OutboundPeek struct { PeekID string RoomID string ServerName gomatrixserverlib.ServerName From 59e2be7113021a4a86d48dedb8059171b6207cbb Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 23:01:06 +0100 Subject: [PATCH 42/68] rename remote_peeks_table as outbound_peeks_table --- .../storage/sqlite3/outbound_peeks_table.go | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 federationsender/storage/sqlite3/outbound_peeks_table.go diff --git a/federationsender/storage/sqlite3/outbound_peeks_table.go b/federationsender/storage/sqlite3/outbound_peeks_table.go new file mode 100644 index 0000000000..f3a81e7fb2 --- /dev/null +++ b/federationsender/storage/sqlite3/outbound_peeks_table.go @@ -0,0 +1,176 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + "time" + + "github.com/matrix-org/dendrite/federationsender/types" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const outboundPeeksSchema = ` +CREATE TABLE IF NOT EXISTS federationsender_outbound_peeks ( + room_id TEXT NOT NULL, + server_name TEXT NOT NULL, + peek_id TEXT NOT NULL, + creation_ts INTEGER NOT NULL, + renewed_ts INTEGER NOT NULL, + renewal_interval INTEGER NOT NULL, + UNIQUE (room_id, server_name, peek_id) +); +` + +const insertOutboundPeekSQL = "" + + "INSERT INTO federationsender_outbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" + +const selectOutboundPeekSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" + +const selectOutboundPeeksSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1" + +const renewOutboundPeekSQL = "" + + "UPDATE federationsender_outbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" + +const deleteOutboundPeekSQL = "" + + "DELETE FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2" + +const deleteOutboundPeeksSQL = "" + + "DELETE FROM federationsender_outbound_peeks WHERE room_id = $1" + +type outboundPeeksStatements struct { + db *sql.DB + insertOutboundPeekStmt *sql.Stmt + selectOutboundPeekStmt *sql.Stmt + selectOutboundPeeksStmt *sql.Stmt + renewOutboundPeekStmt *sql.Stmt + deleteOutboundPeekStmt *sql.Stmt + deleteOutboundPeeksStmt *sql.Stmt +} + +func NewSQLiteOutboundPeeksTable(db *sql.DB) (s *outboundPeeksStatements, err error) { + s = &outboundPeeksStatements{ + db: db, + } + _, err = db.Exec(outboundPeeksSchema) + if err != nil { + return + } + + if s.insertOutboundPeekStmt, err = db.Prepare(insertOutboundPeekSQL); err != nil { + return + } + if s.selectOutboundPeekStmt, err = db.Prepare(selectOutboundPeekSQL); err != nil { + return + } + if s.selectOutboundPeeksStmt, err = db.Prepare(selectOutboundPeeksSQL); err != nil { + return + } + if s.renewOutboundPeekStmt, err = db.Prepare(renewOutboundPeekSQL); err != nil { + return + } + if s.deleteOutboundPeeksStmt, err = db.Prepare(deleteOutboundPeeksSQL); err != nil { + return + } + if s.deleteOutboundPeekStmt, err = db.Prepare(deleteOutboundPeekSQL); err != nil { + return + } + return +} + +func (s *outboundPeeksStatements) InsertOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + stmt := sqlutil.TxStmt(txn, s.insertOutboundPeekStmt) + _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) + return +} + +func (s *outboundPeeksStatements) RenewOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err = sqlutil.TxStmt(txn, s.renewOutboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) + return +} + +func (s *outboundPeeksStatements) SelectOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (*types.OutboundPeek, error) { + row := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryRowContext(ctx, roomID) + outboundPeek := types.OutboundPeek{} + err := row.Scan( + &outboundPeek.RoomID, + &outboundPeek.ServerName, + &outboundPeek.PeekID, + &outboundPeek.CreationTimestamp, + &outboundPeek.RenewedTimestamp, + &outboundPeek.RenewalInterval, + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return &outboundPeek, nil +} + +func (s *outboundPeeksStatements) SelectOutboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (outboundPeeks []types.OutboundPeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectOutboundPeeks: rows.close() failed") + + for rows.Next() { + outboundPeek := types.OutboundPeek{} + if err = rows.Scan( + &outboundPeek.RoomID, + &outboundPeek.ServerName, + &outboundPeek.PeekID, + &outboundPeek.CreationTimestamp, + &outboundPeek.RenewedTimestamp, + &outboundPeek.RenewalInterval, + ); err != nil { + return + } + outboundPeeks = append(outboundPeeks, outboundPeek) + } + + return outboundPeeks, rows.Err() +} + +func (s *outboundPeeksStatements) DeleteOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteOutboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID) + return +} + +func (s *outboundPeeksStatements) DeleteOutboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteOutboundPeeksStmt).ExecContext(ctx, roomID) + return +} From 36e32f14e6b9337da265a6a1c8453a016b091e7e Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 23:01:43 +0100 Subject: [PATCH 43/68] add perform_handle_remote_peek.go --- .../perform/perform_handle_remote_peek.go | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 roomserver/internal/perform/perform_handle_remote_peek.go diff --git a/roomserver/internal/perform/perform_handle_remote_peek.go b/roomserver/internal/perform/perform_handle_remote_peek.go new file mode 100644 index 0000000000..bcacdc7265 --- /dev/null +++ b/roomserver/internal/perform/perform_handle_remote_peek.go @@ -0,0 +1,118 @@ +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package perform + +import ( + "context" + + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/roomserver/internal/helpers" + "github.com/matrix-org/dendrite/roomserver/internal/query" + "github.com/matrix-org/dendrite/roomserver/internal/input" + "github.com/matrix-org/dendrite/roomserver/state" + "github.com/matrix-org/dendrite/roomserver/storage" + "github.com/matrix-org/dendrite/roomserver/types" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +type HandleRemotePeeker struct { + DB storage.Database + Inputer *input.Inputer +} + +// PerformHandleRemotePeek handles peeking into matrix rooms, including over +// federation by talking to the federationsender. called when a remote server +// initiates a /peek over federation. +// +// It should atomically figure out the current state of the room (for the +// response to /peek) while adding the remotepeek to the kafka stream so the +// fed sender can start sending peeked events without a race between the state +// snapshot and the stream of peeked events. +func (r *HandleRemotePeeker) PerformHandleRemotePeek( + ctx context.Context, + request *api.PerformHandleRemotePeekRequest, + response *api.PerformHandleRemotePeekResponse, +) error { + info, err := r.DB.RoomInfo(ctx, request.RoomID) + if err != nil { + return err + } + if info == nil || info.IsStub { + return nil + } + response.RoomExists = true + response.RoomVersion = info.RoomVersion + + var stateEvents []gomatrixserverlib.Event + + // XXX: is this right? + roomState := state.NewStateResolution(r.DB, *info) + + var currentStateSnapshotNID types.StateSnapshotNID + _, currentStateSnapshotNID, _, err = + r.DB.LatestEventIDs(ctx, info.RoomNID) + if err != nil { + return err + } + var stateEntries []types.StateEntry + stateEntries, err = roomState.LoadStateAtSnapshot( + ctx, currentStateSnapshotNID, + ) + if err != nil { + return err + } + stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries) + if err != nil { + return err + } + + // get the auth event IDs for the current state events + var authEventIDs []string + for _, se := range stateEvents { + authEventIDs = append(authEventIDs, se.AuthEventIDs()...) + } + authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe + + authEvents, err := query.GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) + if err != nil { + return err + } + + for _, event := range stateEvents { + response.StateEvents = append(response.StateEvents, event.Headered(info.RoomVersion)) + } + + for _, event := range authEvents { + response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion)) + } + + // FIXME: there's a race here - we really should be atomically telling the + // federationsender to start sending peek events alongside having captured + // the current state, but it's unclear if/how we can do that. + + err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{ + { + Type: api.OutputTypeNewRemotePeek, + NewRemotePeek: &api.OutputNewRemotePeek{ + RoomID: request.RoomID, + PeekID: request.PeekID, + ServerName: request.ServerName, + }, + }, + }) + return err +} + From 0dc422cdd8c69d53edbc09d6b2a79711a97ef4df Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 23:53:46 +0100 Subject: [PATCH 44/68] flesh out federation doc --- docs/peeking.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/peeking.md b/docs/peeking.md index 78bd6f797d..194da65206 100644 --- a/docs/peeking.md +++ b/docs/peeking.md @@ -1,19 +1,26 @@ ## Peeking -Peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753). +Local peeking is implemented as per [MSC2753](https://github.com/matrix-org/matrix-doc/pull/2753). Implementationwise, this means: * Users call `/peek` and `/unpeek` on the clientapi from a given device. * The clientapi delegates these via HTTP to the roomserver, which coordinates peeking in general for a given room * The roomserver writes an NewPeek event into the kafka log headed to the syncserver - * The syncserver tracks the existence of the local peek in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peek` section of the /sync response. + * The syncserver tracks the existence of the local peek in the syncapi_peeks table in its DB, and then starts waking up the peeking devices for the room in question, putting it in the `peek` section of the /sync response. -Questions (given this is [my](https://github.com/ara4n) first time hacking on Dendrite): - * The whole clientapi -> roomserver -> syncapi flow to initiate a peek seems very indirect. Is there a reason not to just let syncapi itself host the implementation of `/peek`? +Peeking over federation is implemented as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444). -In future, peeking over federation will be added as per [MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444). - * The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated `/peek` - * The `federationsender` tracks the existence of the remote peek in question +For requests to peek our rooms ("inbound peeks"): + * Remote servers call `/peek` on federationapi + * The federationapi queries the federationsender to check if this is renewing an inbound peek or not. + * If not, it hits the PerformHandleRemotePeek on the roomserver to ask it for the current state of the room. + * The roomserver atomically (in theory) adds a NewRemotePeek to its kafka stream to tell the federationserver to start peeking. + * The federationsender receives the event, tracks the inbound peek in the federationsender_inbound_peeks table, and starts sending events to the peeking server. + * The federationsender evicts stale inbound peeks which haven't been renewed. + +For peeking into other server's rooms ("outbound peeks"): + * The `roomserver` will kick the `federationsender` much as it does for a federated `/join` in order to trigger a federated outbound `/peek` + * The `federationsender` tracks the existence of the outbound peek in in its federationsender_outbound_peeks table. * The `federationsender` regularly renews the remote peek as long as there are still peeking devices syncing for it. * TBD: how do we tell if there are no devices currently syncing for a given peeked room? The syncserver needs to tell the roomserver somehow who then needs to warn the federationsender. \ No newline at end of file From 71732f2c28ada4a898c39b98a958c689da3ca1b2 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 12 Sep 2020 23:54:46 +0100 Subject: [PATCH 45/68] add inbound peeks table and hook it up --- federationapi/routing/peek.go | 6 +- federationsender/consumers/roomserver.go | 22 ++- federationsender/storage/interface.go | 9 +- federationsender/storage/shared/storage.go | 25 ++- .../storage/sqlite3/inbound_peeks_table.go | 176 ++++++++++++++++++ .../storage/sqlite3/outbound_peeks_table.go | 4 +- federationsender/storage/tables/interface.go | 13 +- federationsender/types/types.go | 11 ++ roomserver/api/output.go | 2 + roomserver/api/perform.go | 1 + .../perform/perform_handle_remote_peek.go | 1 + 11 files changed, 252 insertions(+), 18 deletions(-) create mode 100644 federationsender/storage/sqlite3/inbound_peeks_table.go diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index 423f847310..a6d0739c6a 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -64,6 +64,9 @@ func Peek( // TODO: Check history visibility + // tell the peeking server to renew every hour + renewalInterval := int64(60 * 60 * 1000 * 1000) + var response api.PerformHandleRemotePeekResponse err := rsAPI.PerformHandleRemotePeek( httpReq.Context(), @@ -71,6 +74,7 @@ func Peek( RoomID: roomID, PeekID: peekID, ServerName: request.Origin(), + RenewalInterval: renewalInterval, }, &response, ) @@ -89,7 +93,7 @@ func Peek( StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), RoomVersion: response.RoomVersion, - RenewalInterval: 60 * 60 * 1000 * 1000, // one hour + RenewalInterval: renewalInterval, }, } } diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 16cab62011..6215edb24b 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -115,10 +115,10 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { return nil } -// processMessage updates the list of currently joined hosts in the room -// and then sends the event to the hosts that were joined before the event. +// processRemotePeek adds a new inbound peek (replacing the existing one if any) +// causing the federationsender to start sending messages to the peeking server func (s *OutputRoomEventConsumer) processRemotePeek(orp api.OutputNewRemotePeek) error { - return nil + return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } // processMessage updates the list of currently joined hosts in the room @@ -164,11 +164,6 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err return err } - // TODO: track what hosts are peeking (federationsender_received_peeks) - // TODO: rename federationsender_remote_peeks as federationsender_sent_peeks - - // TODO: add peeking hosts to the joinedHosts list - // TODO: do housekeeping to evict unrenewed peeking hosts // TODO: implement query to let the fedapi check whether a given peek is live or not @@ -180,7 +175,7 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err } // joinedHostsAtEvent works out a list of matrix servers that were joined to -// the room at the event. +// the room at the event (including peeking ones) // It is important to use the state at the event for sending messages because: // 1) We shouldn't send messages to servers that weren't in the room. // 2) If a server is kicked from the rooms it should still be told about the @@ -231,6 +226,15 @@ func (s *OutputRoomEventConsumer) joinedHostsAtEvent( joined[joinedHost.ServerName] = true } + // handle peeking hosts + inboundPeeks, err := s.db.GetInboundPeeks(context.TODO(), ore.Event.RoomID()) + if err != nil { + return nil, err + } + for _, inboundPeek := range inboundPeeks { + joined[inboundPeek.ServerName] = true + } + var result []gomatrixserverlib.ServerName for serverName, include := range joined { if include { diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index a5bad57652..035c0d2653 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -55,8 +55,13 @@ type Database interface { RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) - AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error - RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error + AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error + RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error GetOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.OutboundPeek, error) GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error) + + AddInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error + RenewInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error + GetInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.InboundPeek, error) + GetInboundPeeks(ctx context.Context, roomID string) ([]types.InboundPeek, error) } diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 7f2dea0158..021322d979 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -36,6 +36,7 @@ type Database struct { FederationSenderRooms tables.FederationSenderRooms FederationSenderBlacklist tables.FederationSenderBlacklist FederationSenderOutboundPeeks tables.FederationSenderOutboundPeeks + FederationSenderInboundPeeks tables.FederationSenderInboundPeeks } // An Receipt contains the NIDs of a call to GetNextTransactionPDUs/EDUs. @@ -165,13 +166,13 @@ func (d *Database) IsServerBlacklisted(serverName gomatrixserverlib.ServerName) return d.FederationSenderBlacklist.SelectBlacklist(context.TODO(), nil, serverName) } -func (d *Database) AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) AddOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { return d.FederationSenderOutboundPeeks.InsertOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) } -func (d *Database) RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) error { +func (d *Database) RenewOutboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error { return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { return d.FederationSenderOutboundPeeks.RenewOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) }) @@ -184,3 +185,23 @@ func (d *Database) GetOutboundPeek(ctx context.Context, serverName gomatrixserve func (d *Database) GetOutboundPeeks(ctx context.Context, roomID string) ([]types.OutboundPeek, error) { return d.FederationSenderOutboundPeeks.SelectOutboundPeeks(ctx, nil, roomID) } + +func (d *Database) AddInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.FederationSenderInboundPeeks.InsertInboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) + }) +} + +func (d *Database) RenewInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.FederationSenderInboundPeeks.RenewInboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) + }) +} + +func (d *Database) GetInboundPeek(ctx context.Context, serverName gomatrixserverlib.ServerName, roomID, peekID string) (*types.InboundPeek, error) { + return d.FederationSenderInboundPeeks.SelectInboundPeek(ctx, nil, serverName, roomID, peekID) +} + +func (d *Database) GetInboundPeeks(ctx context.Context, roomID string) ([]types.InboundPeek, error) { + return d.FederationSenderInboundPeeks.SelectInboundPeeks(ctx, nil, roomID) +} diff --git a/federationsender/storage/sqlite3/inbound_peeks_table.go b/federationsender/storage/sqlite3/inbound_peeks_table.go new file mode 100644 index 0000000000..2f1269ac0b --- /dev/null +++ b/federationsender/storage/sqlite3/inbound_peeks_table.go @@ -0,0 +1,176 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqlite3 + +import ( + "context" + "database/sql" + "time" + + "github.com/matrix-org/dendrite/federationsender/types" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const inboundPeeksSchema = ` +CREATE TABLE IF NOT EXISTS federationsender_inbound_peeks ( + room_id TEXT NOT NULL, + server_name TEXT NOT NULL, + peek_id TEXT NOT NULL, + creation_ts INTEGER NOT NULL, + renewed_ts INTEGER NOT NULL, + renewal_interval INTEGER NOT NULL, + UNIQUE (room_id, server_name, peek_id) +); +` + +const insertInboundPeekSQL = "" + + "INSERT INTO federationsender_inbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" + +const selectInboundPeekSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" + +const selectInboundPeeksSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1" + +const renewInboundPeekSQL = "" + + "UPDATE federationsender_inbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" + +const deleteInboundPeekSQL = "" + + "DELETE FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2" + +const deleteInboundPeeksSQL = "" + + "DELETE FROM federationsender_inbound_peeks WHERE room_id = $1" + +type inboundPeeksStatements struct { + db *sql.DB + insertInboundPeekStmt *sql.Stmt + selectInboundPeekStmt *sql.Stmt + selectInboundPeeksStmt *sql.Stmt + renewInboundPeekStmt *sql.Stmt + deleteInboundPeekStmt *sql.Stmt + deleteInboundPeeksStmt *sql.Stmt +} + +func NewSQLiteInboundPeeksTable(db *sql.DB) (s *inboundPeeksStatements, err error) { + s = &inboundPeeksStatements{ + db: db, + } + _, err = db.Exec(inboundPeeksSchema) + if err != nil { + return + } + + if s.insertInboundPeekStmt, err = db.Prepare(insertInboundPeekSQL); err != nil { + return + } + if s.selectInboundPeekStmt, err = db.Prepare(selectInboundPeekSQL); err != nil { + return + } + if s.selectInboundPeeksStmt, err = db.Prepare(selectInboundPeeksSQL); err != nil { + return + } + if s.renewInboundPeekStmt, err = db.Prepare(renewInboundPeekSQL); err != nil { + return + } + if s.deleteInboundPeeksStmt, err = db.Prepare(deleteInboundPeeksSQL); err != nil { + return + } + if s.deleteInboundPeekStmt, err = db.Prepare(deleteInboundPeekSQL); err != nil { + return + } + return +} + +func (s *inboundPeeksStatements) InsertInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + stmt := sqlutil.TxStmt(txn, s.insertInboundPeekStmt) + _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) + return +} + +func (s *inboundPeeksStatements) RenewInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err = sqlutil.TxStmt(txn, s.renewInboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) + return +} + +func (s *inboundPeeksStatements) SelectInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (*types.InboundPeek, error) { + row := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryRowContext(ctx, roomID) + inboundPeek := types.InboundPeek{} + err := row.Scan( + &inboundPeek.RoomID, + &inboundPeek.ServerName, + &inboundPeek.PeekID, + &inboundPeek.CreationTimestamp, + &inboundPeek.RenewedTimestamp, + &inboundPeek.RenewalInterval, + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return &inboundPeek, nil +} + +func (s *inboundPeeksStatements) SelectInboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (inboundPeeks []types.InboundPeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectInboundPeeks: rows.close() failed") + + for rows.Next() { + inboundPeek := types.InboundPeek{} + if err = rows.Scan( + &inboundPeek.RoomID, + &inboundPeek.ServerName, + &inboundPeek.PeekID, + &inboundPeek.CreationTimestamp, + &inboundPeek.RenewedTimestamp, + &inboundPeek.RenewalInterval, + ); err != nil { + return + } + inboundPeeks = append(inboundPeeks, inboundPeek) + } + + return inboundPeeks, rows.Err() +} + +func (s *inboundPeeksStatements) DeleteInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteInboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID) + return +} + +func (s *inboundPeeksStatements) DeleteInboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteInboundPeeksStmt).ExecContext(ctx, roomID) + return +} diff --git a/federationsender/storage/sqlite3/outbound_peeks_table.go b/federationsender/storage/sqlite3/outbound_peeks_table.go index f3a81e7fb2..67edda4d3d 100644 --- a/federationsender/storage/sqlite3/outbound_peeks_table.go +++ b/federationsender/storage/sqlite3/outbound_peeks_table.go @@ -96,7 +96,7 @@ func NewSQLiteOutboundPeeksTable(db *sql.DB) (s *outboundPeeksStatements, err er } func (s *outboundPeeksStatements) InsertOutboundPeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) stmt := sqlutil.TxStmt(txn, s.insertOutboundPeekStmt) @@ -105,7 +105,7 @@ func (s *outboundPeeksStatements) InsertOutboundPeek( } func (s *outboundPeeksStatements) RenewOutboundPeek( - ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int, + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, ) (err error) { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) _, err = sqlutil.TxStmt(txn, s.renewOutboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index e4654f8a99..a2069f078e 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -69,10 +69,19 @@ type FederationSenderBlacklist interface { } type FederationSenderOutboundPeeks interface { - InsertOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) - RenewOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int) (err error) + InsertOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) + RenewOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) SelectOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (outboundPeek *types.OutboundPeek, err error) SelectOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (outboundPeeks []types.OutboundPeek, err error) DeleteOutboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) DeleteOutboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) } + +type FederationSenderInboundPeeks interface { + InsertInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) + RenewInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) + SelectInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (inboundPeek *types.InboundPeek, err error) + SelectInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (inboundPeeks []types.InboundPeek, err error) + DeleteInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) + DeleteInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) +} diff --git a/federationsender/types/types.go b/federationsender/types/types.go index 34390fac1c..90da310c94 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -50,6 +50,7 @@ func (e EventIDMismatchError) Error() string { ) } +// tracks peeks we're performing on another server over federation type OutboundPeek struct { PeekID string RoomID string @@ -58,3 +59,13 @@ type OutboundPeek struct { RenewedTimestamp int64 RenewalInterval int64 } + +// tracks peeks other servers are performing on us over federation +type InboundPeek struct { + PeekID string + RoomID string + ServerName gomatrixserverlib.ServerName + CreationTimestamp int64 + RenewedTimestamp int64 + RenewalInterval int64 +} diff --git a/roomserver/api/output.go b/roomserver/api/output.go index 4163cc2d0b..ec4186a5ee 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -219,4 +219,6 @@ type OutputNewRemotePeek struct { RoomID string PeekID string ServerName gomatrixserverlib.ServerName + // how often we told the peeking server to renew the peek + RenewalInterval int64 } diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index a8967dac15..b7c713330d 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -165,6 +165,7 @@ type PerformHandleRemotePeekRequest struct { RoomID string `json:"room_id"` PeekID string `json:"peek_id"` ServerName gomatrixserverlib.ServerName `json:"server_name"` + RenewalInterval int64 `json:"renewal_interval"` } type PerformHandleRemotePeekResponse struct { diff --git a/roomserver/internal/perform/perform_handle_remote_peek.go b/roomserver/internal/perform/perform_handle_remote_peek.go index bcacdc7265..a2aec73a73 100644 --- a/roomserver/internal/perform/perform_handle_remote_peek.go +++ b/roomserver/internal/perform/perform_handle_remote_peek.go @@ -110,6 +110,7 @@ func (r *HandleRemotePeeker) PerformHandleRemotePeek( RoomID: request.RoomID, PeekID: request.PeekID, ServerName: request.ServerName, + RenewalInterval: request.RenewalInterval, }, }, }) From 8f203febc1016c0b218baf3c4f4b815669fa17e0 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 13 Sep 2020 00:04:02 +0100 Subject: [PATCH 46/68] rename ambiguous RemotePeek as InboundPeek --- docs/peeking.md | 4 ++-- federationapi/routing/peek.go | 8 ++++---- federationsender/consumers/roomserver.go | 10 +++++----- roomserver/api/api.go | 6 +++--- roomserver/api/api_trace.go | 10 +++++----- roomserver/api/output.go | 12 ++++++------ roomserver/api/perform.go | 4 ++-- roomserver/internal/api.go | 4 ++-- ...le_remote_peek.go => perform_inbound_peek.go} | 16 ++++++++-------- roomserver/inthttp/client.go | 12 ++++++------ roomserver/inthttp/server.go | 10 +++++----- 11 files changed, 48 insertions(+), 48 deletions(-) rename roomserver/internal/perform/{perform_handle_remote_peek.go => perform_inbound_peek.go} (89%) diff --git a/docs/peeking.md b/docs/peeking.md index 194da65206..60f3590721 100644 --- a/docs/peeking.md +++ b/docs/peeking.md @@ -13,8 +13,8 @@ Peeking over federation is implemented as per [MSC2444](https://github.com/matri For requests to peek our rooms ("inbound peeks"): * Remote servers call `/peek` on federationapi * The federationapi queries the federationsender to check if this is renewing an inbound peek or not. - * If not, it hits the PerformHandleRemotePeek on the roomserver to ask it for the current state of the room. - * The roomserver atomically (in theory) adds a NewRemotePeek to its kafka stream to tell the federationserver to start peeking. + * If not, it hits the PerformInboundPeek on the roomserver to ask it for the current state of the room. + * The roomserver atomically (in theory) adds a NewInboundPeek to its kafka stream to tell the federationserver to start peeking. * The federationsender receives the event, tracks the inbound peek in the federationsender_inbound_peeks table, and starts sending events to the peeking server. * The federationsender evicts stale inbound peeks which haven't been renewed. diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index a6d0739c6a..6fb3aa7ce8 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -24,7 +24,7 @@ import ( "github.com/matrix-org/util" ) -// Peek implements the SS /peek API +// Peek implements the SS /peek API, handling inbound peeks func Peek( httpReq *http.Request, request *gomatrixserverlib.FederationRequest, @@ -67,10 +67,10 @@ func Peek( // tell the peeking server to renew every hour renewalInterval := int64(60 * 60 * 1000 * 1000) - var response api.PerformHandleRemotePeekResponse - err := rsAPI.PerformHandleRemotePeek( + var response api.PerformInboundPeekResponse + err := rsAPI.PerformInboundPeek( httpReq.Context(), - &api.PerformHandleRemotePeekRequest{ + &api.PerformInboundPeekRequest{ RoomID: roomID, PeekID: peekID, ServerName: request.Origin(), diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 6215edb24b..182feb49c0 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -97,10 +97,10 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { }).Panicf("roomserver output log: write room event failure") return nil } - case api.OutputTypeNewRemotePeek: - if err := s.processRemotePeek(*output.NewRemotePeek); err != nil { + case api.OutputTypeNewInboundPeek: + if err := s.processInboundPeek(*output.NewInboundPeek); err != nil { log.WithFields(log.Fields{ - "event": output.NewRemotePeek, + "event": output.NewInboundPeek, log.ErrorKey: err, }).Panicf("roomserver output log: remote peek event failure") return nil @@ -115,9 +115,9 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { return nil } -// processRemotePeek adds a new inbound peek (replacing the existing one if any) +// processInboundPeek starts tracking a new federated inbound peek (replacing the existing one if any) // causing the federationsender to start sending messages to the peeking server -func (s *OutputRoomEventConsumer) processRemotePeek(orp api.OutputNewRemotePeek) error { +func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPeek) error { return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 08a269a82a..cc0237e6e4 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -48,10 +48,10 @@ type RoomserverInternalAPI interface { res *PerformPublishResponse, ) - PerformHandleRemotePeek( + PerformInboundPeek( ctx context.Context, - req *PerformHandleRemotePeekRequest, - res *PerformHandleRemotePeekResponse, + req *PerformInboundPeekRequest, + res *PerformInboundPeekResponse, ) error QueryPublishedRooms( diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 4be566d4f7..6a14421633 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -75,13 +75,13 @@ func (t *RoomserverInternalAPITrace) PerformPublish( util.GetLogger(ctx).Infof("PerformPublish req=%+v res=%+v", js(req), js(res)) } -func (t *RoomserverInternalAPITrace) PerformHandleRemotePeek( +func (t *RoomserverInternalAPITrace) PerformInboundPeek( ctx context.Context, - req *PerformHandleRemotePeekRequest, - res *PerformHandleRemotePeekResponse, + req *PerformInboundPeekRequest, + res *PerformInboundPeekResponse, ) error { - err := t.Impl.PerformHandleRemotePeek(ctx, req, res) - util.GetLogger(ctx).Infof("PerformHandleRemotePeek req=%+v res=%+v", js(req), js(res)) + err := t.Impl.PerformInboundPeek(ctx, req, res) + util.GetLogger(ctx).Infof("PerformInboundPeek req=%+v res=%+v", js(req), js(res)) return err } diff --git a/roomserver/api/output.go b/roomserver/api/output.go index ec4186a5ee..ea0c7eefec 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -50,8 +50,8 @@ const ( // OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek OutputTypeNewPeek OutputType = "new_peek" - // OutputTypeNewRemotePeek indicates that the kafka event is an OutputNewRemotePeek - OutputTypeNewRemotePeek OutputType = "new_remote_peek" + // OutputTypeNewInboundPeek indicates that the kafka event is an OutputNewInboundPeek + OutputTypeNewInboundPeek OutputType = "new_remote_peek" ) // An OutputEvent is an entry in the roomserver output kafka log. @@ -69,8 +69,8 @@ type OutputEvent struct { RedactedEvent *OutputRedactedEvent `json:"redacted_event,omitempty"` // The content of event with type OutputTypeNewPeek NewPeek *OutputNewPeek `json:"new_peek,omitempty"` - // The content of event with type OutputTypeNewRemotePeek - NewRemotePeek *OutputNewRemotePeek `json:"new_remote_peek,omitempty"` + // The content of event with type OutputTypeNewInboundPeek + NewInboundPeek *OutputNewInboundPeek `json:"new_remote_peek,omitempty"` } // An OutputNewRoomEvent is written when the roomserver receives a new event. @@ -214,8 +214,8 @@ type OutputNewPeek struct { DeviceID string } -// An OutputNewRemotePeek is written whenever a server starts peeking into a room -type OutputNewRemotePeek struct { +// An OutputNewInboundPeek is written whenever a server starts peeking into a room +type OutputNewInboundPeek struct { RoomID string PeekID string ServerName gomatrixserverlib.ServerName diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index b7c713330d..3b002474ee 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -160,7 +160,7 @@ type PerformPublishResponse struct { Error *PerformError } -type PerformHandleRemotePeekRequest struct { +type PerformInboundPeekRequest struct { UserID string `json:"user_id"` RoomID string `json:"room_id"` PeekID string `json:"peek_id"` @@ -168,7 +168,7 @@ type PerformHandleRemotePeekRequest struct { RenewalInterval int64 `json:"renewal_interval"` } -type PerformHandleRemotePeekResponse struct { +type PerformInboundPeekResponse struct { // Does the room exist on this roomserver? // If the room doesn't exist this will be false and StateEvents will be empty. RoomExists bool `json:"room_exists"` diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index e4f645c1e1..c1414c09de 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -23,7 +23,7 @@ type RoomserverInternalAPI struct { *perform.Inviter *perform.Joiner *perform.Peeker - *perform.HandleRemotePeeker + *perform.InboundPeeker *perform.Leaver *perform.Publisher *perform.Backfiller @@ -92,7 +92,7 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen FSAPI: r.fsAPI, Inputer: r.Inputer, } - r.HandleRemotePeeker = &perform.HandleRemotePeeker{ + r.InboundPeeker = &perform.InboundPeeker{ DB: r.DB, Inputer: r.Inputer, } diff --git a/roomserver/internal/perform/perform_handle_remote_peek.go b/roomserver/internal/perform/perform_inbound_peek.go similarity index 89% rename from roomserver/internal/perform/perform_handle_remote_peek.go rename to roomserver/internal/perform/perform_inbound_peek.go index a2aec73a73..e6520b5735 100644 --- a/roomserver/internal/perform/perform_handle_remote_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -28,23 +28,23 @@ import ( "github.com/matrix-org/util" ) -type HandleRemotePeeker struct { +type InboundPeeker struct { DB storage.Database Inputer *input.Inputer } -// PerformHandleRemotePeek handles peeking into matrix rooms, including over +// PerformInboundPeek handles peeking into matrix rooms, including over // federation by talking to the federationsender. called when a remote server // initiates a /peek over federation. // // It should atomically figure out the current state of the room (for the -// response to /peek) while adding the remotepeek to the kafka stream so the +// response to /peek) while adding the new inbound peek to the kafka stream so the // fed sender can start sending peeked events without a race between the state // snapshot and the stream of peeked events. -func (r *HandleRemotePeeker) PerformHandleRemotePeek( +func (r *InboundPeeker) PerformInboundPeek( ctx context.Context, - request *api.PerformHandleRemotePeekRequest, - response *api.PerformHandleRemotePeekResponse, + request *api.PerformInboundPeekRequest, + response *api.PerformInboundPeekResponse, ) error { info, err := r.DB.RoomInfo(ctx, request.RoomID) if err != nil { @@ -105,8 +105,8 @@ func (r *HandleRemotePeeker) PerformHandleRemotePeek( err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{ { - Type: api.OutputTypeNewRemotePeek, - NewRemotePeek: &api.OutputNewRemotePeek{ + Type: api.OutputTypeNewInboundPeek, + NewInboundPeek: &api.OutputNewInboundPeek{ RoomID: request.RoomID, PeekID: request.PeekID, ServerName: request.ServerName, diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index b013bb2c76..e70d2f0ce4 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -31,7 +31,7 @@ const ( RoomserverPerformLeavePath = "/roomserver/performLeave" RoomserverPerformBackfillPath = "/roomserver/performBackfill" RoomserverPerformPublishPath = "/roomserver/performPublish" - RoomserverPerformHandleRemotePeekPath = "/roomserver/performHandleRemotePeek" + RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" @@ -204,15 +204,15 @@ func (h *httpRoomserverInternalAPI) PerformPeek( } } -func (h *httpRoomserverInternalAPI) PerformHandleRemotePeek( +func (h *httpRoomserverInternalAPI) PerformInboundPeek( ctx context.Context, - request *api.PerformHandleRemotePeekRequest, - response *api.PerformHandleRemotePeekResponse, + request *api.PerformInboundPeekRequest, + response *api.PerformInboundPeekResponse, ) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "PerformHandleRemotePeek") + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInboundPeek") defer span.Finish() - apiURL := h.roomserverURL + RoomserverPerformHandleRemotePeekPath + apiURL := h.roomserverURL + RoomserverPerformInboundPeekPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index 032e2b6e47..ffa8acf6ea 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -74,14 +74,14 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) - internalAPIMux.Handle(RoomserverPerformHandleRemotePeekPath, - httputil.MakeInternalAPI("performHandleRemotePeek", func(req *http.Request) util.JSONResponse { - var request api.PerformHandleRemotePeekRequest - var response api.PerformHandleRemotePeekResponse + internalAPIMux.Handle(RoomserverPerformInboundPeekPath, + httputil.MakeInternalAPI("performInboundPeek", func(req *http.Request) util.JSONResponse { + var request api.PerformInboundPeekRequest + var response api.PerformInboundPeekResponse if err := json.NewDecoder(req.Body).Decode(&request); err != nil { return util.MessageResponse(http.StatusBadRequest, err.Error()) } - if err := r.PerformHandleRemotePeek(req.Context(), &request, &response); err != nil { + if err := r.PerformInboundPeek(req.Context(), &request, &response); err != nil { return util.ErrorResponse(err) } return util.JSONResponse{Code: http.StatusOK, JSON: &response} From 3caae7913baebc46c88c46253f5c570b41fb851d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 13 Sep 2020 00:14:24 +0100 Subject: [PATCH 47/68] rename FSAPI's PerformPeek as PerformOutboundPeek --- federationapi/routing/peek.go | 2 +- federationsender/api/api.go | 10 +++++----- federationsender/internal/perform.go | 12 ++++++------ federationsender/inthttp/client.go | 12 ++++++------ go.mod | 2 ++ roomserver/api/output.go | 4 ++-- roomserver/internal/perform/perform_peek.go | 6 +++--- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index 6fb3aa7ce8..6fd3e82964 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -33,7 +33,7 @@ func Peek( roomID, peekID string, remoteVersions []gomatrixserverlib.RoomVersion, ) util.JSONResponse { - // TODO: check if we're just refreshing an existing peek. Somehow. + // TODO: check if we're just refreshing an existing peek by querying the federationsender verReq := api.QueryRoomVersionForRoomRequest{RoomID: roomID} verRes := api.QueryRoomVersionForRoomResponse{} diff --git a/federationsender/api/api.go b/federationsender/api/api.go index 04ac634625..a5f1b1b53f 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -58,10 +58,10 @@ type FederationSenderInternalAPI interface { response *PerformJoinResponse, ) // Handle an instruction to peek a room on a remote server. - PerformPeek( + PerformOutboundPeek( ctx context.Context, - request *PerformPeekRequest, - response *PerformPeekResponse, + request *PerformOutboundPeekRequest, + response *PerformOutboundPeekResponse, ) error // Handle an instruction to make_leave & send_leave with a remote server. PerformLeave( @@ -111,13 +111,13 @@ type PerformJoinResponse struct { LastError *gomatrix.HTTPError } -type PerformPeekRequest struct { +type PerformOutboundPeekRequest struct { RoomID string `json:"room_id"` // The sorted list of servers to try. Servers will be tried sequentially, after de-duplication. ServerNames types.ServerNames `json:"server_names"` } -type PerformPeekResponse struct { +type PerformOutboundPeekResponse struct { LastError *gomatrix.HTTPError } diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index db84caaf35..acaa7c6f4e 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -208,11 +208,11 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( return nil } -// PerformPeekRequest implements api.FederationSenderInternalAPI -func (r *FederationSenderInternalAPI) PerformPeek( +// PerformOutboundPeekRequest implements api.FederationSenderInternalAPI +func (r *FederationSenderInternalAPI) PerformOutboundPeek( ctx context.Context, - request *api.PerformPeekRequest, - response *api.PerformPeekResponse, + request *api.PerformOutboundPeekRequest, + response *api.PerformOutboundPeekResponse, ) error { // Look up the supported room versions. var supportedVersions []gomatrixserverlib.RoomVersion @@ -238,7 +238,7 @@ func (r *FederationSenderInternalAPI) PerformPeek( // successfully completes the peek var lastErr error for _, serverName := range request.ServerNames { - if err := r.performPeekUsingServer( + if err := r.performOutboundPeekUsingServer( ctx, request.RoomID, serverName, @@ -279,7 +279,7 @@ func (r *FederationSenderInternalAPI) PerformPeek( return lastErr } -func (r *FederationSenderInternalAPI) performPeekUsingServer( +func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( ctx context.Context, roomID string, serverName gomatrixserverlib.ServerName, diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index 88e9e3d920..0623fd9aad 100644 --- a/federationsender/inthttp/client.go +++ b/federationsender/inthttp/client.go @@ -20,7 +20,7 @@ const ( FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest" FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest" FederationSenderPerformInviteRequestPath = "/federationsender/performInviteRequest" - FederationSenderPerformPeekRequestPath = "/federationsender/performPeekRequest" + FederationSenderPerformOutboundPeekRequestPath = "/federationsender/performOutboundPeekRequest" FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" @@ -74,15 +74,15 @@ func (h *httpFederationSenderInternalAPI) PerformInvite( } // Handle starting a peek on a remote server. -func (h *httpFederationSenderInternalAPI) PerformPeek( +func (h *httpFederationSenderInternalAPI) PerformOutboundPeek( ctx context.Context, - request *api.PerformPeekRequest, - response *api.PerformPeekResponse, + request *api.PerformOutboundPeekRequest, + response *api.PerformOutboundPeekResponse, ) error { - span, ctx := opentracing.StartSpanFromContext(ctx, "PerformPeekRequest") + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformOutboundPeekRequest") defer span.Finish() - apiURL := h.federationSenderURL + FederationSenderPerformPeekRequestPath + apiURL := h.federationSenderURL + FederationSenderPerformOutboundPeekRequestPath return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) } diff --git a/go.mod b/go.mod index f1cb3c9be7..502e0873d7 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/matrix-org/dendrite +replace github.com/matrix-org/gomatrixserverlib => ../gomatrixserverlib + require ( github.com/Shopify/sarama v1.27.0 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect diff --git a/roomserver/api/output.go b/roomserver/api/output.go index ea0c7eefec..fd02df9fc4 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -51,7 +51,7 @@ const ( OutputTypeNewPeek OutputType = "new_peek" // OutputTypeNewInboundPeek indicates that the kafka event is an OutputNewInboundPeek - OutputTypeNewInboundPeek OutputType = "new_remote_peek" + OutputTypeNewInboundPeek OutputType = "new_inbound_peek" ) // An OutputEvent is an entry in the roomserver output kafka log. @@ -70,7 +70,7 @@ type OutputEvent struct { // The content of event with type OutputTypeNewPeek NewPeek *OutputNewPeek `json:"new_peek,omitempty"` // The content of event with type OutputTypeNewInboundPeek - NewInboundPeek *OutputNewInboundPeek `json:"new_remote_peek,omitempty"` + NewInboundPeek *OutputNewInboundPeek `json:"new_inbound_peek,omitempty"` } // An OutputNewRoomEvent is written when the roomserver receives a new event. diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 8fdb23bf77..94f9284dd7 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -159,12 +159,12 @@ func (r *Peeker) performPeekRoomByID( req.ServerNames = append(req.ServerNames, domain) // Try peeking by all of the supplied server names. - fedReq := fsAPI.PerformPeekRequest{ + fedReq := fsAPI.PerformOutboundPeekRequest{ RoomID: req.RoomIDOrAlias, // the room ID to try and peek ServerNames: req.ServerNames, // the servers to try peeking via } - fedRes := fsAPI.PerformPeekResponse{} - r.FSAPI.PerformPeek(ctx, &fedReq, &fedRes) + fedRes := fsAPI.PerformOutboundPeekResponse{} + r.FSAPI.PerformOutboundPeek(ctx, &fedReq, &fedRes) if fedRes.LastError != nil { return "", &api.PerformError{ Code: api.PerformErrRemote, From a160c074e8a5d3c786db4dd8e538ff8729f8aeb7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 13 Sep 2020 03:31:11 +0100 Subject: [PATCH 48/68] setup inbound peeks db correctly --- federationsender/storage/sqlite3/storage.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index 35ac10cedb..91f2b964fd 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -69,6 +69,10 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { if err != nil { return nil, err } + inboundPeeks, err := NewSQLiteInboundPeeksTable(d.db) + if err != nil { + return nil, err + } d.Database = shared.Database{ DB: d.db, Writer: d.writer, @@ -79,6 +83,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { FederationSenderRooms: rooms, FederationSenderBlacklist: blacklist, FederationSenderOutboundPeeks: outboundPeeks, + FederationSenderInboundPeeks: inboundPeeks, } if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil { return nil, err From 32f898d66860d24517d6be3c4f2976c102ab207c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 13 Sep 2020 03:32:01 +0100 Subject: [PATCH 49/68] fix api.SendEventWithState with no event --- federationapi/routing/send.go | 2 +- federationsender/internal/perform.go | 9 ++++++--- roomserver/api/wrapper.go | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index ca4473f778..1453f8bbf1 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -459,7 +459,7 @@ func (t *txnReq) processEventWithMissingState(ctx context.Context, e gomatrixser // pass the event along with the state to the roomserver using a background context so we don't // needlessly expire headeredEvent := e.Headered(roomVersion) - return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, &headeredEvent, t.haveEventIDs()) + return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, &headeredEvent, t.haveEventIDs(), roomVersion) } // lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index acaa7c6f4e..46a9c113b3 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -201,6 +201,7 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( respState, &headeredEvent, nil, + respMakeJoin.RoomVersion, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) } @@ -310,7 +311,7 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( } } - // Try to perform a /peek using the information supplied in the + // Try to perform an outbound /peek using the information supplied in the // request. respPeek, err := r.federation.Peek( ctx, @@ -321,12 +322,12 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( ) if err != nil { r.statistics.ForServer(serverName).Failure() - return fmt.Errorf("r.federation.MakePeek: %w", err) + return fmt.Errorf("r.federation.Peek: %w", err) } r.statistics.ForServer(serverName).Success() // Work out if we support the room version that has been supplied in - // the make_peek response. + // the peek response. if respPeek.RoomVersion == "" { respPeek.RoomVersion = gomatrixserverlib.RoomVersionV1 } @@ -349,11 +350,13 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( } respState := respPeek.ToRespState() + // logrus.Warnf("converted respPeek %#v to respState %#v", respPeek, respState) // Send the newly returned state to the roomserver to update our local view. if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, &respState, nil, nil, + respPeek.RoomVersion, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) } diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index 2e0e0273f3..7659e1b607 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -47,6 +47,7 @@ func SendEvents( func SendEventWithState( ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, event *gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, + roomVersion gomatrixserverlib.RoomVersion, ) error { outliers, err := state.Events() if err != nil { @@ -60,7 +61,7 @@ func SendEventWithState( } ires = append(ires, InputRoomEvent{ Kind: KindOutlier, - Event: outlier.Headered(event.RoomVersion), + Event: outlier.Headered(roomVersion), AuthEventIDs: outlier.AuthEventIDs(), }) } From 20e2cb4b7eb09ce5106f82472ff8bdeaed7d83c4 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 22 Sep 2020 23:42:19 +0100 Subject: [PATCH 50/68] track latestevent on /peek --- federationapi/routing/peek.go | 15 +++++++------ federationapi/routing/send.go | 2 +- federationsender/consumers/roomserver.go | 3 ++- federationsender/internal/perform.go | 2 +- roomserver/api/output.go | 4 ++++ roomserver/api/perform.go | 2 ++ roomserver/api/wrapper.go | 21 ++++++++----------- .../internal/perform/perform_inbound_peek.go | 16 ++++++++++---- 8 files changed, 40 insertions(+), 25 deletions(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index 6fd3e82964..65df540797 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -87,14 +87,17 @@ func Peek( return util.JSONResponse{Code: http.StatusNotFound, JSON: nil} } + respPeek := gomatrixserverlib.RespPeek{ + StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), + AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), + RoomVersion: response.RoomVersion, + LatestEvent: response.LatestEvent.Unwrap(), + RenewalInterval: renewalInterval, + } + return util.JSONResponse{ Code: http.StatusOK, - JSON: gomatrixserverlib.RespPeek{ - StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), - AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), - RoomVersion: response.RoomVersion, - RenewalInterval: renewalInterval, - }, + JSON: respPeek, } } diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index de60e59c6b..6903724140 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -456,7 +456,7 @@ func (t *txnReq) processEventWithMissingState(ctx context.Context, e gomatrixser // pass the event along with the state to the roomserver using a background context so we don't // needlessly expire headeredEvent := e.Headered(roomVersion) - return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, &headeredEvent, t.haveEventIDs(), roomVersion) + return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, headeredEvent, t.haveEventIDs(), roomVersion) } // lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event) diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 182feb49c0..fef7068651 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -118,6 +118,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { // processInboundPeek starts tracking a new federated inbound peek (replacing the existing one if any) // causing the federationsender to start sending messages to the peeking server func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPeek) error { + // FIXME: do something with orp.LatestEventID to prevent races return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } @@ -227,7 +228,7 @@ func (s *OutputRoomEventConsumer) joinedHostsAtEvent( } // handle peeking hosts - inboundPeeks, err := s.db.GetInboundPeeks(context.TODO(), ore.Event.RoomID()) + inboundPeeks, err := s.db.GetInboundPeeks(context.TODO(), ore.Event.Event.RoomID()) if err != nil { return nil, err } diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 120df4e85d..ec2fbe1ece 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -357,7 +357,7 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, &respState, - nil, nil, + respPeek.LatestEvent.Headered(respPeek.RoomVersion), nil, respPeek.RoomVersion, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) diff --git a/roomserver/api/output.go b/roomserver/api/output.go index be19b8d5f7..bc1a55d040 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -232,6 +232,10 @@ type OutputNewPeek struct { type OutputNewInboundPeek struct { RoomID string PeekID string + // the event ID at which the peek begins (so we can avoid + // a race between tracking the state returned by /peek and emitting subsequent + // peeked events) + LatestEventID string ServerName gomatrixserverlib.ServerName // how often we told the peeking server to renew the peek RenewalInterval int64 diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 3b002474ee..f2556d7b3b 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -178,4 +178,6 @@ type PerformInboundPeekResponse struct { // The lists will be in an arbitrary order. StateEvents []gomatrixserverlib.HeaderedEvent `json:"state_events"` AuthChainEvents []gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"` + // The event at which this state was captured + LatestEvent gomatrixserverlib.HeaderedEvent `json:"latest_event"` } \ No newline at end of file diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index f1e0c40fd2..8fc7f138d6 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -42,11 +42,10 @@ func SendEvents( // 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. The event itself is optional in case -// hou just want to write outliers to the roomserver. +// marked as `true` in haveEventIDs. func SendEventWithState( ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, - event *gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, + event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, roomVersion gomatrixserverlib.RoomVersion, ) error { outliers, err := state.Events() @@ -71,15 +70,13 @@ func SendEventWithState( stateEventIDs[i] = state.StateEvents[i].EventID() } - if event != nil { - ires = append(ires, InputRoomEvent{ - Kind: KindNew, - Event: *event, - AuthEventIDs: event.AuthEventIDs(), - HasState: true, - StateEventIDs: stateEventIDs, - }) - } + ires = append(ires, InputRoomEvent{ + Kind: KindNew, + Event: event, + AuthEventIDs: event.AuthEventIDs(), + HasState: true, + StateEventIDs: stateEventIDs, + }) return SendInputRoomEvents(ctx, rsAPI, ires) } diff --git a/roomserver/internal/perform/perform_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go index e6520b5735..1cb0181ad6 100644 --- a/roomserver/internal/perform/perform_inbound_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -58,15 +58,22 @@ func (r *InboundPeeker) PerformInboundPeek( var stateEvents []gomatrixserverlib.Event - // XXX: is this right? - roomState := state.NewStateResolution(r.DB, *info) - var currentStateSnapshotNID types.StateSnapshotNID - _, currentStateSnapshotNID, _, err = + latestEventRefs, currentStateSnapshotNID, _, err := r.DB.LatestEventIDs(ctx, info.RoomNID) if err != nil { return err } + // XXX: is this actually the latest of the latest events? + latestEvents, err := r.DB.EventsFromIDs(ctx, []string{ latestEventRefs[0].EventID }) + if err != nil { + return err + } + response.LatestEvent = latestEvents[0].Headered(info.RoomVersion) + + // XXX: do we actually need to do a state resolution here? + roomState := state.NewStateResolution(r.DB, *info) + var stateEntries []types.StateEntry stateEntries, err = roomState.LoadStateAtSnapshot( ctx, currentStateSnapshotNID, @@ -109,6 +116,7 @@ func (r *InboundPeeker) PerformInboundPeek( NewInboundPeek: &api.OutputNewInboundPeek{ RoomID: request.RoomID, PeekID: request.PeekID, + LatestEventID: latestEvents[0].EventID(), ServerName: request.ServerName, RenewalInterval: request.RenewalInterval, }, From 3202c7e76f3cb8e9b40a4c894d65ab571e658d5d Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 23 Sep 2020 00:08:23 +0100 Subject: [PATCH 51/68] go fmt --- federationapi/routing/peek.go | 15 +- federationsender/consumers/roomserver.go | 2 +- federationsender/internal/perform.go | 2 +- federationsender/inthttp/client.go | 2 +- .../storage/sqlite3/inbound_peeks_table.go | 2 +- .../storage/sqlite3/outbound_peeks_table.go | 2 +- federationsender/storage/tables/interface.go | 12 +- roomserver/api/api.go | 2 +- roomserver/api/output.go | 6 +- roomserver/api/perform.go | 12 +- roomserver/internal/api.go | 5 +- .../internal/perform/perform_inbound_peek.go | 157 +++++++++--------- roomserver/inthttp/client.go | 14 +- 13 files changed, 115 insertions(+), 118 deletions(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index 65df540797..f3b3fd3822 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -71,9 +71,9 @@ func Peek( err := rsAPI.PerformInboundPeek( httpReq.Context(), &api.PerformInboundPeekRequest{ - RoomID: roomID, - PeekID: peekID, - ServerName: request.Origin(), + RoomID: roomID, + PeekID: peekID, + ServerName: request.Origin(), RenewalInterval: renewalInterval, }, &response, @@ -88,10 +88,10 @@ func Peek( } respPeek := gomatrixserverlib.RespPeek{ - StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), - AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), - RoomVersion: response.RoomVersion, - LatestEvent: response.LatestEvent.Unwrap(), + StateEvents: gomatrixserverlib.UnwrapEventHeaders(response.StateEvents), + AuthEvents: gomatrixserverlib.UnwrapEventHeaders(response.AuthChainEvents), + RoomVersion: response.RoomVersion, + LatestEvent: response.LatestEvent.Unwrap(), RenewalInterval: renewalInterval, } @@ -100,4 +100,3 @@ func Peek( JSON: respPeek, } } - diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index fef7068651..5663cc473e 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -119,7 +119,7 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { // causing the federationsender to start sending messages to the peeking server func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPeek) error { // FIXME: do something with orp.LatestEventID to prevent races - return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) + return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } // processMessage updates the list of currently joined hosts in the room diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index ec2fbe1ece..3a0c978265 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -304,7 +304,7 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( renewing := false if outboundPeek != nil { nowMilli := time.Now().UnixNano() / int64(time.Millisecond) - if nowMilli > outboundPeek.RenewedTimestamp + outboundPeek.RenewalInterval { + if nowMilli > outboundPeek.RenewedTimestamp+outboundPeek.RenewalInterval { logrus.Infof("stale outbound peek to %s for %s already exists; renewing", serverName, roomID) renewing = true } else { diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index 0623fd9aad..2adad520d3 100644 --- a/federationsender/inthttp/client.go +++ b/federationsender/inthttp/client.go @@ -20,7 +20,7 @@ const ( FederationSenderPerformJoinRequestPath = "/federationsender/performJoinRequest" FederationSenderPerformLeaveRequestPath = "/federationsender/performLeaveRequest" FederationSenderPerformInviteRequestPath = "/federationsender/performInviteRequest" - FederationSenderPerformOutboundPeekRequestPath = "/federationsender/performOutboundPeekRequest" + FederationSenderPerformOutboundPeekRequestPath = "/federationsender/performOutboundPeekRequest" FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" diff --git a/federationsender/storage/sqlite3/inbound_peeks_table.go b/federationsender/storage/sqlite3/inbound_peeks_table.go index 2f1269ac0b..d5eacf9e47 100644 --- a/federationsender/storage/sqlite3/inbound_peeks_table.go +++ b/federationsender/storage/sqlite3/inbound_peeks_table.go @@ -56,7 +56,7 @@ const deleteInboundPeeksSQL = "" + "DELETE FROM federationsender_inbound_peeks WHERE room_id = $1" type inboundPeeksStatements struct { - db *sql.DB + db *sql.DB insertInboundPeekStmt *sql.Stmt selectInboundPeekStmt *sql.Stmt selectInboundPeeksStmt *sql.Stmt diff --git a/federationsender/storage/sqlite3/outbound_peeks_table.go b/federationsender/storage/sqlite3/outbound_peeks_table.go index 67edda4d3d..02aefce797 100644 --- a/federationsender/storage/sqlite3/outbound_peeks_table.go +++ b/federationsender/storage/sqlite3/outbound_peeks_table.go @@ -56,7 +56,7 @@ const deleteOutboundPeeksSQL = "" + "DELETE FROM federationsender_outbound_peeks WHERE room_id = $1" type outboundPeeksStatements struct { - db *sql.DB + db *sql.DB insertOutboundPeekStmt *sql.Stmt selectOutboundPeekStmt *sql.Stmt selectOutboundPeeksStmt *sql.Stmt diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index a2069f078e..2250823951 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -78,10 +78,10 @@ type FederationSenderOutboundPeeks interface { } type FederationSenderInboundPeeks interface { - InsertInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) - RenewInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) - SelectInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (inboundPeek *types.InboundPeek, err error) - SelectInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (inboundPeeks []types.InboundPeek, err error) - DeleteInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) - DeleteInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) + InsertInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) + RenewInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64) (err error) + SelectInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (inboundPeek *types.InboundPeek, err error) + SelectInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (inboundPeeks []types.InboundPeek, err error) + DeleteInboundPeek(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string) (err error) + DeleteInboundPeeks(ctx context.Context, txn *sql.Tx, roomID string) (err error) } diff --git a/roomserver/api/api.go b/roomserver/api/api.go index 5374dcdc84..ebfe4622c4 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -187,4 +187,4 @@ type RoomserverInternalAPI interface { req *RemoveRoomAliasRequest, response *RemoveRoomAliasResponse, ) error -} \ No newline at end of file +} diff --git a/roomserver/api/output.go b/roomserver/api/output.go index bc1a55d040..a3f6d150f7 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -230,13 +230,13 @@ type OutputNewPeek struct { // An OutputNewInboundPeek is written whenever a server starts peeking into a room type OutputNewInboundPeek struct { - RoomID string - PeekID string + RoomID string + PeekID string // the event ID at which the peek begins (so we can avoid // a race between tracking the state returned by /peek and emitting subsequent // peeked events) LatestEventID string - ServerName gomatrixserverlib.ServerName + ServerName gomatrixserverlib.ServerName // how often we told the peeking server to renew the peek RenewalInterval int64 } diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index f2556d7b3b..07614cdf87 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -161,11 +161,11 @@ type PerformPublishResponse struct { } type PerformInboundPeekRequest struct { - UserID string `json:"user_id"` - RoomID string `json:"room_id"` - PeekID string `json:"peek_id"` - ServerName gomatrixserverlib.ServerName `json:"server_name"` - RenewalInterval int64 `json:"renewal_interval"` + UserID string `json:"user_id"` + RoomID string `json:"room_id"` + PeekID string `json:"peek_id"` + ServerName gomatrixserverlib.ServerName `json:"server_name"` + RenewalInterval int64 `json:"renewal_interval"` } type PerformInboundPeekResponse struct { @@ -180,4 +180,4 @@ type PerformInboundPeekResponse struct { AuthChainEvents []gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"` // The event at which this state was captured LatestEvent gomatrixserverlib.HeaderedEvent `json:"latest_event"` -} \ No newline at end of file +} diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index c1414c09de..c104fc7203 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -93,8 +93,8 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen Inputer: r.Inputer, } r.InboundPeeker = &perform.InboundPeeker{ - DB: r.DB, - Inputer: r.Inputer, + DB: r.DB, + Inputer: r.Inputer, } r.Leaver = &perform.Leaver{ Cfg: r.Cfg, @@ -142,4 +142,3 @@ func (r *RoomserverInternalAPI) PerformLeave( } return r.WriteOutputEvents(req.RoomID, outputEvents) } - diff --git a/roomserver/internal/perform/perform_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go index 1cb0181ad6..99342a90f8 100644 --- a/roomserver/internal/perform/perform_inbound_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -18,18 +18,18 @@ import ( "context" "github.com/matrix-org/dendrite/roomserver/api" - "github.com/matrix-org/dendrite/roomserver/internal/helpers" - "github.com/matrix-org/dendrite/roomserver/internal/query" + "github.com/matrix-org/dendrite/roomserver/internal/helpers" "github.com/matrix-org/dendrite/roomserver/internal/input" - "github.com/matrix-org/dendrite/roomserver/state" - "github.com/matrix-org/dendrite/roomserver/storage" - "github.com/matrix-org/dendrite/roomserver/types" + "github.com/matrix-org/dendrite/roomserver/internal/query" + "github.com/matrix-org/dendrite/roomserver/state" + "github.com/matrix-org/dendrite/roomserver/storage" + "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) type InboundPeeker struct { - DB storage.Database + DB storage.Database Inputer *input.Inputer } @@ -42,86 +42,85 @@ type InboundPeeker struct { // fed sender can start sending peeked events without a race between the state // snapshot and the stream of peeked events. func (r *InboundPeeker) PerformInboundPeek( - ctx context.Context, - request *api.PerformInboundPeekRequest, - response *api.PerformInboundPeekResponse, + ctx context.Context, + request *api.PerformInboundPeekRequest, + response *api.PerformInboundPeekResponse, ) error { - info, err := r.DB.RoomInfo(ctx, request.RoomID) - if err != nil { - return err - } - if info == nil || info.IsStub { - return nil - } - response.RoomExists = true - response.RoomVersion = info.RoomVersion - - var stateEvents []gomatrixserverlib.Event - - var currentStateSnapshotNID types.StateSnapshotNID - latestEventRefs, currentStateSnapshotNID, _, err := - r.DB.LatestEventIDs(ctx, info.RoomNID) - if err != nil { - return err - } - // XXX: is this actually the latest of the latest events? - latestEvents, err := r.DB.EventsFromIDs(ctx, []string{ latestEventRefs[0].EventID }) - if err != nil { - return err - } - response.LatestEvent = latestEvents[0].Headered(info.RoomVersion) - - // XXX: do we actually need to do a state resolution here? - roomState := state.NewStateResolution(r.DB, *info) - - var stateEntries []types.StateEntry - stateEntries, err = roomState.LoadStateAtSnapshot( - ctx, currentStateSnapshotNID, - ) - if err != nil { - return err - } - stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries) - if err != nil { - return err - } - - // get the auth event IDs for the current state events - var authEventIDs []string - for _, se := range stateEvents { - authEventIDs = append(authEventIDs, se.AuthEventIDs()...) - } - authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe - - authEvents, err := query.GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) - if err != nil { - return err - } - - for _, event := range stateEvents { - response.StateEvents = append(response.StateEvents, event.Headered(info.RoomVersion)) - } - - for _, event := range authEvents { - response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion)) - } - - // FIXME: there's a race here - we really should be atomically telling the - // federationsender to start sending peek events alongside having captured - // the current state, but it's unclear if/how we can do that. + info, err := r.DB.RoomInfo(ctx, request.RoomID) + if err != nil { + return err + } + if info == nil || info.IsStub { + return nil + } + response.RoomExists = true + response.RoomVersion = info.RoomVersion + + var stateEvents []gomatrixserverlib.Event + + var currentStateSnapshotNID types.StateSnapshotNID + latestEventRefs, currentStateSnapshotNID, _, err := + r.DB.LatestEventIDs(ctx, info.RoomNID) + if err != nil { + return err + } + // XXX: is this actually the latest of the latest events? + latestEvents, err := r.DB.EventsFromIDs(ctx, []string{latestEventRefs[0].EventID}) + if err != nil { + return err + } + response.LatestEvent = latestEvents[0].Headered(info.RoomVersion) + + // XXX: do we actually need to do a state resolution here? + roomState := state.NewStateResolution(r.DB, *info) + + var stateEntries []types.StateEntry + stateEntries, err = roomState.LoadStateAtSnapshot( + ctx, currentStateSnapshotNID, + ) + if err != nil { + return err + } + stateEvents, err = helpers.LoadStateEvents(ctx, r.DB, stateEntries) + if err != nil { + return err + } + + // get the auth event IDs for the current state events + var authEventIDs []string + for _, se := range stateEvents { + authEventIDs = append(authEventIDs, se.AuthEventIDs()...) + } + authEventIDs = util.UniqueStrings(authEventIDs) // de-dupe + + authEvents, err := query.GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) + if err != nil { + return err + } + + for _, event := range stateEvents { + response.StateEvents = append(response.StateEvents, event.Headered(info.RoomVersion)) + } + + for _, event := range authEvents { + response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion)) + } + + // FIXME: there's a race here - we really should be atomically telling the + // federationsender to start sending peek events alongside having captured + // the current state, but it's unclear if/how we can do that. err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{ { Type: api.OutputTypeNewInboundPeek, NewInboundPeek: &api.OutputNewInboundPeek{ - RoomID: request.RoomID, - PeekID: request.PeekID, - LatestEventID: latestEvents[0].EventID(), - ServerName: request.ServerName, - RenewalInterval: request.RenewalInterval, + RoomID: request.RoomID, + PeekID: request.PeekID, + LatestEventID: latestEvents[0].EventID(), + ServerName: request.ServerName, + RenewalInterval: request.RenewalInterval, }, }, }) - return err + return err } - diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index e3ce44a9ae..193050d404 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -25,13 +25,13 @@ const ( RoomserverInputRoomEventsPath = "/roomserver/inputRoomEvents" // Perform operations - RoomserverPerformInvitePath = "/roomserver/performInvite" - RoomserverPerformPeekPath = "/roomserver/performPeek" - RoomserverPerformJoinPath = "/roomserver/performJoin" - RoomserverPerformLeavePath = "/roomserver/performLeave" - RoomserverPerformBackfillPath = "/roomserver/performBackfill" - RoomserverPerformPublishPath = "/roomserver/performPublish" - RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" + RoomserverPerformInvitePath = "/roomserver/performInvite" + RoomserverPerformPeekPath = "/roomserver/performPeek" + RoomserverPerformJoinPath = "/roomserver/performJoin" + RoomserverPerformLeavePath = "/roomserver/performLeave" + RoomserverPerformBackfillPath = "/roomserver/performBackfill" + RoomserverPerformPublishPath = "/roomserver/performPublish" + RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" From 0ab4bc9e8ef536e722ef8aeda8d04a9d2598f6c5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 23 Sep 2020 00:11:58 +0100 Subject: [PATCH 52/68] document the peek send stream race better --- federationsender/consumers/roomserver.go | 10 +++++++++- roomserver/internal/perform/perform_inbound_peek.go | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 5663cc473e..4d5e2ab4df 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -118,7 +118,15 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { // processInboundPeek starts tracking a new federated inbound peek (replacing the existing one if any) // causing the federationsender to start sending messages to the peeking server func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPeek) error { - // FIXME: do something with orp.LatestEventID to prevent races + + // FIXME: there's a race here - we should start /sending new peeked events + // atomically after the orp.LatestEventID to ensure there are no gaps between + // the peek beginning and the send stream beginning. + // + // We probably need to track orp.LatestEventID on the inbound peek, but it's + // unclear how we then use that to prevent the race when we start the send + // stream. + return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } diff --git a/roomserver/internal/perform/perform_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go index 99342a90f8..8599ad2043 100644 --- a/roomserver/internal/perform/perform_inbound_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -106,10 +106,6 @@ func (r *InboundPeeker) PerformInboundPeek( response.AuthChainEvents = append(response.AuthChainEvents, event.Headered(info.RoomVersion)) } - // FIXME: there's a race here - we really should be atomically telling the - // federationsender to start sending peek events alongside having captured - // the current state, but it's unclear if/how we can do that. - err = r.Inputer.WriteOutputEvents(request.RoomID, []api.OutputEvent{ { Type: api.OutputTypeNewInboundPeek, From 2b4353eeca3440546dfe868ec92cc4a797bb7daf Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 00:31:15 +0100 Subject: [PATCH 53/68] fix SendEventWithRewrite not to bail if handed a non-state event --- roomserver/api/wrapper.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/roomserver/api/wrapper.go b/roomserver/api/wrapper.go index ec27b304f7..f8fb1dd19e 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -46,7 +46,6 @@ func SendEvents( func SendEventWithState( ctx context.Context, rsAPI RoomserverInternalAPI, state *gomatrixserverlib.RespState, event gomatrixserverlib.HeaderedEvent, haveEventIDs map[string]bool, - roomVersion gomatrixserverlib.RoomVersion, ) error { outliers, err := state.Events() if err != nil { @@ -60,7 +59,7 @@ func SendEventWithState( } ires = append(ires, InputRoomEvent{ Kind: KindOutlier, - Event: outlier.Headered(roomVersion), + Event: outlier.Headered(event.RoomVersion), AuthEventIDs: outlier.AuthEventIDs(), }) } @@ -116,9 +115,6 @@ func SendEventWithRewrite( 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: From 927a62a7f0a9f9594a8fbcfb7532007fbb79f3fa Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 00:31:25 +0100 Subject: [PATCH 54/68] add fixme --- roomserver/internal/perform/perform_peek.go | 1 + 1 file changed, 1 insertion(+) diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 94f9284dd7..ad6ec112e5 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -152,6 +152,7 @@ func (r *Peeker) performPeekRoomByID( } // handle federated peeks + // FIXME: don't create an outbound peek if we already have one going. if domain != r.Cfg.Matrix.ServerName { // If the server name in the room ID isn't ours then it's a // possible candidate for finding the room via federation. Add From 1e6e23dab184f27acef81236e736d80b223e4d67 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 00:31:52 +0100 Subject: [PATCH 55/68] switch SS /peek to use SendEventWithRewrite --- federationapi/routing/send.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 6903724140..51ccf6c96a 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -456,7 +456,7 @@ func (t *txnReq) processEventWithMissingState(ctx context.Context, e gomatrixser // pass the event along with the state to the roomserver using a background context so we don't // needlessly expire headeredEvent := e.Headered(roomVersion) - return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, headeredEvent, t.haveEventIDs(), roomVersion) + return api.SendEventWithState(context.Background(), t.rsAPI, resolvedState, headeredEvent, t.haveEventIDs()) } // lookupStateAfterEvent returns the room state after `eventID`, which is the state before eventID with the state of `eventID` (if it's a state event) From fd908498029509f1bd5e6587cb95f5d374859b0c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 00:31:56 +0100 Subject: [PATCH 56/68] fix comment --- federationsender/internal/perform.go | 10 +++++----- federationsender/storage/interface.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 211314503d..9d7c28fca4 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -391,13 +391,13 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( } respState := respPeek.ToRespState() - // logrus.Warnf("converted respPeek %#v to respState %#v", respPeek, respState) + // logrus.Warnf("got respPeek %#v", respPeek) // 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, - respPeek.LatestEvent.Headered(respPeek.RoomVersion), nil, - respPeek.RoomVersion, + respPeek.LatestEvent.Headered(respPeek.RoomVersion), + nil, ); err != nil { return fmt.Errorf("r.producer.SendEventWithState: %w", err) } @@ -578,4 +578,4 @@ func (r *FederationSenderInternalAPI) PerformBroadcastEDU( } return nil -} +} \ No newline at end of file diff --git a/federationsender/storage/interface.go b/federationsender/storage/interface.go index 035c0d2653..0d38527fb5 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -50,7 +50,7 @@ type Database interface { GetPendingPDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) GetPendingEDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) - // XXX: why don't these have contexts passed in? + // these don't have contexts passed in as we want things to happen regardless of the request context AddServerToBlacklist(serverName gomatrixserverlib.ServerName) error RemoveServerFromBlacklist(serverName gomatrixserverlib.ServerName) error IsServerBlacklisted(serverName gomatrixserverlib.ServerName) (bool, error) From ed9e3fc385e33fa6c1c5be71221fc2f9e4ccf52c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 00:42:06 +0100 Subject: [PATCH 57/68] use reverse topo ordering to find latest extrem --- roomserver/internal/perform/perform_inbound_peek.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/roomserver/internal/perform/perform_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go index 8599ad2043..e919d20404 100644 --- a/roomserver/internal/perform/perform_inbound_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -64,12 +64,19 @@ func (r *InboundPeeker) PerformInboundPeek( if err != nil { return err } - // XXX: is this actually the latest of the latest events? latestEvents, err := r.DB.EventsFromIDs(ctx, []string{latestEventRefs[0].EventID}) if err != nil { return err } - response.LatestEvent = latestEvents[0].Headered(info.RoomVersion) + var sortedLatestEvents []gomatrixserverlib.Event + for _, ev := range latestEvents { + sortedLatestEvents = append(sortedLatestEvents, ev.Event) + } + sortedLatestEvents = gomatrixserverlib.ReverseTopologicalOrdering( + sortedLatestEvents, + gomatrixserverlib.TopologicalOrderByPrevEvents, + ) + response.LatestEvent = sortedLatestEvents[0].Headered(info.RoomVersion) // XXX: do we actually need to do a state resolution here? roomState := state.NewStateResolution(r.DB, *info) From efd8656302f8dfe2b9989a8d6dfebf6c80b421b4 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 20:59:04 +0100 Subject: [PATCH 58/68] support postgres for federated peeking --- federationsender/consumers/roomserver.go | 2 + .../storage/postgres/inbound_peeks_table.go | 176 ++++++++++++++++++ .../storage/postgres/outbound_peeks_table.go | 176 ++++++++++++++++++ federationsender/storage/postgres/storage.go | 10 + 4 files changed, 364 insertions(+) create mode 100644 federationsender/storage/postgres/inbound_peeks_table.go create mode 100644 federationsender/storage/postgres/outbound_peeks_table.go diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 4d5e2ab4df..d0bf0a08e5 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -126,6 +126,8 @@ func (s *OutputRoomEventConsumer) processInboundPeek(orp api.OutputNewInboundPee // We probably need to track orp.LatestEventID on the inbound peek, but it's // unclear how we then use that to prevent the race when we start the send // stream. + // + // This is making the tests flakey. return s.db.AddInboundPeek(context.TODO(), orp.ServerName, orp.RoomID, orp.PeekID, orp.RenewalInterval) } diff --git a/federationsender/storage/postgres/inbound_peeks_table.go b/federationsender/storage/postgres/inbound_peeks_table.go new file mode 100644 index 0000000000..fe35ce44c0 --- /dev/null +++ b/federationsender/storage/postgres/inbound_peeks_table.go @@ -0,0 +1,176 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + "time" + + "github.com/matrix-org/dendrite/federationsender/types" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const inboundPeeksSchema = ` +CREATE TABLE IF NOT EXISTS federationsender_inbound_peeks ( + room_id TEXT NOT NULL, + server_name TEXT NOT NULL, + peek_id TEXT NOT NULL, + creation_ts BIGINT NOT NULL, + renewed_ts BIGINT NOT NULL, + renewal_interval BIGINT NOT NULL, + UNIQUE (room_id, server_name, peek_id) +); +` + +const insertInboundPeekSQL = "" + + "INSERT INTO federationsender_inbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" + +const selectInboundPeekSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" + +const selectInboundPeeksSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_inbound_peeks WHERE room_id = $1" + +const renewInboundPeekSQL = "" + + "UPDATE federationsender_inbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" + +const deleteInboundPeekSQL = "" + + "DELETE FROM federationsender_inbound_peeks WHERE room_id = $1 and server_name = $2" + +const deleteInboundPeeksSQL = "" + + "DELETE FROM federationsender_inbound_peeks WHERE room_id = $1" + +type inboundPeeksStatements struct { + db *sql.DB + insertInboundPeekStmt *sql.Stmt + selectInboundPeekStmt *sql.Stmt + selectInboundPeeksStmt *sql.Stmt + renewInboundPeekStmt *sql.Stmt + deleteInboundPeekStmt *sql.Stmt + deleteInboundPeeksStmt *sql.Stmt +} + +func NewPostgresInboundPeeksTable(db *sql.DB) (s *inboundPeeksStatements, err error) { + s = &inboundPeeksStatements{ + db: db, + } + _, err = db.Exec(inboundPeeksSchema) + if err != nil { + return + } + + if s.insertInboundPeekStmt, err = db.Prepare(insertInboundPeekSQL); err != nil { + return + } + if s.selectInboundPeekStmt, err = db.Prepare(selectInboundPeekSQL); err != nil { + return + } + if s.selectInboundPeeksStmt, err = db.Prepare(selectInboundPeeksSQL); err != nil { + return + } + if s.renewInboundPeekStmt, err = db.Prepare(renewInboundPeekSQL); err != nil { + return + } + if s.deleteInboundPeeksStmt, err = db.Prepare(deleteInboundPeeksSQL); err != nil { + return + } + if s.deleteInboundPeekStmt, err = db.Prepare(deleteInboundPeekSQL); err != nil { + return + } + return +} + +func (s *inboundPeeksStatements) InsertInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + stmt := sqlutil.TxStmt(txn, s.insertInboundPeekStmt) + _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) + return +} + +func (s *inboundPeeksStatements) RenewInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err = sqlutil.TxStmt(txn, s.renewInboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) + return +} + +func (s *inboundPeeksStatements) SelectInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (*types.InboundPeek, error) { + row := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryRowContext(ctx, roomID) + inboundPeek := types.InboundPeek{} + err := row.Scan( + &inboundPeek.RoomID, + &inboundPeek.ServerName, + &inboundPeek.PeekID, + &inboundPeek.CreationTimestamp, + &inboundPeek.RenewedTimestamp, + &inboundPeek.RenewalInterval, + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return &inboundPeek, nil +} + +func (s *inboundPeeksStatements) SelectInboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (inboundPeeks []types.InboundPeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectInboundPeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectInboundPeeks: rows.close() failed") + + for rows.Next() { + inboundPeek := types.InboundPeek{} + if err = rows.Scan( + &inboundPeek.RoomID, + &inboundPeek.ServerName, + &inboundPeek.PeekID, + &inboundPeek.CreationTimestamp, + &inboundPeek.RenewedTimestamp, + &inboundPeek.RenewalInterval, + ); err != nil { + return + } + inboundPeeks = append(inboundPeeks, inboundPeek) + } + + return inboundPeeks, rows.Err() +} + +func (s *inboundPeeksStatements) DeleteInboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteInboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID) + return +} + +func (s *inboundPeeksStatements) DeleteInboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteInboundPeeksStmt).ExecContext(ctx, roomID) + return +} diff --git a/federationsender/storage/postgres/outbound_peeks_table.go b/federationsender/storage/postgres/outbound_peeks_table.go new file mode 100644 index 0000000000..596b4bcc77 --- /dev/null +++ b/federationsender/storage/postgres/outbound_peeks_table.go @@ -0,0 +1,176 @@ +// Copyright 2020 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package postgres + +import ( + "context" + "database/sql" + "time" + + "github.com/matrix-org/dendrite/federationsender/types" + "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/gomatrixserverlib" +) + +const outboundPeeksSchema = ` +CREATE TABLE IF NOT EXISTS federationsender_outbound_peeks ( + room_id TEXT NOT NULL, + server_name TEXT NOT NULL, + peek_id TEXT NOT NULL, + creation_ts BIGINT NOT NULL, + renewed_ts BIGINT NOT NULL, + renewal_interval BIGINT NOT NULL, + UNIQUE (room_id, server_name, peek_id) +); +` + +const insertOutboundPeekSQL = "" + + "INSERT INTO federationsender_outbound_peeks (room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval) VALUES ($1, $2, $3, $4, $5, $6)" + +const selectOutboundPeekSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2 and peek_id = $3" + +const selectOutboundPeeksSQL = "" + + "SELECT room_id, server_name, peek_id, creation_ts, renewed_ts, renewal_interval FROM federationsender_outbound_peeks WHERE room_id = $1" + +const renewOutboundPeekSQL = "" + + "UPDATE federationsender_outbound_peeks SET renewed_ts=$1, renewal_interval=$2 WHERE room_id = $3 and server_name = $4 and peek_id = $5" + +const deleteOutboundPeekSQL = "" + + "DELETE FROM federationsender_outbound_peeks WHERE room_id = $1 and server_name = $2" + +const deleteOutboundPeeksSQL = "" + + "DELETE FROM federationsender_outbound_peeks WHERE room_id = $1" + +type outboundPeeksStatements struct { + db *sql.DB + insertOutboundPeekStmt *sql.Stmt + selectOutboundPeekStmt *sql.Stmt + selectOutboundPeeksStmt *sql.Stmt + renewOutboundPeekStmt *sql.Stmt + deleteOutboundPeekStmt *sql.Stmt + deleteOutboundPeeksStmt *sql.Stmt +} + +func NewPostgresOutboundPeeksTable(db *sql.DB) (s *outboundPeeksStatements, err error) { + s = &outboundPeeksStatements{ + db: db, + } + _, err = db.Exec(outboundPeeksSchema) + if err != nil { + return + } + + if s.insertOutboundPeekStmt, err = db.Prepare(insertOutboundPeekSQL); err != nil { + return + } + if s.selectOutboundPeekStmt, err = db.Prepare(selectOutboundPeekSQL); err != nil { + return + } + if s.selectOutboundPeeksStmt, err = db.Prepare(selectOutboundPeeksSQL); err != nil { + return + } + if s.renewOutboundPeekStmt, err = db.Prepare(renewOutboundPeekSQL); err != nil { + return + } + if s.deleteOutboundPeeksStmt, err = db.Prepare(deleteOutboundPeeksSQL); err != nil { + return + } + if s.deleteOutboundPeekStmt, err = db.Prepare(deleteOutboundPeekSQL); err != nil { + return + } + return +} + +func (s *outboundPeeksStatements) InsertOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + stmt := sqlutil.TxStmt(txn, s.insertOutboundPeekStmt) + _, err = stmt.ExecContext(ctx, roomID, serverName, peekID, nowMilli, nowMilli, renewalInterval) + return +} + +func (s *outboundPeeksStatements) RenewOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, renewalInterval int64, +) (err error) { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + _, err = sqlutil.TxStmt(txn, s.renewOutboundPeekStmt).ExecContext(ctx, nowMilli, renewalInterval, roomID, serverName, peekID) + return +} + +func (s *outboundPeeksStatements) SelectOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (*types.OutboundPeek, error) { + row := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryRowContext(ctx, roomID) + outboundPeek := types.OutboundPeek{} + err := row.Scan( + &outboundPeek.RoomID, + &outboundPeek.ServerName, + &outboundPeek.PeekID, + &outboundPeek.CreationTimestamp, + &outboundPeek.RenewedTimestamp, + &outboundPeek.RenewalInterval, + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return &outboundPeek, nil +} + +func (s *outboundPeeksStatements) SelectOutboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (outboundPeeks []types.OutboundPeek, err error) { + rows, err := sqlutil.TxStmt(txn, s.selectOutboundPeeksStmt).QueryContext(ctx, roomID) + if err != nil { + return + } + defer internal.CloseAndLogIfError(ctx, rows, "SelectOutboundPeeks: rows.close() failed") + + for rows.Next() { + outboundPeek := types.OutboundPeek{} + if err = rows.Scan( + &outboundPeek.RoomID, + &outboundPeek.ServerName, + &outboundPeek.PeekID, + &outboundPeek.CreationTimestamp, + &outboundPeek.RenewedTimestamp, + &outboundPeek.RenewalInterval, + ); err != nil { + return + } + outboundPeeks = append(outboundPeeks, outboundPeek) + } + + return outboundPeeks, rows.Err() +} + +func (s *outboundPeeksStatements) DeleteOutboundPeek( + ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName, roomID, peekID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteOutboundPeekStmt).ExecContext(ctx, roomID, serverName, peekID) + return +} + +func (s *outboundPeeksStatements) DeleteOutboundPeeks( + ctx context.Context, txn *sql.Tx, roomID string, +) (err error) { + _, err = sqlutil.TxStmt(txn, s.deleteOutboundPeeksStmt).ExecContext(ctx, roomID) + return +} diff --git a/federationsender/storage/postgres/storage.go b/federationsender/storage/postgres/storage.go index b3b4da398b..e63c11bf3f 100644 --- a/federationsender/storage/postgres/storage.go +++ b/federationsender/storage/postgres/storage.go @@ -63,6 +63,14 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { if err != nil { return nil, err } + inboundPeeks, err := NewPostgresInboundPeeksTable(d.db) + if err != nil { + return nil, err + } + outboundPeeks, err := NewPostgresOutboundPeeksTable(d.db) + if err != nil { + return nil, err + } d.Database = shared.Database{ DB: d.db, Writer: d.writer, @@ -72,6 +80,8 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { FederationSenderQueueJSON: queueJSON, FederationSenderRooms: rooms, FederationSenderBlacklist: blacklist, + FederationSenderInboundPeeks: inboundPeeks, + FederationSenderOutboundPeeks: outboundPeeks, } if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil { return nil, err From ebeff8ddb1cfcda2b35b6300673518c4797c8a6a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 21:03:00 +0100 Subject: [PATCH 59/68] go fmt --- federationsender/internal/perform.go | 2 +- federationsender/storage/postgres/storage.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 9d7c28fca4..1a3eb92253 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -578,4 +578,4 @@ func (r *FederationSenderInternalAPI) PerformBroadcastEDU( } return nil -} \ No newline at end of file +} diff --git a/federationsender/storage/postgres/storage.go b/federationsender/storage/postgres/storage.go index e63c11bf3f..8884e35b2d 100644 --- a/federationsender/storage/postgres/storage.go +++ b/federationsender/storage/postgres/storage.go @@ -72,15 +72,15 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*Database, error) { return nil, err } d.Database = shared.Database{ - DB: d.db, - Writer: d.writer, - FederationSenderJoinedHosts: joinedHosts, - FederationSenderQueuePDUs: queuePDUs, - FederationSenderQueueEDUs: queueEDUs, - FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, - FederationSenderBlacklist: blacklist, - FederationSenderInboundPeeks: inboundPeeks, + DB: d.db, + Writer: d.writer, + FederationSenderJoinedHosts: joinedHosts, + FederationSenderQueuePDUs: queuePDUs, + FederationSenderQueueEDUs: queueEDUs, + FederationSenderQueueJSON: queueJSON, + FederationSenderRooms: rooms, + FederationSenderBlacklist: blacklist, + FederationSenderInboundPeeks: inboundPeeks, FederationSenderOutboundPeeks: outboundPeeks, } if err = d.PartitionOffsetStatements.Prepare(d.db, d.writer, "federationsender"); err != nil { From 4262095f0266aed32585277b7f172f24f18b2349 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 27 Sep 2020 21:04:31 +0100 Subject: [PATCH 60/68] back out bogus go.mod change --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index d60fe2354b..ca0c2710c0 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,5 @@ module github.com/matrix-org/dendrite -replace github.com/matrix-org/gomatrixserverlib => ../gomatrixserverlib - require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/Shopify/sarama v1.27.0 From be5a4e6adc63849bf0c92260597120eee903d294 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 20 Oct 2020 17:32:41 +0100 Subject: [PATCH 61/68] Fix performOutboundPeekUsingServer --- federationsender/internal/perform.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index e5df8bb92a..4d706c3e47 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -408,8 +408,9 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( respState := respPeek.ToRespState() // logrus.Warnf("got respPeek %#v", respPeek) // Send the newly returned state to the roomserver to update our local view. - if err = roomserverAPI.SendEventWithRewrite( + if err = roomserverAPI.SendEventWithState( ctx, r.rsAPI, + roomserverAPI.KindNew, &respState, respPeek.LatestEvent.Headered(respPeek.RoomVersion), nil, From 90017d04ff6ea10c32a27493b8568fd75d45f4f5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 20 Oct 2020 17:34:06 +0100 Subject: [PATCH 62/68] Fix getAuthChain -> GetAuthChain --- roomserver/internal/query/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index 853a409137..8123353ce5 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -107,7 +107,7 @@ func (r *Queryer) QueryStateAfterEvents( } authEventIDs = util.UniqueStrings(authEventIDs) - authEvents, err := getAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) + authEvents, err := GetAuthChain(ctx, r.DB.EventsFromIDs, authEventIDs) if err != nil { return fmt.Errorf("getAuthChain: %w", err) } From 7fc3852bdf7308b4421ef976f4408301ad0ab69d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 2 Dec 2020 15:48:45 +0000 Subject: [PATCH 63/68] Fix build issues --- federationapi/routing/send_test.go | 8 ++++++++ roomserver/api/perform.go | 7 +++---- roomserver/internal/perform/perform_inbound_peek.go | 4 ++-- roomserver/internal/query/query.go | 2 +- roomserver/internal/query/query_test.go | 4 ++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/federationapi/routing/send_test.go b/federationapi/routing/send_test.go index 9b9db873b8..2f1c36cf5c 100644 --- a/federationapi/routing/send_test.go +++ b/federationapi/routing/send_test.go @@ -146,6 +146,14 @@ func (t *testRoomserverAPI) PerformLeave( return nil } +func (t *testRoomserverAPI) PerformInboundPeek( + ctx context.Context, + req *api.PerformInboundPeekRequest, + res *api.PerformInboundPeekResponse, +) error { + return nil +} + // Query the latest events and state for a room from the room server. func (t *testRoomserverAPI) QueryLatestEventsAndState( ctx context.Context, diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index 3f5dcfb3e1..ae6bfadd7b 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -177,10 +177,10 @@ type PerformInboundPeekResponse struct { RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"` // The current state and auth chain events. // The lists will be in an arbitrary order. - StateEvents []gomatrixserverlib.HeaderedEvent `json:"state_events"` - AuthChainEvents []gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"` + StateEvents []*gomatrixserverlib.HeaderedEvent `json:"state_events"` + AuthChainEvents []*gomatrixserverlib.HeaderedEvent `json:"auth_chain_events"` // The event at which this state was captured - LatestEvent gomatrixserverlib.HeaderedEvent `json:"latest_event"` + LatestEvent *gomatrixserverlib.HeaderedEvent `json:"latest_event"` } // PerformForgetRequest is a request to PerformForget @@ -190,4 +190,3 @@ type PerformForgetRequest struct { } type PerformForgetResponse struct{} - diff --git a/roomserver/internal/perform/perform_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go index e919d20404..eb3c9727d9 100644 --- a/roomserver/internal/perform/perform_inbound_peek.go +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -56,7 +56,7 @@ func (r *InboundPeeker) PerformInboundPeek( response.RoomExists = true response.RoomVersion = info.RoomVersion - var stateEvents []gomatrixserverlib.Event + var stateEvents []*gomatrixserverlib.Event var currentStateSnapshotNID types.StateSnapshotNID latestEventRefs, currentStateSnapshotNID, _, err := @@ -68,7 +68,7 @@ func (r *InboundPeeker) PerformInboundPeek( if err != nil { return err } - var sortedLatestEvents []gomatrixserverlib.Event + var sortedLatestEvents []*gomatrixserverlib.Event for _, ev := range latestEvents { sortedLatestEvents = append(sortedLatestEvents, ev.Event) } diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index aef6fa3f3a..da0ebc55d4 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -447,7 +447,7 @@ func (r *Queryer) QueryStateAndAuthChain( response.RoomExists = true response.RoomVersion = info.RoomVersion - var stateEvents []gomatrixserverlib.Event + var stateEvents []*gomatrixserverlib.Event stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) if err != nil { return err diff --git a/roomserver/internal/query/query_test.go b/roomserver/internal/query/query_test.go index 4e761d8ec9..ba5bb9f55f 100644 --- a/roomserver/internal/query/query_test.go +++ b/roomserver/internal/query/query_test.go @@ -106,7 +106,7 @@ func TestGetAuthChainSingle(t *testing.T) { t.Fatalf("Failed to add events to db: %v", err) } - result, err := getAuthChain(context.TODO(), db.EventsFromIDs, []string{"e"}) + result, err := GetAuthChain(context.TODO(), db.EventsFromIDs, []string{"e"}) if err != nil { t.Fatalf("getAuthChain failed: %v", err) } @@ -139,7 +139,7 @@ func TestGetAuthChainMultiple(t *testing.T) { t.Fatalf("Failed to add events to db: %v", err) } - result, err := getAuthChain(context.TODO(), db.EventsFromIDs, []string{"e", "f"}) + result, err := GetAuthChain(context.TODO(), db.EventsFromIDs, []string{"e", "f"}) if err != nil { t.Fatalf("getAuthChain failed: %v", err) } From c2f7c80872543851419a55468e829a5a416f4d5a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 3 Dec 2020 11:16:42 +0000 Subject: [PATCH 64/68] Fix build again --- federationapi/routing/peek.go | 2 +- roomserver/inthttp/server.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index f3b3fd3822..a036234ef5 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -18,7 +18,7 @@ import ( "net/http" "github.com/matrix-org/dendrite/clientapi/jsonerror" - "github.com/matrix-org/dendrite/internal/config" + "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index cc476f1b14..b429384010 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -82,6 +82,9 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { if err := r.PerformInboundPeek(req.Context(), &request, &response); err != nil { return util.ErrorResponse(err) } + return util.JSONResponse{Code: http.StatusOK, JSON: &response} + }), + ) internalAPIMux.Handle(RoomserverPerformPeekPath, httputil.MakeInternalAPI("performUnpeek", func(req *http.Request) util.JSONResponse { var request api.PerformUnpeekRequest From d47ab1f3b9d6d2490ddad42ef54cb8628b7f0c95 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 10 Dec 2020 10:42:26 +0000 Subject: [PATCH 65/68] Fix getAuthChain -> GetAuthChain --- roomserver/internal/query/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index f077e9f7b0..2a361641ad 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -720,7 +720,7 @@ func (r *Queryer) QueryServerBannedFromRoom(ctx context.Context, req *api.QueryS } func (r *Queryer) QueryAuthChain(ctx context.Context, req *api.QueryAuthChainRequest, res *api.QueryAuthChainResponse) error { - chain, err := getAuthChain(ctx, r.DB.EventsFromIDs, req.EventIDs) + chain, err := GetAuthChain(ctx, r.DB.EventsFromIDs, req.EventIDs) if err != nil { return err } From 0fe0e2301dfd3855635860bf640ab5036f80b515 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 10 Dec 2020 11:00:17 +0000 Subject: [PATCH 66/68] Don't repeat outbound peeks for the same room ID to the same servers --- federationsender/internal/perform.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 02ea32df5c..3adf8fc94b 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -292,6 +292,16 @@ func (r *FederationSenderInternalAPI) PerformOutboundPeek( } request.ServerNames = uniqueList + // See if there's an existing outbound peek for this room ID with + // one of the specified servers. + if peeks, err := r.db.GetOutboundPeeks(ctx, request.RoomID); err == nil { + for _, peek := range peeks { + if _, ok := seenSet[peek.ServerName]; ok { + return nil + } + } + } + // Try each server that we were provided until we land on one that // successfully completes the peek var lastErr error From e0a35c05c5807cad7e4f7cf0da993851d68101f8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 10 Dec 2020 11:15:11 +0000 Subject: [PATCH 67/68] Fix lint --- federationapi/routing/peek.go | 2 +- federationsender/storage/postgres/storage.go | 2 +- federationsender/storage/shared/storage.go | 2 +- roomserver/internal/api.go | 2 +- roomserver/internal/perform/perform_peek.go | 2 +- roomserver/inthttp/client.go | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go index a036234ef5..8f83cb157d 100644 --- a/federationapi/routing/peek.go +++ b/federationapi/routing/peek.go @@ -18,8 +18,8 @@ import ( "net/http" "github.com/matrix-org/dendrite/clientapi/jsonerror" - "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" ) diff --git a/federationsender/storage/postgres/storage.go b/federationsender/storage/postgres/storage.go index 4758359ef3..b9827ca190 100644 --- a/federationsender/storage/postgres/storage.go +++ b/federationsender/storage/postgres/storage.go @@ -74,7 +74,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS } d.Database = shared.Database{ DB: d.db, - Cache: cache, + Cache: cache, Writer: d.writer, FederationSenderJoinedHosts: joinedHosts, FederationSenderQueuePDUs: queuePDUs, diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 08ef7be677..4c9490424b 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -28,7 +28,7 @@ import ( type Database struct { DB *sql.DB - Cache caching.FederationSenderCache + Cache caching.FederationSenderCache Writer sqlutil.Writer FederationSenderQueuePDUs tables.FederationSenderQueuePDUs FederationSenderQueueEDUs tables.FederationSenderQueueEDUs diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 06621146cc..5d4e8cab57 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -99,7 +99,7 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen r.InboundPeeker = &perform.InboundPeeker{ DB: r.DB, Inputer: r.Inputer, - } + } r.Unpeeker = &perform.Unpeeker{ ServerName: r.Cfg.Matrix.ServerName, Cfg: r.Cfg, diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index ea7c729466..443276cd7b 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -165,7 +165,7 @@ func (r *Peeker) performPeekRoomByID( ServerNames: req.ServerNames, // the servers to try peeking via } fedRes := fsAPI.PerformOutboundPeekResponse{} - r.FSAPI.PerformOutboundPeek(ctx, &fedReq, &fedRes) + _ = r.FSAPI.PerformOutboundPeek(ctx, &fedReq, &fedRes) if fedRes.LastError != nil { return "", &api.PerformError{ Code: api.PerformErrRemote, diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index c03ae90c81..5060530a49 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -27,13 +27,13 @@ const ( // Perform operations RoomserverPerformInvitePath = "/roomserver/performInvite" RoomserverPerformPeekPath = "/roomserver/performPeek" - RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" + RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" RoomserverPerformJoinPath = "/roomserver/performJoin" RoomserverPerformLeavePath = "/roomserver/performLeave" RoomserverPerformBackfillPath = "/roomserver/performBackfill" RoomserverPerformPublishPath = "/roomserver/performPublish" RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" - RoomserverPerformForgetPath = "/roomserver/performForget" + RoomserverPerformForgetPath = "/roomserver/performForget" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" From 4491e53cccee589dd555a992f3e6b340307c3d26 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 22 Jan 2021 13:57:04 +0000 Subject: [PATCH 68/68] Don't omitempty to appease sytest --- go.mod | 4 ++-- go.sum | 12 ++++-------- syncapi/types/types.go | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 58871dfb03..90117e6f64 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd - github.com/matrix-org/gomatrixserverlib v0.0.0-20210119115951-bd57c7cff614 + github.com/matrix-org/gomatrixserverlib v0.0.0-20210121144455-07960e3caf21 github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/mattn/go-sqlite3 v1.14.2 @@ -41,7 +41,7 @@ require ( go.uber.org/atomic v1.6.0 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/net v0.0.0-20200528225125-3c3fba18258b - golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect gopkg.in/h2non/bimg.v1 v1.1.4 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index b79337faef..3dee740c8a 100644 --- a/go.sum +++ b/go.sum @@ -567,12 +567,8 @@ github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bh github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd h1:xVrqJK3xHREMNjwjljkAUaadalWc0rRbmVuQatzmgwg= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210115150839-9ba5f3e11086 h1:nfGXVXx+cg1iBAWatukPsBe5OKsW+TdmF/qydnt04eg= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210115150839-9ba5f3e11086/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210115152401-7c4619994337 h1:HJ9iH00PwMDaXsH7vWpO7nRucz+d92QLoH0PNW7hs58= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210115152401-7c4619994337/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210119115951-bd57c7cff614 h1:X5FP1YOiGmPfpK4IAc8KyX8lOW4nC81/YZPTbOWAyKs= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210119115951-bd57c7cff614/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210121144455-07960e3caf21 h1:mDdMfekLt0dIoikYQZ+dJrQEUC/2uwS/QHeTQt9ELMw= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210121144455-07960e3caf21/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 h1:HJ6U3S3ljJqNffYMcIeAncp5qT/i+ZMiJ2JC2F0aXP4= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= @@ -999,8 +995,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY= -golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/syncapi/types/types.go b/syncapi/types/types.go index 4ccc8a4899..49fa1a1662 100644 --- a/syncapi/types/types.go +++ b/syncapi/types/types.go @@ -372,7 +372,7 @@ type Response struct { Leave map[string]LeaveResponse `json:"leave"` } `json:"rooms"` ToDevice struct { - Events []gomatrixserverlib.SendToDeviceEvent `json:"events,omitempty"` + Events []gomatrixserverlib.SendToDeviceEvent `json:"events"` } `json:"to_device"` DeviceLists struct { Changed []string `json:"changed,omitempty"`