From ccfcb2d2808f4daa960921174a40b52b956d3a2a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 18 Jan 2021 15:09:54 +0000 Subject: [PATCH 01/33] Create bin dir --- build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sh b/build.sh index 09ecb61ca..a49814084 100755 --- a/build.sh +++ b/build.sh @@ -17,6 +17,8 @@ else export FLAGS="" fi +mkdir -p bin + CGO_ENABLED=1 go build -trimpath -ldflags "$FLAGS" -v -o "bin/" ./cmd/... CGO_ENABLED=0 GOOS=js GOARCH=wasm go build -trimpath -ldflags "$FLAGS" -o bin/main.wasm ./cmd/dendritejs From 80aa9aa8b053655683cbdae1aeccb083166bc714 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Tue, 19 Jan 2021 17:14:25 +0000 Subject: [PATCH 02/33] Implement MSC2946 over federation (#1722) * Add fedsender dep on msc2946 * Add MSC2946Spaces to fsAPI * Add exclude_rooms impl * Implement fed spaces handler * Use stripped state not room version * Call federated spaces at the right time --- federationsender/api/api.go | 1 + federationsender/internal/api.go | 14 ++ federationsender/inthttp/client.go | 32 +++ federationsender/inthttp/server.go | 22 ++ go.mod | 11 +- go.sum | 29 +-- setup/mscs/msc2946/msc2946.go | 317 +++++++++++++++++++++++++---- setup/mscs/msc2946/msc2946_test.go | 22 +- setup/mscs/mscs.go | 2 +- 9 files changed, 379 insertions(+), 71 deletions(-) diff --git a/federationsender/api/api.go b/federationsender/api/api.go index e4d176b16..dfc2dd8a3 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -22,6 +22,7 @@ type FederationClient interface { GetEvent(ctx context.Context, s gomatrixserverlib.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error) GetServerKeys(ctx context.Context, matrixServer gomatrixserverlib.ServerName) (gomatrixserverlib.ServerKeys, error) MSC2836EventRelationships(ctx context.Context, dst gomatrixserverlib.ServerName, r gomatrixserverlib.MSC2836EventRelationshipsRequest, roomVersion gomatrixserverlib.RoomVersion) (res gomatrixserverlib.MSC2836EventRelationshipsResponse, err error) + MSC2946Spaces(ctx context.Context, dst gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest) (res gomatrixserverlib.MSC2946SpacesResponse, err error) LookupServerKeys(ctx context.Context, s gomatrixserverlib.ServerName, keyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) ([]gomatrixserverlib.ServerKeys, error) } diff --git a/federationsender/internal/api.go b/federationsender/internal/api.go index 407e7ffec..1de774ef3 100644 --- a/federationsender/internal/api.go +++ b/federationsender/internal/api.go @@ -244,3 +244,17 @@ func (a *FederationSenderInternalAPI) MSC2836EventRelationships( } return ires.(gomatrixserverlib.MSC2836EventRelationshipsResponse), nil } + +func (a *FederationSenderInternalAPI) MSC2946Spaces( + ctx context.Context, s gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest, +) (res gomatrixserverlib.MSC2946SpacesResponse, err error) { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + ires, err := a.doRequest(s, func() (interface{}, error) { + return a.federation.MSC2946Spaces(ctx, s, roomID, r) + }) + if err != nil { + return res, err + } + return ires.(gomatrixserverlib.MSC2946SpacesResponse), nil +} diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index fe98ff33d..81d3fc514 100644 --- a/federationsender/inthttp/client.go +++ b/federationsender/inthttp/client.go @@ -33,6 +33,7 @@ const ( FederationSenderGetServerKeysPath = "/federationsender/client/getServerKeys" FederationSenderLookupServerKeysPath = "/federationsender/client/lookupServerKeys" FederationSenderEventRelationshipsPath = "/federationsender/client/msc2836eventRelationships" + FederationSenderSpacesSummaryPath = "/federationsender/client/msc2946spacesSummary" ) // NewFederationSenderClient creates a FederationSenderInternalAPI implemented by talking to a HTTP POST API. @@ -449,3 +450,34 @@ func (h *httpFederationSenderInternalAPI) MSC2836EventRelationships( } return response.Res, nil } + +type spacesReq struct { + S gomatrixserverlib.ServerName + Req gomatrixserverlib.MSC2946SpacesRequest + RoomID string + Res gomatrixserverlib.MSC2946SpacesResponse + Err *api.FederationClientError +} + +func (h *httpFederationSenderInternalAPI) MSC2946Spaces( + ctx context.Context, dst gomatrixserverlib.ServerName, roomID string, r gomatrixserverlib.MSC2946SpacesRequest, +) (res gomatrixserverlib.MSC2946SpacesResponse, err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "MSC2946Spaces") + defer span.Finish() + + request := spacesReq{ + S: dst, + Req: r, + RoomID: roomID, + } + var response spacesReq + apiURL := h.federationSenderURL + FederationSenderSpacesSummaryPath + err = httputil.PostJSON(ctx, span, h.httpClient, apiURL, &request, &response) + if err != nil { + return res, err + } + if response.Err != nil { + return res, response.Err + } + return response.Res, nil +} diff --git a/federationsender/inthttp/server.go b/federationsender/inthttp/server.go index 293fb4209..be9951115 100644 --- a/federationsender/inthttp/server.go +++ b/federationsender/inthttp/server.go @@ -329,4 +329,26 @@ func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Route return util.JSONResponse{Code: http.StatusOK, JSON: request} }), ) + internalAPIMux.Handle( + FederationSenderSpacesSummaryPath, + httputil.MakeInternalAPI("MSC2946SpacesSummary", func(req *http.Request) util.JSONResponse { + var request spacesReq + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + res, err := intAPI.MSC2946Spaces(req.Context(), request.S, request.RoomID, request.Req) + if err != nil { + ferr, ok := err.(*api.FederationClientError) + if ok { + request.Err = ferr + } else { + request.Err = &api.FederationClientError{ + Err: err.Error(), + } + } + } + request.Res = res + return util.JSONResponse{Code: http.StatusOK, JSON: request} + }), + ) } diff --git a/go.mod b/go.mod index c94388412..58871dfb0 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-20210113173004-b1c67ac867cc + github.com/matrix-org/gomatrixserverlib v0.0.0-20210119115951-bd57c7cff614 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 @@ -33,16 +33,15 @@ require ( github.com/pressly/goose v2.7.0-rc5+incompatible github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 - github.com/tidwall/gjson v1.6.3 - github.com/tidwall/match v1.0.2 // indirect - github.com/tidwall/sjson v1.1.2 + github.com/tidwall/gjson v1.6.7 + github.com/tidwall/sjson v1.1.4 github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/uber/jaeger-lib v2.2.0+incompatible github.com/yggdrasil-network/yggdrasil-go v0.3.15-0.20201006093556-760d9a7fd5ee go.uber.org/atomic v1.6.0 - golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 + 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-20201119102817-f84b799fce68 // indirect + golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 // 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 7accb06ec..b79337fae 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +567,12 @@ 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-20210113173004-b1c67ac867cc h1:n2Hnbg8RZ4102Qmxie1riLkIyrqeqShJUILg1miSmDI= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210113173004-b1c67ac867cc/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +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/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= @@ -810,13 +814,12 @@ github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpP github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= -github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= -github.com/tidwall/gjson v1.6.3 h1:aHoiiem0dr7GHkW001T1SMTJ7X5PvyekH5WX0whWGnI= -github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= +github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= +github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/match v1.0.2 h1:uuqvHuBGSedK7awZ2YoAtpnimfwBGFjHuWLuLqQj+bU= -github.com/tidwall/match v1.0.2/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -824,8 +827,8 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.0.3 h1:DeF+0LZqvIt4fKYw41aPB29ZGlvwVkHKktoXJ1YW9Y8= github.com/tidwall/sjson v1.0.3/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= -github.com/tidwall/sjson v1.1.2 h1:NC5okI+tQ8OG/oyzchvwXXxRxCV/FVdhODbPKkQ25jQ= -github.com/tidwall/sjson v1.1.2/go.mod h1:SEzaDwxiPzKzNfUEO4HbYF/m4UCSJDsGgNqsS1LvdoY= +github.com/tidwall/sjson v1.1.4 h1:bTSsPLdAYF5QNLSwYsKfBKKTnlGbIuhqL3CpRsjzGhg= +github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/Lx1fg= github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U= github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= @@ -906,8 +909,8 @@ golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzht golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -996,8 +999,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-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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/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/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 2b5477376..c3a68632b 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -17,13 +17,17 @@ package msc2946 import ( "context" + "encoding/json" "fmt" "net/http" "strings" "sync" + "time" "github.com/gorilla/mux" chttputil "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" + fs "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/hooks" "github.com/matrix-org/dendrite/internal/httputil" roomserver "github.com/matrix-org/dendrite/roomserver/api" @@ -40,38 +44,16 @@ const ( ConstSpaceParentEventType = "org.matrix.msc1772.space.parent" ) -// SpacesRequest is the request body to POST /_matrix/client/r0/rooms/{roomID}/spaces -type SpacesRequest struct { - MaxRoomsPerSpace int `json:"max_rooms_per_space"` - Limit int `json:"limit"` - Batch string `json:"batch"` -} - // Defaults sets the request defaults -func (r *SpacesRequest) Defaults() { +func Defaults(r *gomatrixserverlib.MSC2946SpacesRequest) { r.Limit = 100 r.MaxRoomsPerSpace = -1 } -// SpacesResponse is the response body to POST /_matrix/client/r0/rooms/{roomID}/spaces -type SpacesResponse struct { - NextBatch string `json:"next_batch"` - // Rooms are nodes on the space graph. - Rooms []Room `json:"rooms"` - // Events are edges on the space graph, exclusively m.space.child or m.space.parent events - Events []gomatrixserverlib.ClientEvent `json:"events"` -} - -// Room is a node on the space graph -type Room struct { - gomatrixserverlib.PublicRoom - NumRefs int `json:"num_refs"` - RoomType string `json:"room_type"` -} - // Enable this MSC func Enable( base *setup.BaseDendrite, rsAPI roomserver.RoomserverInternalAPI, userAPI userapi.UserInternalAPI, + fsAPI fs.FederationSenderInternalAPI, keyRing gomatrixserverlib.JSONVerifier, ) error { db, err := NewDatabase(&base.Cfg.MSCs.Database) if err != nil { @@ -89,12 +71,69 @@ func Enable( }) base.PublicClientAPIMux.Handle("/unstable/rooms/{roomID}/spaces", - httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(db, rsAPI)), + httputil.MakeAuthAPI("spaces", userAPI, spacesHandler(db, rsAPI, fsAPI, base.Cfg.Global.ServerName)), ).Methods(http.MethodPost, http.MethodOptions) + + base.PublicFederationAPIMux.Handle("/unstable/spaces/{roomID}", httputil.MakeExternalAPI( + "msc2946_fed_spaces", func(req *http.Request) util.JSONResponse { + fedReq, errResp := gomatrixserverlib.VerifyHTTPRequest( + req, time.Now(), base.Cfg.Global.ServerName, keyRing, + ) + if fedReq == nil { + return errResp + } + // Extract the room ID from the request. Sanity check request data. + params, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + roomID := params["roomID"] + return federatedSpacesHandler(req.Context(), fedReq, roomID, db, rsAPI, fsAPI, base.Cfg.Global.ServerName) + }, + )).Methods(http.MethodPost, http.MethodOptions) return nil } -func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*http.Request, *userapi.Device) util.JSONResponse { +func federatedSpacesHandler( + ctx context.Context, fedReq *gomatrixserverlib.FederationRequest, roomID string, db Database, + rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI, + thisServer gomatrixserverlib.ServerName, +) util.JSONResponse { + inMemoryBatchCache := make(map[string]set) + var r gomatrixserverlib.MSC2946SpacesRequest + Defaults(&r) + if err := json.Unmarshal(fedReq.Content(), &r); err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()), + } + } + if r.Limit > 100 { + r.Limit = 100 + } + w := walker{ + req: &r, + rootRoomID: roomID, + serverName: fedReq.Origin(), + thisServer: thisServer, + ctx: ctx, + + db: db, + rsAPI: rsAPI, + fsAPI: fsAPI, + inMemoryBatchCache: inMemoryBatchCache, + } + res := w.walk() + return util.JSONResponse{ + Code: 200, + JSON: res, + } +} + +func spacesHandler( + db Database, rsAPI roomserver.RoomserverInternalAPI, fsAPI fs.FederationSenderInternalAPI, + thisServer gomatrixserverlib.ServerName, +) func(*http.Request, *userapi.Device) util.JSONResponse { return func(req *http.Request, device *userapi.Device) util.JSONResponse { inMemoryBatchCache := make(map[string]set) // Extract the room ID from the request. Sanity check request data. @@ -103,8 +142,8 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht return util.ErrorResponse(err) } roomID := params["roomID"] - var r SpacesRequest - r.Defaults() + var r gomatrixserverlib.MSC2946SpacesRequest + Defaults(&r) if resErr := chttputil.UnmarshalJSONRequest(req, &r); resErr != nil { return *resErr } @@ -115,10 +154,12 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht req: &r, rootRoomID: roomID, caller: device, + thisServer: thisServer, ctx: req.Context(), db: db, rsAPI: rsAPI, + fsAPI: fsAPI, inMemoryBatchCache: inMemoryBatchCache, } res := w.walk() @@ -130,11 +171,14 @@ func spacesHandler(db Database, rsAPI roomserver.RoomserverInternalAPI) func(*ht } type walker struct { - req *SpacesRequest + req *gomatrixserverlib.MSC2946SpacesRequest rootRoomID string caller *userapi.Device + serverName gomatrixserverlib.ServerName + thisServer gomatrixserverlib.ServerName db Database rsAPI roomserver.RoomserverInternalAPI + fsAPI fs.FederationSenderInternalAPI ctx context.Context // user ID|device ID|batch_num => event/room IDs sent to client @@ -142,10 +186,26 @@ type walker struct { mu sync.Mutex } +func (w *walker) roomIsExcluded(roomID string) bool { + for _, exclRoom := range w.req.ExcludeRooms { + if exclRoom == roomID { + return true + } + } + return false +} + +func (w *walker) callerID() string { + if w.caller != nil { + return w.caller.UserID + "|" + w.caller.ID + } + return string(w.serverName) +} + func (w *walker) alreadySent(id string) bool { w.mu.Lock() defer w.mu.Unlock() - m, ok := w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID] + m, ok := w.inMemoryBatchCache[w.callerID()] if !ok { return false } @@ -155,17 +215,17 @@ func (w *walker) alreadySent(id string) bool { func (w *walker) markSent(id string) { w.mu.Lock() defer w.mu.Unlock() - m := w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID] + m := w.inMemoryBatchCache[w.callerID()] if m == nil { m = make(set) } m[id] = true - w.inMemoryBatchCache[w.caller.UserID+"|"+w.caller.ID] = m + w.inMemoryBatchCache[w.callerID()] = m } // nolint:gocyclo -func (w *walker) walk() *SpacesResponse { - var res SpacesResponse +func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse { + var res gomatrixserverlib.MSC2946SpacesResponse // Begin walking the graph starting with the room ID in the request in a queue of unvisited rooms unvisited := []string{w.rootRoomID} processed := make(set) @@ -178,9 +238,20 @@ func (w *walker) walk() *SpacesResponse { } // Mark this room as processed. processed[roomID] = true + // Is the caller currently joined to the room or is the room `world_readable` // If no, skip this room. If yes, continue. - if !w.authorised(roomID) { + if !w.roomExists(roomID) || !w.authorised(roomID) { + // attempt to query this room over federation, as either we've never heard of it before + // or we've left it and hence are not authorised (but info may be exposed regardless) + fedRes, err := w.federatedRoomInfo(roomID) + if err != nil { + util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces") + continue + } + if fedRes != nil { + res = combineResponses(res, *fedRes) + } continue } // Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get @@ -194,7 +265,7 @@ func (w *walker) walk() *SpacesResponse { // If this room has not ever been in `rooms` (across multiple requests), extract the // `PublicRoomsChunk` for this room. - if !w.alreadySent(roomID) { + if !w.alreadySent(roomID) && !w.roomIsExcluded(roomID) { pubRoom := w.publicRoomsChunk(roomID) roomType := "" create := w.stateEvent(roomID, gomatrixserverlib.MRoomCreate, "") @@ -204,11 +275,12 @@ func (w *walker) walk() *SpacesResponse { } // Add the total number of events to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`. - res.Rooms = append(res.Rooms, Room{ + res.Rooms = append(res.Rooms, gomatrixserverlib.MSC2946Room{ PublicRoom: *pubRoom, NumRefs: refs.len(), RoomType: roomType, }) + w.markSent(roomID) } uniqueRooms := make(set) @@ -218,9 +290,11 @@ func (w *walker) walk() *SpacesResponse { if w.rootRoomID == roomID { for _, ev := range refs.events() { if !w.alreadySent(ev.EventID()) { - res.Events = append(res.Events, gomatrixserverlib.HeaderedToClientEvent( - ev, gomatrixserverlib.FormatAll, - )) + strip := stripped(ev.Event) + if strip == nil { + continue + } + res.Events = append(res.Events, *strip) uniqueRooms[ev.RoomID()] = true uniqueRooms[SpaceTarget(ev)] = true w.markSent(ev.EventID()) @@ -240,9 +314,16 @@ func (w *walker) walk() *SpacesResponse { if w.alreadySent(ev.EventID()) { continue } - res.Events = append(res.Events, gomatrixserverlib.HeaderedToClientEvent( - ev, gomatrixserverlib.FormatAll, - )) + // Skip the room if it's part of exclude_rooms but ONLY IF the source matches, as we still + // want to catch arrows which point to excluded rooms. + if w.roomIsExcluded(ev.RoomID()) { + continue + } + strip := stripped(ev.Event) + if strip == nil { + continue + } + res.Events = append(res.Events, *strip) uniqueRooms[ev.RoomID()] = true uniqueRooms[SpaceTarget(ev)] = true w.markSent(ev.EventID()) @@ -289,8 +370,120 @@ func (w *walker) publicRoomsChunk(roomID string) *gomatrixserverlib.PublicRoom { return &pubRooms[0] } +// federatedRoomInfo returns more of the spaces graph from another server. Returns nil if this was +// unsuccessful. +func (w *walker) federatedRoomInfo(roomID string) (*gomatrixserverlib.MSC2946SpacesResponse, error) { + // only do federated requests for client requests + if w.caller == nil { + return nil, nil + } + // extract events which point to this room ID and extract their vias + events, err := w.db.References(w.ctx, roomID) + if err != nil { + return nil, fmt.Errorf("failed to get References events: %w", err) + } + vias := make(set) + for _, ev := range events { + if ev.StateKeyEquals(roomID) { + // event points at this room, extract vias + content := struct { + Vias []string `json:"via"` + }{} + if err = json.Unmarshal(ev.Content(), &content); err != nil { + continue // silently ignore corrupted state events + } + for _, v := range content.Vias { + vias[v] = true + } + } + } + util.GetLogger(w.ctx).Infof("Querying federatedRoomInfo via %+v", vias) + ctx := context.Background() + // query more of the spaces graph using these servers + for serverName := range vias { + if serverName == string(w.thisServer) { + continue + } + res, err := w.fsAPI.MSC2946Spaces(ctx, gomatrixserverlib.ServerName(serverName), roomID, gomatrixserverlib.MSC2946SpacesRequest{ + Limit: w.req.Limit, + MaxRoomsPerSpace: w.req.MaxRoomsPerSpace, + }) + if err != nil { + util.GetLogger(w.ctx).WithError(err).Warnf("failed to call MSC2946Spaces on server %s", serverName) + continue + } + return &res, nil + } + return nil, nil +} + +func (w *walker) roomExists(roomID string) bool { + var queryRes roomserver.QueryServerJoinedToRoomResponse + err := w.rsAPI.QueryServerJoinedToRoom(w.ctx, &roomserver.QueryServerJoinedToRoomRequest{ + RoomID: roomID, + ServerName: w.thisServer, + }, &queryRes) + if err != nil { + util.GetLogger(w.ctx).WithError(err).Error("failed to QueryServerJoinedToRoom") + return false + } + // if the room exists but we aren't in the room then we might have stale data so we want to fetch + // it fresh via federation + return queryRes.RoomExists && queryRes.IsInRoom +} + // authorised returns true iff the user is joined this room or the room is world_readable func (w *walker) authorised(roomID string) bool { + if w.caller != nil { + return w.authorisedUser(roomID) + } + return w.authorisedServer(roomID) +} + +// authorisedServer returns true iff the server is joined this room or the room is world_readable +func (w *walker) authorisedServer(roomID string) bool { + // Check history visibility first + hisVisTuple := gomatrixserverlib.StateKeyTuple{ + EventType: gomatrixserverlib.MRoomHistoryVisibility, + StateKey: "", + } + var queryRoomRes roomserver.QueryCurrentStateResponse + err := w.rsAPI.QueryCurrentState(w.ctx, &roomserver.QueryCurrentStateRequest{ + RoomID: roomID, + StateTuples: []gomatrixserverlib.StateKeyTuple{ + hisVisTuple, + }, + }, &queryRoomRes) + if err != nil { + util.GetLogger(w.ctx).WithError(err).Error("failed to QueryCurrentState") + return false + } + hisVisEv := queryRoomRes.StateEvents[hisVisTuple] + if hisVisEv != nil { + hisVis, _ := hisVisEv.HistoryVisibility() + if hisVis == "world_readable" { + return true + } + } + // check if server is joined to the room + var queryRes fs.QueryJoinedHostServerNamesInRoomResponse + err = w.fsAPI.QueryJoinedHostServerNamesInRoom(w.ctx, &fs.QueryJoinedHostServerNamesInRoomRequest{ + RoomID: roomID, + }, &queryRes) + if err != nil { + util.GetLogger(w.ctx).WithError(err).Error("failed to QueryJoinedHostServerNamesInRoom") + return false + } + for _, srv := range queryRes.ServerNames { + if srv == w.serverName { + return true + } + } + return false +} + +// authorisedUser returns true iff the user is joined this room or the room is world_readable +func (w *walker) authorisedUser(roomID string) bool { hisVisTuple := gomatrixserverlib.StateKeyTuple{ EventType: gomatrixserverlib.MRoomHistoryVisibility, StateKey: "", @@ -374,3 +567,41 @@ func (el eventLookup) events() (events []*gomatrixserverlib.HeaderedEvent) { } type set map[string]bool + +func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEvent { + if ev.StateKey() == nil { + return nil + } + return &gomatrixserverlib.MSC2946StrippedEvent{ + Type: ev.Type(), + StateKey: *ev.StateKey(), + Content: ev.Content(), + Sender: ev.Sender(), + RoomID: ev.RoomID(), + } +} + +func combineResponses(local, remote gomatrixserverlib.MSC2946SpacesResponse) gomatrixserverlib.MSC2946SpacesResponse { + knownRooms := make(set) + for _, room := range local.Rooms { + knownRooms[room.RoomID] = true + } + knownEvents := make(set) + for _, event := range local.Events { + knownEvents[event.RoomID+event.Type+event.StateKey] = true + } + // mux in remote entries if and only if they aren't present already + for _, room := range remote.Rooms { + if knownRooms[room.RoomID] { + continue + } + local.Rooms = append(local.Rooms, room) + } + for _, event := range remote.Events { + if knownEvents[event.RoomID+event.Type+event.StateKey] { + continue + } + local.Events = append(local.Events, event) + } + return local +} diff --git a/setup/mscs/msc2946/msc2946_test.go b/setup/mscs/msc2946/msc2946_test.go index d2d935e86..4f180a988 100644 --- a/setup/mscs/msc2946/msc2946_test.go +++ b/setup/mscs/msc2946/msc2946_test.go @@ -41,6 +41,7 @@ var ( client = &http.Client{ Timeout: 10 * time.Second, } + roomVer = gomatrixserverlib.RoomVersionV6 ) // Basic sanity check of MSC2946 logic. Tests a single room with a few state events @@ -269,13 +270,13 @@ func TestMSC2946(t *testing.T) { }) } -func newReq(t *testing.T, jsonBody map[string]interface{}) *msc2946.SpacesRequest { +func newReq(t *testing.T, jsonBody map[string]interface{}) *gomatrixserverlib.MSC2946SpacesRequest { t.Helper() b, err := json.Marshal(jsonBody) if err != nil { t.Fatalf("Failed to marshal request: %s", err) } - var r msc2946.SpacesRequest + var r gomatrixserverlib.MSC2946SpacesRequest if err := json.Unmarshal(b, &r); err != nil { t.Fatalf("Failed to unmarshal request: %s", err) } @@ -299,10 +300,10 @@ func runServer(t *testing.T, router *mux.Router) func() { } } -func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *msc2946.SpacesRequest) *msc2946.SpacesResponse { +func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *gomatrixserverlib.MSC2946SpacesRequest) *gomatrixserverlib.MSC2946SpacesResponse { t.Helper() - var r msc2946.SpacesRequest - r.Defaults() + var r gomatrixserverlib.MSC2946SpacesRequest + msc2946.Defaults(&r) data, err := json.Marshal(req) if err != nil { t.Fatalf("failed to marshal request: %s", err) @@ -324,7 +325,7 @@ func postSpaces(t *testing.T, expectCode int, accessToken, roomID string, req *m t.Fatalf("wrong response code, got %d want %d - body: %s", res.StatusCode, expectCode, string(body)) } if res.StatusCode == 200 { - var result msc2946.SpacesResponse + var result gomatrixserverlib.MSC2946SpacesResponse body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("response 200 OK but failed to read response body: %s", err) @@ -400,6 +401,12 @@ type testRoomserverAPI struct { pubRoomState map[string]map[gomatrixserverlib.StateKeyTuple]string } +func (r *testRoomserverAPI) QueryServerJoinedToRoom(ctx context.Context, req *roomserver.QueryServerJoinedToRoomRequest, res *roomserver.QueryServerJoinedToRoomResponse) error { + res.IsInRoom = true + res.RoomExists = true + return nil +} + func (r *testRoomserverAPI) QueryBulkStateContent(ctx context.Context, req *roomserver.QueryBulkStateContentRequest, res *roomserver.QueryBulkStateContentResponse) error { res.Rooms = make(map[string]map[gomatrixserverlib.StateKeyTuple]string) for _, roomID := range req.RoomIDs { @@ -452,7 +459,7 @@ func injectEvents(t *testing.T, userAPI userapi.UserInternalAPI, rsAPI roomserve PublicFederationAPIMux: mux.NewRouter().PathPrefix(httputil.PublicFederationPathPrefix).Subrouter(), } - err := msc2946.Enable(base, rsAPI, userAPI) + err := msc2946.Enable(base, rsAPI, userAPI, nil, nil) if err != nil { t.Fatalf("failed to enable MSC2946: %s", err) } @@ -472,7 +479,6 @@ type fledglingEvent struct { func mustCreateEvent(t *testing.T, ev fledglingEvent) (result *gomatrixserverlib.HeaderedEvent) { t.Helper() - roomVer := gomatrixserverlib.RoomVersionV6 seed := make([]byte, ed25519.SeedSize) // zero seed key := ed25519.NewKeyFromSeed(seed) eb := gomatrixserverlib.EventBuilder{ diff --git a/setup/mscs/mscs.go b/setup/mscs/mscs.go index bf2103629..027885c8d 100644 --- a/setup/mscs/mscs.go +++ b/setup/mscs/mscs.go @@ -41,7 +41,7 @@ func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) e case "msc2836": return msc2836.Enable(base, monolith.RoomserverAPI, monolith.FederationSenderAPI, monolith.UserAPI, monolith.KeyRing) case "msc2946": - return msc2946.Enable(base, monolith.RoomserverAPI, monolith.UserAPI) + return msc2946.Enable(base, monolith.RoomserverAPI, monolith.UserAPI, monolith.FederationSenderAPI, monolith.KeyRing) default: return fmt.Errorf("EnableMSC: unknown msc '%s'", msc) } From b70238f2d5579876d834ec393f178161599a2fa7 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 19 Jan 2021 18:00:42 +0000 Subject: [PATCH 03/33] Basic sync filtering (#1721) * Add some filtering (postgres only for now) * Fix build error * Try to use request filter * Use default filter as a template when retrieving from the database * Remove unused strut * Update sytest-whitelist * Add filtering to SelectEarlyEvents * Fix Postgres selectEarlyEvents query * Attempt filtering on SQLite * Test limit, set field for limit/order in prepareWithFilters * Remove debug logging, add comments * Tweaks, debug logging * Separate SQLite stream IDs * Fix filtering in current state table * Fix lock issues * More tweaks * Current state requires room ID * Review comments --- syncapi/notifier/notifier_test.go | 1 - syncapi/routing/messages.go | 5 +- syncapi/storage/interface.go | 4 +- syncapi/storage/postgres/filter_table.go | 2 +- .../postgres/output_room_events_table.go | 42 ++++- syncapi/storage/shared/syncserver.go | 10 +- syncapi/storage/sqlite3/account_data_table.go | 2 +- .../sqlite3/current_room_state_table.go | 37 ++--- syncapi/storage/sqlite3/filter_table.go | 2 +- syncapi/storage/sqlite3/filtering.go | 76 +++++++++ syncapi/storage/sqlite3/invites_table.go | 4 +- .../sqlite3/output_room_events_table.go | 150 +++++++++--------- syncapi/storage/sqlite3/peeks_table.go | 6 +- syncapi/storage/sqlite3/stream_id_table.go | 26 ++- syncapi/storage/tables/interface.go | 4 +- syncapi/streams/stream_pdu.go | 22 +-- syncapi/sync/request.go | 38 ++--- syncapi/types/provider.go | 3 +- sytest-whitelist | 5 + 19 files changed, 279 insertions(+), 160 deletions(-) create mode 100644 syncapi/storage/sqlite3/filtering.go diff --git a/syncapi/notifier/notifier_test.go b/syncapi/notifier/notifier_test.go index 8b9425e37..1401fc676 100644 --- a/syncapi/notifier/notifier_test.go +++ b/syncapi/notifier/notifier_test.go @@ -367,7 +367,6 @@ func newTestSyncRequest(userID, deviceID string, since types.StreamingToken) typ Timeout: 1 * time.Minute, Since: since, WantFullState: false, - Limit: 20, Log: util.GetLogger(context.TODO()), Context: context.TODO(), } diff --git a/syncapi/routing/messages.go b/syncapi/routing/messages.go index e294c8803..ba739148d 100644 --- a/syncapi/routing/messages.go +++ b/syncapi/routing/messages.go @@ -235,12 +235,15 @@ func (r *messagesReq) retrieveEvents() ( clientEvents []gomatrixserverlib.ClientEvent, start, end types.TopologyToken, err error, ) { + eventFilter := gomatrixserverlib.DefaultRoomEventFilter() + eventFilter.Limit = r.limit + // Retrieve the events from the local database. var streamEvents []types.StreamEvent if r.fromStream != nil { toStream := r.to.StreamToken() streamEvents, err = r.db.GetEventsInStreamingRange( - r.ctx, r.fromStream, &toStream, r.roomID, r.limit, r.backwardOrdering, + r.ctx, r.fromStream, &toStream, r.roomID, &eventFilter, r.backwardOrdering, ) } else { streamEvents, err = r.db.GetEventsInTopologicalRange( diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index a51ab4e0d..22d801617 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -40,7 +40,7 @@ type Database interface { GetStateDeltas(ctx context.Context, device *userapi.Device, r types.Range, userID string, stateFilter *gomatrixserverlib.StateFilter) ([]types.StateDelta, []string, error) RoomIDsWithMembership(ctx context.Context, userID string, membership string) ([]string, error) - RecentEvents(ctx context.Context, roomID string, r types.Range, limit int, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) + RecentEvents(ctx context.Context, roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) GetBackwardTopologyPos(ctx context.Context, events []types.StreamEvent) (types.TopologyToken, error) PositionInTopology(ctx context.Context, eventID string) (pos types.StreamPosition, spos types.StreamPosition, err error) @@ -105,7 +105,7 @@ type Database interface { // Returns an error if there was a problem communicating with the database. DeletePeeks(ctx context.Context, RoomID, UserID string) (types.StreamPosition, error) // GetEventsInStreamingRange retrieves all of the events on a given ordering using the given extremities and limit. - GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) + GetEventsInStreamingRange(ctx context.Context, from, to *types.StreamingToken, roomID string, eventFilter *gomatrixserverlib.RoomEventFilter, backwardOrdering bool) (events []types.StreamEvent, err error) // GetEventsInTopologicalRange retrieves all of the events on a given ordering using the given extremities and limit. GetEventsInTopologicalRange(ctx context.Context, from, to *types.TopologyToken, roomID string, limit int, backwardOrdering bool) (events []types.StreamEvent, err error) // EventPositionInTopology returns the depth and stream position of the given event. diff --git a/syncapi/storage/postgres/filter_table.go b/syncapi/storage/postgres/filter_table.go index beeb864ba..dfd3d6963 100644 --- a/syncapi/storage/postgres/filter_table.go +++ b/syncapi/storage/postgres/filter_table.go @@ -83,7 +83,7 @@ func (s *filterStatements) SelectFilter( } // Unmarshal JSON into Filter struct - var filter gomatrixserverlib.Filter + filter := gomatrixserverlib.DefaultFilter() if err = json.Unmarshal(filterData, &filter); err != nil { return nil, err } diff --git a/syncapi/storage/postgres/output_room_events_table.go b/syncapi/storage/postgres/output_room_events_table.go index f4bbebd26..28668de0e 100644 --- a/syncapi/storage/postgres/output_room_events_table.go +++ b/syncapi/storage/postgres/output_room_events_table.go @@ -84,17 +84,29 @@ const selectEventsSQL = "" + const selectRecentEventsSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + " WHERE room_id = $1 AND id > $2 AND id <= $3" + - " ORDER BY id DESC LIMIT $4" + " AND ( $4::text[] IS NULL OR sender = ANY($4) )" + + " AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" + + " AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" + + " AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" + + " ORDER BY id DESC LIMIT $8" const selectRecentEventsForSyncSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + " WHERE room_id = $1 AND id > $2 AND id <= $3 AND exclude_from_sync = FALSE" + - " ORDER BY id DESC LIMIT $4" + " AND ( $4::text[] IS NULL OR sender = ANY($4) )" + + " AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" + + " AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" + + " AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" + + " ORDER BY id DESC LIMIT $8" const selectEarlyEventsSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + " WHERE room_id = $1 AND id > $2 AND id <= $3" + - " ORDER BY id ASC LIMIT $4" + " AND ( $4::text[] IS NULL OR sender = ANY($4) )" + + " AND ( $5::text[] IS NULL OR NOT(sender = ANY($5)) )" + + " AND ( $6::text[] IS NULL OR type LIKE ANY($6) )" + + " AND ( $7::text[] IS NULL OR NOT(type LIKE ANY($7)) )" + + " ORDER BY id ASC LIMIT $8" const selectMaxEventIDSQL = "" + "SELECT MAX(id) FROM syncapi_output_room_events" @@ -322,7 +334,7 @@ func (s *outputRoomEventsStatements) InsertEvent( // from sync. func (s *outputRoomEventsStatements) SelectRecentEvents( ctx context.Context, txn *sql.Tx, - roomID string, r types.Range, limit int, + roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool, ) ([]types.StreamEvent, bool, error) { var stmt *sql.Stmt @@ -331,7 +343,14 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( } else { stmt = sqlutil.TxStmt(txn, s.selectRecentEventsStmt) } - rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit+1) + rows, err := stmt.QueryContext( + ctx, roomID, r.Low(), r.High(), + pq.StringArray(eventFilter.Senders), + pq.StringArray(eventFilter.NotSenders), + pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.Types)), + pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.NotTypes)), + eventFilter.Limit+1, + ) if err != nil { return nil, false, err } @@ -350,7 +369,7 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( } // we queried for 1 more than the limit, so if we returned one more mark limited=true limited := false - if len(events) > limit { + if len(events) > eventFilter.Limit { limited = true // re-slice the extra (oldest) event out: in chronological order this is the first entry, else the last. if chronologicalOrder { @@ -367,10 +386,17 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( // from a given position, up to a maximum of 'limit'. func (s *outputRoomEventsStatements) SelectEarlyEvents( ctx context.Context, txn *sql.Tx, - roomID string, r types.Range, limit int, + roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, ) ([]types.StreamEvent, error) { stmt := sqlutil.TxStmt(txn, s.selectEarlyEventsStmt) - rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit) + rows, err := stmt.QueryContext( + ctx, roomID, r.Low(), r.High(), + pq.StringArray(eventFilter.Senders), + pq.StringArray(eventFilter.NotSenders), + pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.Types)), + pq.StringArray(filterConvertTypeWildcardToSQL(eventFilter.NotTypes)), + eventFilter.Limit, + ) if err != nil { return nil, err } diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 5b06aabcd..9df076935 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -110,8 +110,8 @@ func (d *Database) RoomIDsWithMembership(ctx context.Context, userID string, mem return d.CurrentRoomState.SelectRoomIDsWithMembership(ctx, nil, userID, membership) } -func (d *Database) RecentEvents(ctx context.Context, roomID string, r types.Range, limit int, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) { - return d.OutputEvents.SelectRecentEvents(ctx, nil, roomID, r, limit, chronologicalOrder, onlySyncEvents) +func (d *Database) RecentEvents(ctx context.Context, roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) { + return d.OutputEvents.SelectRecentEvents(ctx, nil, roomID, r, eventFilter, chronologicalOrder, onlySyncEvents) } func (d *Database) PositionInTopology(ctx context.Context, eventID string) (pos types.StreamPosition, spos types.StreamPosition, err error) { @@ -151,7 +151,7 @@ func (d *Database) Events(ctx context.Context, eventIDs []string) ([]*gomatrixse func (d *Database) GetEventsInStreamingRange( ctx context.Context, from, to *types.StreamingToken, - roomID string, limit int, + roomID string, eventFilter *gomatrixserverlib.RoomEventFilter, backwardOrdering bool, ) (events []types.StreamEvent, err error) { r := types.Range{ @@ -162,14 +162,14 @@ func (d *Database) GetEventsInStreamingRange( if backwardOrdering { // When using backward ordering, we want the most recent events first. if events, _, err = d.OutputEvents.SelectRecentEvents( - ctx, nil, roomID, r, limit, false, false, + ctx, nil, roomID, r, eventFilter, false, false, ); err != nil { return } } else { // When using forward ordering, we want the least recent events first. if events, err = d.OutputEvents.SelectEarlyEvents( - ctx, nil, roomID, r, limit, + ctx, nil, roomID, r, eventFilter, ); err != nil { return } diff --git a/syncapi/storage/sqlite3/account_data_table.go b/syncapi/storage/sqlite3/account_data_table.go index 4bcc06ed1..1c65cb6a9 100644 --- a/syncapi/storage/sqlite3/account_data_table.go +++ b/syncapi/storage/sqlite3/account_data_table.go @@ -82,7 +82,7 @@ func (s *accountDataStatements) InsertAccountData( ctx context.Context, txn *sql.Tx, userID, roomID, dataType string, ) (pos types.StreamPosition, err error) { - pos, err = s.streamIDStatements.nextStreamID(ctx, txn) + pos, err = s.streamIDStatements.nextAccountDataID(ctx, txn) if err != nil { return } diff --git a/syncapi/storage/sqlite3/current_room_state_table.go b/syncapi/storage/sqlite3/current_room_state_table.go index ac6590575..55ed27a41 100644 --- a/syncapi/storage/sqlite3/current_room_state_table.go +++ b/syncapi/storage/sqlite3/current_room_state_table.go @@ -19,6 +19,7 @@ import ( "context" "database/sql" "encoding/json" + "fmt" "strings" "github.com/matrix-org/dendrite/internal" @@ -66,13 +67,8 @@ const selectRoomIDsWithMembershipSQL = "" + "SELECT DISTINCT room_id FROM syncapi_current_room_state WHERE type = 'm.room.member' AND state_key = $1 AND membership = $2" const selectCurrentStateSQL = "" + - "SELECT event_id, headered_event_json FROM syncapi_current_room_state WHERE room_id = $1" + - " AND ( $2 IS NULL OR sender IN ($2) )" + - " AND ( $3 IS NULL OR NOT(sender IN ($3)) )" + - " AND ( $4 IS NULL OR type IN ($4) )" + - " AND ( $5 IS NULL OR NOT(type IN ($5)) )" + - " AND ( $6 IS NULL OR contains_url = $6 )" + - " LIMIT $7" + "SELECT event_id, headered_event_json FROM syncapi_current_room_state WHERE room_id = $1" + // WHEN, ORDER BY and LIMIT will be added by prepareWithFilter const selectJoinedUsersSQL = "" + "SELECT room_id, state_key FROM syncapi_current_room_state WHERE type = 'm.room.member' AND membership = 'join'" @@ -95,7 +91,6 @@ type currentRoomStateStatements struct { deleteRoomStateByEventIDStmt *sql.Stmt DeleteRoomStateForRoomStmt *sql.Stmt selectRoomIDsWithMembershipStmt *sql.Stmt - selectCurrentStateStmt *sql.Stmt selectJoinedUsersStmt *sql.Stmt selectStateEventStmt *sql.Stmt } @@ -121,9 +116,6 @@ func NewSqliteCurrentRoomStateTable(db *sql.DB, streamID *streamIDStatements) (t if s.selectRoomIDsWithMembershipStmt, err = db.Prepare(selectRoomIDsWithMembershipSQL); err != nil { return nil, err } - if s.selectCurrentStateStmt, err = db.Prepare(selectCurrentStateSQL); err != nil { - return nil, err - } if s.selectJoinedUsersStmt, err = db.Prepare(selectJoinedUsersSQL); err != nil { return nil, err } @@ -185,17 +177,22 @@ func (s *currentRoomStateStatements) SelectRoomIDsWithMembership( // CurrentState returns all the current state events for the given room. func (s *currentRoomStateStatements) SelectCurrentState( ctx context.Context, txn *sql.Tx, roomID string, - stateFilterPart *gomatrixserverlib.StateFilter, + stateFilter *gomatrixserverlib.StateFilter, ) ([]*gomatrixserverlib.HeaderedEvent, error) { - stmt := sqlutil.TxStmt(txn, s.selectCurrentStateStmt) - rows, err := stmt.QueryContext(ctx, roomID, - nil, // FIXME: pq.StringArray(stateFilterPart.Senders), - nil, // FIXME: pq.StringArray(stateFilterPart.NotSenders), - nil, // FIXME: pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.Types)), - nil, // FIXME: pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.NotTypes)), - stateFilterPart.ContainsURL, - stateFilterPart.Limit, + stmt, params, err := prepareWithFilters( + s.db, txn, selectCurrentStateSQL, + []interface{}{ + roomID, + }, + stateFilter.Senders, stateFilter.NotSenders, + stateFilter.Types, stateFilter.NotTypes, + stateFilter.Limit, FilterOrderNone, ) + if err != nil { + return nil, fmt.Errorf("s.prepareWithFilters: %w", err) + } + + rows, err := stmt.QueryContext(ctx, params...) if err != nil { return nil, err } diff --git a/syncapi/storage/sqlite3/filter_table.go b/syncapi/storage/sqlite3/filter_table.go index 3092bcd7d..0cfebef2a 100644 --- a/syncapi/storage/sqlite3/filter_table.go +++ b/syncapi/storage/sqlite3/filter_table.go @@ -87,7 +87,7 @@ func (s *filterStatements) SelectFilter( } // Unmarshal JSON into Filter struct - var filter gomatrixserverlib.Filter + filter := gomatrixserverlib.DefaultFilter() if err = json.Unmarshal(filterData, &filter); err != nil { return nil, err } diff --git a/syncapi/storage/sqlite3/filtering.go b/syncapi/storage/sqlite3/filtering.go new file mode 100644 index 000000000..0faf5297a --- /dev/null +++ b/syncapi/storage/sqlite3/filtering.go @@ -0,0 +1,76 @@ +package sqlite3 + +import ( + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" +) + +type FilterOrder int + +const ( + FilterOrderNone = iota + FilterOrderAsc + FilterOrderDesc +) + +// prepareWithFilters returns a prepared statement with the +// relevant filters included. It also includes an []interface{} +// list of all the relevant parameters to pass straight to +// QueryContext, QueryRowContext etc. +// We don't take the filter object directly here because the +// fields might come from either a StateFilter or an EventFilter, +// and it's easier just to have the caller extract the relevant +// parts. +func prepareWithFilters( + db *sql.DB, txn *sql.Tx, query string, params []interface{}, + senders, notsenders, types, nottypes []string, + limit int, order FilterOrder, +) (*sql.Stmt, []interface{}, error) { + offset := len(params) + if count := len(senders); count > 0 { + query += " AND sender IN " + sqlutil.QueryVariadicOffset(count, offset) + for _, v := range senders { + params, offset = append(params, v), offset+1 + } + } + if count := len(notsenders); count > 0 { + query += " AND sender NOT IN " + sqlutil.QueryVariadicOffset(count, offset) + for _, v := range notsenders { + params, offset = append(params, v), offset+1 + } + } + if count := len(types); count > 0 { + query += " AND type IN " + sqlutil.QueryVariadicOffset(count, offset) + for _, v := range types { + params, offset = append(params, v), offset+1 + } + } + if count := len(nottypes); count > 0 { + query += " AND type NOT IN " + sqlutil.QueryVariadicOffset(count, offset) + for _, v := range nottypes { + params, offset = append(params, v), offset+1 + } + } + switch order { + case FilterOrderAsc: + query += " ORDER BY id ASC" + case FilterOrderDesc: + query += " ORDER BY id DESC" + } + query += fmt.Sprintf(" LIMIT $%d", offset+1) + params = append(params, limit) + + var stmt *sql.Stmt + var err error + if txn != nil { + stmt, err = txn.Prepare(query) + } else { + stmt, err = db.Prepare(query) + } + if err != nil { + return nil, nil, fmt.Errorf("s.db.Prepare: %w", err) + } + return stmt, params, nil +} diff --git a/syncapi/storage/sqlite3/invites_table.go b/syncapi/storage/sqlite3/invites_table.go index f9dcfdbcd..7498fd683 100644 --- a/syncapi/storage/sqlite3/invites_table.go +++ b/syncapi/storage/sqlite3/invites_table.go @@ -93,7 +93,7 @@ func NewSqliteInvitesTable(db *sql.DB, streamID *streamIDStatements) (tables.Inv func (s *inviteEventsStatements) InsertInviteEvent( ctx context.Context, txn *sql.Tx, inviteEvent *gomatrixserverlib.HeaderedEvent, ) (streamPos types.StreamPosition, err error) { - streamPos, err = s.streamIDStatements.nextStreamID(ctx, txn) + streamPos, err = s.streamIDStatements.nextInviteID(ctx, txn) if err != nil { return } @@ -119,7 +119,7 @@ func (s *inviteEventsStatements) InsertInviteEvent( func (s *inviteEventsStatements) DeleteInviteEvent( ctx context.Context, txn *sql.Tx, inviteEventID string, ) (types.StreamPosition, error) { - streamPos, err := s.streamIDStatements.nextStreamID(ctx, txn) + streamPos, err := s.streamIDStatements.nextInviteID(ctx, txn) if err != nil { return streamPos, err } diff --git a/syncapi/storage/sqlite3/output_room_events_table.go b/syncapi/storage/sqlite3/output_room_events_table.go index edbd36fb1..019aba8b3 100644 --- a/syncapi/storage/sqlite3/output_room_events_table.go +++ b/syncapi/storage/sqlite3/output_room_events_table.go @@ -19,6 +19,7 @@ import ( "context" "database/sql" "encoding/json" + "fmt" "sort" "github.com/matrix-org/dendrite/internal" @@ -60,18 +61,18 @@ const selectEventsSQL = "" + const selectRecentEventsSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + - " WHERE room_id = $1 AND id > $2 AND id <= $3" + - " ORDER BY id DESC LIMIT $4" + " WHERE room_id = $1 AND id > $2 AND id <= $3" + // WHEN, ORDER BY and LIMIT are appended by prepareWithFilters const selectRecentEventsForSyncSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + - " WHERE room_id = $1 AND id > $2 AND id <= $3 AND exclude_from_sync = FALSE" + - " ORDER BY id DESC LIMIT $4" + " WHERE room_id = $1 AND id > $2 AND id <= $3 AND exclude_from_sync = FALSE" + // WHEN, ORDER BY and LIMIT are appended by prepareWithFilters const selectEarlyEventsSQL = "" + "SELECT event_id, id, headered_event_json, session_id, exclude_from_sync, transaction_id FROM syncapi_output_room_events" + - " WHERE room_id = $1 AND id > $2 AND id <= $3" + - " ORDER BY id ASC LIMIT $4" + " WHERE room_id = $1 AND id > $2 AND id <= $3" + // WHEN, ORDER BY and LIMIT are appended by prepareWithFilters const selectMaxEventIDSQL = "" + "SELECT MAX(id) FROM syncapi_output_room_events" @@ -79,45 +80,24 @@ const selectMaxEventIDSQL = "" + const updateEventJSONSQL = "" + "UPDATE syncapi_output_room_events SET headered_event_json=$1 WHERE event_id=$2" -// In order for us to apply the state updates correctly, rows need to be ordered in the order they were received (id). -/* - $1 = oldPos, - $2 = newPos, - $3 = pq.StringArray(stateFilterPart.Senders), - $4 = pq.StringArray(stateFilterPart.NotSenders), - $5 = pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.Types)), - $6 = pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.NotTypes)), - $7 = stateFilterPart.ContainsURL, - $8 = stateFilterPart.Limit, -*/ const selectStateInRangeSQL = "" + "SELECT id, headered_event_json, exclude_from_sync, add_state_ids, remove_state_ids" + " FROM syncapi_output_room_events" + - " WHERE (id > $1 AND id <= $2)" + // old/new pos - " AND (add_state_ids IS NOT NULL OR remove_state_ids IS NOT NULL)" + - /* " AND ( $3 IS NULL OR sender IN ($3) )" + // sender - " AND ( $4 IS NULL OR NOT(sender IN ($4)) )" + // not sender - " AND ( $5 IS NULL OR type IN ($5) )" + // type - " AND ( $6 IS NULL OR NOT(type IN ($6)) )" + // not type - " AND ( $7 IS NULL OR contains_url = $7)" + // contains URL? */ - " ORDER BY id ASC" + - " LIMIT $8" // limit + " WHERE (id > $1 AND id <= $2)" + + " AND ((add_state_ids IS NOT NULL AND add_state_ids != '') OR (remove_state_ids IS NOT NULL AND remove_state_ids != ''))" + // WHEN, ORDER BY and LIMIT are appended by prepareWithFilters const deleteEventsForRoomSQL = "" + "DELETE FROM syncapi_output_room_events WHERE room_id = $1" type outputRoomEventsStatements struct { - db *sql.DB - streamIDStatements *streamIDStatements - insertEventStmt *sql.Stmt - selectEventsStmt *sql.Stmt - selectMaxEventIDStmt *sql.Stmt - selectRecentEventsStmt *sql.Stmt - selectRecentEventsForSyncStmt *sql.Stmt - selectEarlyEventsStmt *sql.Stmt - selectStateInRangeStmt *sql.Stmt - updateEventJSONStmt *sql.Stmt - deleteEventsForRoomStmt *sql.Stmt + db *sql.DB + streamIDStatements *streamIDStatements + insertEventStmt *sql.Stmt + selectEventsStmt *sql.Stmt + selectMaxEventIDStmt *sql.Stmt + updateEventJSONStmt *sql.Stmt + deleteEventsForRoomStmt *sql.Stmt } func NewSqliteEventsTable(db *sql.DB, streamID *streamIDStatements) (tables.Events, error) { @@ -138,18 +118,6 @@ func NewSqliteEventsTable(db *sql.DB, streamID *streamIDStatements) (tables.Even if s.selectMaxEventIDStmt, err = db.Prepare(selectMaxEventIDSQL); err != nil { return nil, err } - if s.selectRecentEventsStmt, err = db.Prepare(selectRecentEventsSQL); err != nil { - return nil, err - } - if s.selectRecentEventsForSyncStmt, err = db.Prepare(selectRecentEventsForSyncSQL); err != nil { - return nil, err - } - if s.selectEarlyEventsStmt, err = db.Prepare(selectEarlyEventsSQL); err != nil { - return nil, err - } - if s.selectStateInRangeStmt, err = db.Prepare(selectStateInRangeSQL); err != nil { - return nil, err - } if s.updateEventJSONStmt, err = db.Prepare(updateEventJSONSQL); err != nil { return nil, err } @@ -173,19 +141,22 @@ func (s *outputRoomEventsStatements) UpdateEventJSON(ctx context.Context, event // two positions, only the most recent state is returned. func (s *outputRoomEventsStatements) SelectStateInRange( ctx context.Context, txn *sql.Tx, r types.Range, - stateFilterPart *gomatrixserverlib.StateFilter, + stateFilter *gomatrixserverlib.StateFilter, ) (map[string]map[string]bool, map[string]types.StreamEvent, error) { - stmt := sqlutil.TxStmt(txn, s.selectStateInRangeStmt) - - rows, err := stmt.QueryContext( - ctx, r.Low(), r.High(), - /*pq.StringArray(stateFilterPart.Senders), - pq.StringArray(stateFilterPart.NotSenders), - pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.Types)), - pq.StringArray(filterConvertTypeWildcardToSQL(stateFilterPart.NotTypes)), - stateFilterPart.ContainsURL,*/ - stateFilterPart.Limit, + stmt, params, err := prepareWithFilters( + s.db, txn, selectStateInRangeSQL, + []interface{}{ + r.Low(), r.High(), + }, + stateFilter.Senders, stateFilter.NotSenders, + stateFilter.Types, stateFilter.NotTypes, + stateFilter.Limit, FilterOrderAsc, ) + if err != nil { + return nil, nil, fmt.Errorf("s.prepareWithFilters: %w", err) + } + + rows, err := stmt.QueryContext(ctx, params...) if err != nil { return nil, nil, err } @@ -298,16 +269,21 @@ func (s *outputRoomEventsStatements) InsertEvent( return 0, err } - addStateJSON, err := json.Marshal(addState) - if err != nil { - return 0, err + var addStateJSON, removeStateJSON []byte + if len(addState) > 0 { + addStateJSON, err = json.Marshal(addState) } - removeStateJSON, err := json.Marshal(removeState) if err != nil { - return 0, err + return 0, fmt.Errorf("json.Marshal(addState): %w", err) + } + if len(removeState) > 0 { + removeStateJSON, err = json.Marshal(removeState) + } + if err != nil { + return 0, fmt.Errorf("json.Marshal(removeState): %w", err) } - streamPos, err := s.streamIDStatements.nextStreamID(ctx, txn) + streamPos, err := s.streamIDStatements.nextPDUID(ctx, txn) if err != nil { return 0, err } @@ -333,17 +309,30 @@ func (s *outputRoomEventsStatements) InsertEvent( func (s *outputRoomEventsStatements) SelectRecentEvents( ctx context.Context, txn *sql.Tx, - roomID string, r types.Range, limit int, + roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool, ) ([]types.StreamEvent, bool, error) { - var stmt *sql.Stmt + var query string if onlySyncEvents { - stmt = sqlutil.TxStmt(txn, s.selectRecentEventsForSyncStmt) + query = selectRecentEventsForSyncSQL } else { - stmt = sqlutil.TxStmt(txn, s.selectRecentEventsStmt) + query = selectRecentEventsSQL } - rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit+1) + stmt, params, err := prepareWithFilters( + s.db, txn, query, + []interface{}{ + roomID, r.Low(), r.High(), + }, + eventFilter.Senders, eventFilter.NotSenders, + eventFilter.Types, eventFilter.NotTypes, + eventFilter.Limit+1, FilterOrderDesc, + ) + if err != nil { + return nil, false, fmt.Errorf("s.prepareWithFilters: %w", err) + } + + rows, err := stmt.QueryContext(ctx, params...) if err != nil { return nil, false, err } @@ -362,7 +351,7 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( } // we queried for 1 more than the limit, so if we returned one more mark limited=true limited := false - if len(events) > limit { + if len(events) > eventFilter.Limit { limited = true // re-slice the extra (oldest) event out: in chronological order this is the first entry, else the last. if chronologicalOrder { @@ -376,10 +365,21 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( func (s *outputRoomEventsStatements) SelectEarlyEvents( ctx context.Context, txn *sql.Tx, - roomID string, r types.Range, limit int, + roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, ) ([]types.StreamEvent, error) { - stmt := sqlutil.TxStmt(txn, s.selectEarlyEventsStmt) - rows, err := stmt.QueryContext(ctx, roomID, r.Low(), r.High(), limit) + stmt, params, err := prepareWithFilters( + s.db, txn, selectEarlyEventsSQL, + []interface{}{ + roomID, r.Low(), r.High(), + }, + eventFilter.Senders, eventFilter.NotSenders, + eventFilter.Types, eventFilter.NotTypes, + eventFilter.Limit, FilterOrderAsc, + ) + if err != nil { + return nil, fmt.Errorf("s.prepareWithFilters: %w", err) + } + rows, err := stmt.QueryContext(ctx, params...) if err != nil { return nil, err } diff --git a/syncapi/storage/sqlite3/peeks_table.go b/syncapi/storage/sqlite3/peeks_table.go index d755e28c2..c93c82051 100644 --- a/syncapi/storage/sqlite3/peeks_table.go +++ b/syncapi/storage/sqlite3/peeks_table.go @@ -108,7 +108,7 @@ func NewSqlitePeeksTable(db *sql.DB, streamID *streamIDStatements) (tables.Peeks 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) + streamPos, err = s.streamIDStatements.nextPDUID(ctx, txn) if err != nil { return } @@ -120,7 +120,7 @@ func (s *peekStatements) InsertPeek( 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) + streamPos, err = s.streamIDStatements.nextPDUID(ctx, txn) if err != nil { return } @@ -131,7 +131,7 @@ func (s *peekStatements) DeletePeek( func (s *peekStatements) DeletePeeks( ctx context.Context, txn *sql.Tx, roomID, userID string, ) (types.StreamPosition, error) { - streamPos, err := s.streamIDStatements.nextStreamID(ctx, txn) + streamPos, err := s.streamIDStatements.nextPDUID(ctx, txn) if err != nil { return 0, err } diff --git a/syncapi/storage/sqlite3/stream_id_table.go b/syncapi/storage/sqlite3/stream_id_table.go index f73be422d..b614271da 100644 --- a/syncapi/storage/sqlite3/stream_id_table.go +++ b/syncapi/storage/sqlite3/stream_id_table.go @@ -20,6 +20,10 @@ INSERT INTO syncapi_stream_id (stream_name, stream_id) VALUES ("global", 0) ON CONFLICT DO NOTHING; INSERT INTO syncapi_stream_id (stream_name, stream_id) VALUES ("receipt", 0) ON CONFLICT DO NOTHING; +INSERT INTO syncapi_stream_id (stream_name, stream_id) VALUES ("accountdata", 0) + ON CONFLICT DO NOTHING; +INSERT INTO syncapi_stream_id (stream_name, stream_id) VALUES ("invite", 0) + ON CONFLICT DO NOTHING; ` const increaseStreamIDStmt = "" + @@ -49,7 +53,7 @@ func (s *streamIDStatements) prepare(db *sql.DB) (err error) { return } -func (s *streamIDStatements) nextStreamID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) { +func (s *streamIDStatements) nextPDUID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) { increaseStmt := sqlutil.TxStmt(txn, s.increaseStreamIDStmt) selectStmt := sqlutil.TxStmt(txn, s.selectStreamIDStmt) if _, err = increaseStmt.ExecContext(ctx, "global"); err != nil { @@ -68,3 +72,23 @@ func (s *streamIDStatements) nextReceiptID(ctx context.Context, txn *sql.Tx) (po err = selectStmt.QueryRowContext(ctx, "receipt").Scan(&pos) return } + +func (s *streamIDStatements) nextInviteID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) { + increaseStmt := sqlutil.TxStmt(txn, s.increaseStreamIDStmt) + selectStmt := sqlutil.TxStmt(txn, s.selectStreamIDStmt) + if _, err = increaseStmt.ExecContext(ctx, "invite"); err != nil { + return + } + err = selectStmt.QueryRowContext(ctx, "invite").Scan(&pos) + return +} + +func (s *streamIDStatements) nextAccountDataID(ctx context.Context, txn *sql.Tx) (pos types.StreamPosition, err error) { + increaseStmt := sqlutil.TxStmt(txn, s.increaseStreamIDStmt) + selectStmt := sqlutil.TxStmt(txn, s.selectStreamIDStmt) + if _, err = increaseStmt.ExecContext(ctx, "accountdata"); err != nil { + return + } + err = selectStmt.QueryRowContext(ctx, "accountdata").Scan(&pos) + return +} diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index fca888249..739676770 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -56,9 +56,9 @@ type Events interface { // SelectRecentEvents returns events between the two stream positions: exclusive of low and inclusive of high. // If onlySyncEvents has a value of true, only returns the events that aren't marked as to exclude from sync. // Returns up to `limit` events. Returns `limited=true` if there are more events in this range but we hit the `limit`. - SelectRecentEvents(ctx context.Context, txn *sql.Tx, roomID string, r types.Range, limit int, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) + SelectRecentEvents(ctx context.Context, txn *sql.Tx, roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter, chronologicalOrder bool, onlySyncEvents bool) ([]types.StreamEvent, bool, error) // SelectEarlyEvents returns the earliest events in the given room. - SelectEarlyEvents(ctx context.Context, txn *sql.Tx, roomID string, r types.Range, limit int) ([]types.StreamEvent, error) + SelectEarlyEvents(ctx context.Context, txn *sql.Tx, roomID string, r types.Range, eventFilter *gomatrixserverlib.RoomEventFilter) ([]types.StreamEvent, error) SelectEvents(ctx context.Context, txn *sql.Tx, eventIDs []string) ([]types.StreamEvent, error) UpdateEventJSON(ctx context.Context, event *gomatrixserverlib.HeaderedEvent) error // DeleteEventsForRoom removes all event information for a room. This should only be done when removing the room entirely. diff --git a/syncapi/streams/stream_pdu.go b/syncapi/streams/stream_pdu.go index 483be575e..d6d7ff444 100644 --- a/syncapi/streams/stream_pdu.go +++ b/syncapi/streams/stream_pdu.go @@ -48,13 +48,14 @@ func (p *PDUStreamProvider) CompleteSync( return from } - stateFilter := gomatrixserverlib.DefaultStateFilter() // TODO: use filter provided in request + stateFilter := req.Filter.Room.State + eventFilter := req.Filter.Room.Timeline // Build up a /sync response. Add joined rooms. for _, roomID := range joinedRoomIDs { var jr *types.JoinResponse jr, err = p.getJoinResponseForCompleteSync( - ctx, roomID, r, &stateFilter, req.Limit, req.Device, + ctx, roomID, r, &stateFilter, &eventFilter, req.Device, ) if err != nil { req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") @@ -74,7 +75,7 @@ func (p *PDUStreamProvider) CompleteSync( if !peek.Deleted { var jr *types.JoinResponse jr, err = p.getJoinResponseForCompleteSync( - ctx, peek.RoomID, r, &stateFilter, req.Limit, req.Device, + ctx, peek.RoomID, r, &stateFilter, &eventFilter, req.Device, ) if err != nil { req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") @@ -104,8 +105,8 @@ func (p *PDUStreamProvider) IncrementalSync( var stateDeltas []types.StateDelta var joinedRooms []string - // TODO: use filter provided in request - stateFilter := gomatrixserverlib.DefaultStateFilter() + stateFilter := req.Filter.Room.State + eventFilter := req.Filter.Room.Timeline if req.WantFullState { if stateDeltas, joinedRooms, err = p.DB.GetStateDeltasForFullStateSync(ctx, req.Device, r, req.Device.UserID, &stateFilter); err != nil { @@ -124,7 +125,7 @@ func (p *PDUStreamProvider) IncrementalSync( } for _, delta := range stateDeltas { - if err = p.addRoomDeltaToResponse(ctx, req.Device, r, delta, req.Limit, req.Response); err != nil { + if err = p.addRoomDeltaToResponse(ctx, req.Device, r, delta, &eventFilter, req.Response); err != nil { req.Log.WithError(err).Error("d.addRoomDeltaToResponse failed") return newPos } @@ -138,7 +139,7 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse( device *userapi.Device, r types.Range, delta types.StateDelta, - numRecentEventsPerRoom int, + eventFilter *gomatrixserverlib.RoomEventFilter, res *types.Response, ) error { if delta.MembershipPos > 0 && delta.Membership == gomatrixserverlib.Leave { @@ -152,7 +153,7 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse( } recentStreamEvents, limited, err := p.DB.RecentEvents( ctx, delta.RoomID, r, - numRecentEventsPerRoom, true, true, + eventFilter, true, true, ) if err != nil { return err @@ -209,7 +210,8 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync( roomID string, r types.Range, stateFilter *gomatrixserverlib.StateFilter, - numRecentEventsPerRoom int, device *userapi.Device, + eventFilter *gomatrixserverlib.RoomEventFilter, + device *userapi.Device, ) (jr *types.JoinResponse, err error) { var stateEvents []*gomatrixserverlib.HeaderedEvent stateEvents, err = p.DB.CurrentState(ctx, roomID, stateFilter) @@ -221,7 +223,7 @@ func (p *PDUStreamProvider) getJoinResponseForCompleteSync( var recentStreamEvents []types.StreamEvent var limited bool recentStreamEvents, limited, err = p.DB.RecentEvents( - ctx, roomID, r, numRecentEventsPerRoom, true, true, + ctx, roomID, r, eventFilter, true, true, ) if err != nil { return diff --git a/syncapi/sync/request.go b/syncapi/sync/request.go index 5f89ffc33..09a62e3dd 100644 --- a/syncapi/sync/request.go +++ b/syncapi/sync/request.go @@ -16,6 +16,7 @@ package sync import ( "encoding/json" + "fmt" "net/http" "strconv" "time" @@ -31,14 +32,6 @@ import ( const defaultSyncTimeout = time.Duration(0) const DefaultTimelineLimit = 20 -type filter struct { - Room struct { - Timeline struct { - Limit *int `json:"limit"` - } `json:"timeline"` - } `json:"room"` -} - func newSyncRequest(req *http.Request, device userapi.Device, syncDB storage.Database) (*types.SyncRequest, error) { timeout := getTimeout(req.URL.Query().Get("timeout")) fullState := req.URL.Query().Get("full_state") @@ -51,41 +44,37 @@ func newSyncRequest(req *http.Request, device userapi.Device, syncDB storage.Dat return nil, err } } - timelineLimit := DefaultTimelineLimit // TODO: read from stored filters too + filter := gomatrixserverlib.DefaultFilter() filterQuery := req.URL.Query().Get("filter") if filterQuery != "" { if filterQuery[0] == '{' { - // attempt to parse the timeline limit at least - var f filter - err := json.Unmarshal([]byte(filterQuery), &f) - if err == nil && f.Room.Timeline.Limit != nil { - timelineLimit = *f.Room.Timeline.Limit + // Parse the filter from the query string + if err := json.Unmarshal([]byte(filterQuery), &filter); err != nil { + return nil, fmt.Errorf("json.Unmarshal: %w", err) } } else { - // attempt to load the filter ID + // Try to load the filter from the database localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) if err != nil { util.GetLogger(req.Context()).WithError(err).Error("gomatrixserverlib.SplitID failed") - return nil, err + return nil, fmt.Errorf("gomatrixserverlib.SplitID: %w", err) } - f, err := syncDB.GetFilter(req.Context(), localpart, filterQuery) - if err == nil { - timelineLimit = f.Room.Timeline.Limit + if f, err := syncDB.GetFilter(req.Context(), localpart, filterQuery); err != nil { + util.GetLogger(req.Context()).WithError(err).Error("syncDB.GetFilter failed") + return nil, fmt.Errorf("syncDB.GetFilter: %w", err) + } else { + filter = *f } } } - filter := gomatrixserverlib.DefaultEventFilter() - filter.Limit = timelineLimit - // TODO: Additional query params: set_presence, filter - logger := util.GetLogger(req.Context()).WithFields(logrus.Fields{ "user_id": device.UserID, "device_id": device.ID, "since": since, "timeout": timeout, - "limit": timelineLimit, + "limit": filter.Room.Timeline.Limit, }) return &types.SyncRequest{ @@ -96,7 +85,6 @@ func newSyncRequest(req *http.Request, device userapi.Device, syncDB storage.Dat Filter: filter, // Since: since, // Timeout: timeout, // - Limit: timelineLimit, // Rooms: make(map[string]string), // Populated by the PDU stream WantFullState: wantFullState, // }, nil diff --git a/syncapi/types/provider.go b/syncapi/types/provider.go index 24b453a80..93ed12661 100644 --- a/syncapi/types/provider.go +++ b/syncapi/types/provider.go @@ -14,9 +14,8 @@ type SyncRequest struct { Log *logrus.Entry Device *userapi.Device Response *Response - Filter gomatrixserverlib.EventFilter + Filter gomatrixserverlib.Filter Since StreamingToken - Limit int Timeout time.Duration WantFullState bool diff --git a/sytest-whitelist b/sytest-whitelist index cb84913b8..b2dcbf5e4 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -503,3 +503,8 @@ A next_batch token can be used in the v1 messages API Users receive device_list updates for their own devices m.room.history_visibility == "world_readable" allows/forbids appropriately for Guest users m.room.history_visibility == "world_readable" allows/forbids appropriately for Real users +State is included in the timeline in the initial sync +State from remote users is included in the state in the initial sync +Changes to state are included in an gapped incremental sync +A full_state incremental update returns all state +Can pass a JSON filter as a query parameter From c08e38df2c5b00521e15c15a76d5234860b34bd4 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Wed, 20 Jan 2021 17:03:35 +0000 Subject: [PATCH 04/33] MSC2946: Treat federation responses the same way as local responses (#1724) * Start treating fed rooms/events the same as local rooms/events * Share more code --- setup/mscs/msc2946/msc2946.go | 171 ++++++++++++++-------------------- 1 file changed, 70 insertions(+), 101 deletions(-) diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index c3a68632b..3580d4d2e 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -239,33 +239,23 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse { // Mark this room as processed. processed[roomID] = true - // Is the caller currently joined to the room or is the room `world_readable` - // If no, skip this room. If yes, continue. - if !w.roomExists(roomID) || !w.authorised(roomID) { - // attempt to query this room over federation, as either we've never heard of it before - // or we've left it and hence are not authorised (but info may be exposed regardless) - fedRes, err := w.federatedRoomInfo(roomID) + // Collect rooms/events to send back (either locally or fetched via federation) + var discoveredRooms []gomatrixserverlib.MSC2946Room + var discoveredEvents []gomatrixserverlib.MSC2946StrippedEvent + + // If we know about this room and the caller is authorised (joined/world_readable) then pull + // events locally + if w.roomExists(roomID) && w.authorised(roomID) { + // Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get + // all `m.space.child` and `m.space.parent` state events which *point to* (via `state_key` or `content.room_id`) + // this room. This requires servers to store reverse lookups. + events, err := w.references(roomID) if err != nil { - util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces") + util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room") continue } - if fedRes != nil { - res = combineResponses(res, *fedRes) - } - continue - } - // Get all `m.space.child` and `m.space.parent` state events for the room. *In addition*, get - // all `m.space.child` and `m.space.parent` state events which *point to* (via `state_key` or `content.room_id`) - // this room. This requires servers to store reverse lookups. - refs, err := w.references(roomID) - if err != nil { - util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Error("failed to extract references for room") - continue - } + discoveredEvents = events - // If this room has not ever been in `rooms` (across multiple requests), extract the - // `PublicRoomsChunk` for this room. - if !w.alreadySent(roomID) && !w.roomIsExcluded(roomID) { pubRoom := w.publicRoomsChunk(roomID) roomType := "" create := w.stateEvent(roomID, gomatrixserverlib.MRoomCreate, "") @@ -275,12 +265,31 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse { } // Add the total number of events to `PublicRoomsChunk` under `num_refs`. Add `PublicRoomsChunk` to `rooms`. - res.Rooms = append(res.Rooms, gomatrixserverlib.MSC2946Room{ + discoveredRooms = append(discoveredRooms, gomatrixserverlib.MSC2946Room{ PublicRoom: *pubRoom, - NumRefs: refs.len(), + NumRefs: len(discoveredEvents), RoomType: roomType, }) - w.markSent(roomID) + } else { + // attempt to query this room over federation, as either we've never heard of it before + // or we've left it and hence are not authorised (but info may be exposed regardless) + fedRes, err := w.federatedRoomInfo(roomID) + if err != nil { + util.GetLogger(w.ctx).WithError(err).WithField("room_id", roomID).Errorf("failed to query federated spaces") + continue + } + if fedRes != nil { + discoveredRooms = fedRes.Rooms + discoveredEvents = fedRes.Events + } + } + + // If this room has not ever been in `rooms` (across multiple requests), send it now + for _, room := range discoveredRooms { + if !w.alreadySent(room.RoomID) && !w.roomIsExcluded(room.RoomID) { + res.Rooms = append(res.Rooms, room) + w.markSent(room.RoomID) + } } uniqueRooms := make(set) @@ -288,45 +297,37 @@ func (w *walker) walk() *gomatrixserverlib.MSC2946SpacesResponse { // If this is the root room from the original request, insert all these events into `events` if // they haven't been added before (across multiple requests). if w.rootRoomID == roomID { - for _, ev := range refs.events() { - if !w.alreadySent(ev.EventID()) { - strip := stripped(ev.Event) - if strip == nil { - continue - } - res.Events = append(res.Events, *strip) - uniqueRooms[ev.RoomID()] = true - uniqueRooms[SpaceTarget(ev)] = true - w.markSent(ev.EventID()) + for _, ev := range discoveredEvents { + if !w.alreadySent(eventKey(&ev)) { + res.Events = append(res.Events, ev) + uniqueRooms[ev.RoomID] = true + uniqueRooms[spaceTargetStripped(&ev)] = true + w.markSent(eventKey(&ev)) } } } else { // Else add them to `events` honouring the `limit` and `max_rooms_per_space` values. If either // are exceeded, stop adding events. If the event has already been added, do not add it again. numAdded := 0 - for _, ev := range refs.events() { + for _, ev := range discoveredEvents { if w.req.Limit > 0 && len(res.Events) >= w.req.Limit { break } if w.req.MaxRoomsPerSpace > 0 && numAdded >= w.req.MaxRoomsPerSpace { break } - if w.alreadySent(ev.EventID()) { + if w.alreadySent(eventKey(&ev)) { continue } // Skip the room if it's part of exclude_rooms but ONLY IF the source matches, as we still // want to catch arrows which point to excluded rooms. - if w.roomIsExcluded(ev.RoomID()) { + if w.roomIsExcluded(ev.RoomID) { continue } - strip := stripped(ev.Event) - if strip == nil { - continue - } - res.Events = append(res.Events, *strip) - uniqueRooms[ev.RoomID()] = true - uniqueRooms[SpaceTarget(ev)] = true - w.markSent(ev.EventID()) + res.Events = append(res.Events, ev) + uniqueRooms[ev.RoomID] = true + uniqueRooms[spaceTargetStripped(&ev)] = true + w.markSent(eventKey(&ev)) // we don't distinguish between child state events and parent state events for the purposes of // max_rooms_per_space, maybe we should? numAdded++ @@ -521,51 +522,27 @@ func (w *walker) authorisedUser(roomID string) bool { } // references returns all references pointing to or from this room. -func (w *walker) references(roomID string) (eventLookup, error) { +func (w *walker) references(roomID string) ([]gomatrixserverlib.MSC2946StrippedEvent, error) { events, err := w.db.References(w.ctx, roomID) if err != nil { return nil, err } - el := make(eventLookup) + el := make([]gomatrixserverlib.MSC2946StrippedEvent, 0, len(events)) for _, ev := range events { // only return events that have a `via` key as per MSC1772 // else we'll incorrectly walk redacted events (as the link // is in the state_key) if gjson.GetBytes(ev.Content(), "via").Exists() { - el.set(ev) + strip := stripped(ev.Event) + if strip == nil { + continue + } + el = append(el, *strip) } } return el, nil } -// state event lookup across multiple rooms keyed on event type -// NOT THREAD SAFE -type eventLookup map[string][]*gomatrixserverlib.HeaderedEvent - -func (el eventLookup) set(ev *gomatrixserverlib.HeaderedEvent) { - evs := el[ev.Type()] - if evs == nil { - evs = make([]*gomatrixserverlib.HeaderedEvent, 0) - } - evs = append(evs, ev) - el[ev.Type()] = evs -} - -func (el eventLookup) len() int { - sum := 0 - for _, evs := range el { - sum += len(evs) - } - return sum -} - -func (el eventLookup) events() (events []*gomatrixserverlib.HeaderedEvent) { - for _, evs := range el { - events = append(events, evs...) - } - return -} - type set map[string]bool func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEvent { @@ -581,27 +558,19 @@ func stripped(ev *gomatrixserverlib.Event) *gomatrixserverlib.MSC2946StrippedEve } } -func combineResponses(local, remote gomatrixserverlib.MSC2946SpacesResponse) gomatrixserverlib.MSC2946SpacesResponse { - knownRooms := make(set) - for _, room := range local.Rooms { - knownRooms[room.RoomID] = true - } - knownEvents := make(set) - for _, event := range local.Events { - knownEvents[event.RoomID+event.Type+event.StateKey] = true - } - // mux in remote entries if and only if they aren't present already - for _, room := range remote.Rooms { - if knownRooms[room.RoomID] { - continue - } - local.Rooms = append(local.Rooms, room) - } - for _, event := range remote.Events { - if knownEvents[event.RoomID+event.Type+event.StateKey] { - continue - } - local.Events = append(local.Events, event) - } - return local +func eventKey(event *gomatrixserverlib.MSC2946StrippedEvent) string { + return event.RoomID + "|" + event.Type + "|" + event.StateKey +} + +func spaceTargetStripped(event *gomatrixserverlib.MSC2946StrippedEvent) string { + if event.StateKey == "" { + return "" // no-op + } + switch event.Type { + case ConstSpaceParentEventType: + return event.StateKey + case ConstSpaceChildEventType: + return event.StateKey + } + return "" } From 8fe51019ad603d44fa30d092fa662be6c668872d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 20 Jan 2021 20:43:20 +0000 Subject: [PATCH 05/33] Add sync API memberships table (#1726) --- syncapi/storage/postgres/memberships_table.go | 111 ++++++++++++++++ .../output_room_events_topology_table.go | 9 +- syncapi/storage/postgres/syncserver.go | 5 + syncapi/storage/shared/syncserver.go | 11 +- syncapi/storage/sqlite3/memberships_table.go | 119 ++++++++++++++++++ .../output_room_events_topology_table.go | 7 +- syncapi/storage/sqlite3/syncserver.go | 5 + syncapi/storage/tables/interface.go | 7 +- 8 files changed, 262 insertions(+), 12 deletions(-) create mode 100644 syncapi/storage/postgres/memberships_table.go create mode 100644 syncapi/storage/sqlite3/memberships_table.go diff --git a/syncapi/storage/postgres/memberships_table.go b/syncapi/storage/postgres/memberships_table.go new file mode 100644 index 000000000..6566544d6 --- /dev/null +++ b/syncapi/storage/postgres/memberships_table.go @@ -0,0 +1,111 @@ +// Copyright 2021 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" + "fmt" + + "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" +) + +// The memberships table is designed to track the last time that +// the user was a given state. This allows us to find out the +// most recent time that a user was invited to, joined or left +// a room, either by choice or otherwise. This is important for +// building history visibility. + +const membershipsSchema = ` +CREATE TABLE IF NOT EXISTS syncapi_memberships ( + -- The 'room_id' key for the state event. + room_id TEXT NOT NULL, + -- The state event ID + user_id TEXT NOT NULL, + -- The status of the membership + membership TEXT NOT NULL, + -- The event ID that last changed the membership + event_id TEXT NOT NULL, + -- The stream position of the change + stream_pos BIGINT NOT NULL, + -- The topological position of the change in the room + topological_pos BIGINT NOT NULL, + -- Unique index + CONSTRAINT syncapi_memberships_unique UNIQUE (room_id, user_id, membership) +); +` + +const upsertMembershipSQL = "" + + "INSERT INTO syncapi_memberships (room_id, user_id, membership, event_id, stream_pos, topological_pos)" + + " VALUES ($1, $2, $3, $4, $5, $6)" + + " ON CONFLICT ON CONSTRAINT syncapi_memberships_unique" + + " DO UPDATE SET event_id = $4, stream_pos = $5, topological_pos = $6" + +const selectMembershipSQL = "" + + "SELECT event_id, stream_pos, topological_pos FROM syncapi_memberships" + + " WHERE room_id = $1 AND user_id = $2 AND membership = ANY($3)" + + " ORDER BY stream_pos DESC" + + " LIMIT 1" + +type membershipsStatements struct { + upsertMembershipStmt *sql.Stmt + selectMembershipStmt *sql.Stmt +} + +func NewPostgresMembershipsTable(db *sql.DB) (tables.Memberships, error) { + s := &membershipsStatements{} + _, err := db.Exec(membershipsSchema) + if err != nil { + return nil, err + } + if s.upsertMembershipStmt, err = db.Prepare(upsertMembershipSQL); err != nil { + return nil, err + } + if s.selectMembershipStmt, err = db.Prepare(selectMembershipSQL); err != nil { + return nil, err + } + return s, nil +} + +func (s *membershipsStatements) UpsertMembership( + ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, + streamPos, topologicalPos types.StreamPosition, +) error { + membership, err := event.Membership() + if err != nil { + return fmt.Errorf("event.Membership: %w", err) + } + _, err = sqlutil.TxStmt(txn, s.upsertMembershipStmt).ExecContext( + ctx, + event.RoomID(), + *event.StateKey(), + membership, + event.EventID(), + streamPos, + topologicalPos, + ) + return err +} + +func (s *membershipsStatements) SelectMembership( + ctx context.Context, txn *sql.Tx, roomID, userID, memberships []string, +) (eventID string, streamPos, topologyPos types.StreamPosition, err error) { + stmt := sqlutil.TxStmt(txn, s.selectMembershipStmt) + err = stmt.QueryRowContext(ctx, roomID, userID, memberships).Scan(&eventID, &streamPos, &topologyPos) + return +} diff --git a/syncapi/storage/postgres/output_room_events_topology_table.go b/syncapi/storage/postgres/output_room_events_topology_table.go index cbd20a075..57774453c 100644 --- a/syncapi/storage/postgres/output_room_events_topology_table.go +++ b/syncapi/storage/postgres/output_room_events_topology_table.go @@ -44,7 +44,8 @@ CREATE UNIQUE INDEX IF NOT EXISTS syncapi_event_topological_position_idx ON sync const insertEventInTopologySQL = "" + "INSERT INTO syncapi_output_room_events_topology (event_id, topological_position, room_id, stream_position)" + " VALUES ($1, $2, $3, $4)" + - " ON CONFLICT (topological_position, stream_position, room_id) DO UPDATE SET event_id = $1" + " ON CONFLICT (topological_position, stream_position, room_id) DO UPDATE SET event_id = $1" + + " RETURNING topological_position" const selectEventIDsInRangeASCSQL = "" + "SELECT event_id FROM syncapi_output_room_events_topology" + @@ -115,10 +116,10 @@ func NewPostgresTopologyTable(db *sql.DB) (tables.Topology, error) { // on the event's depth. func (s *outputRoomEventsTopologyStatements) InsertEventInTopology( ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, pos types.StreamPosition, -) (err error) { - _, err = s.insertEventInTopologyStmt.ExecContext( +) (topoPos types.StreamPosition, err error) { + err = sqlutil.TxStmt(txn, s.insertEventInTopologyStmt).QueryRowContext( ctx, event.EventID(), event.Depth(), event.RoomID(), pos, - ) + ).Scan(&topoPos) return } diff --git a/syncapi/storage/postgres/syncserver.go b/syncapi/storage/postgres/syncserver.go index 0fbf3c232..a69fda4fe 100644 --- a/syncapi/storage/postgres/syncserver.go +++ b/syncapi/storage/postgres/syncserver.go @@ -87,6 +87,10 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, e if err != nil { return nil, err } + memberships, err := NewPostgresMembershipsTable(d.db) + if err != nil { + return nil, err + } m := sqlutil.NewMigrations() deltas.LoadFixSequences(m) deltas.LoadRemoveSendToDeviceSentColumn(m) @@ -106,6 +110,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions) (*SyncServerDatasource, e Filter: filter, SendToDevice: sendToDevice, Receipts: receipts, + Memberships: memberships, } return &d, nil } diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 9df076935..239f68129 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -48,6 +48,7 @@ type Database struct { SendToDevice tables.SendToDevice Filter tables.Filter Receipts tables.Receipts + Memberships tables.Memberships } func (d *Database) readOnlySnapshot(ctx context.Context) (*sql.Tx, error) { @@ -383,8 +384,8 @@ func (d *Database) WriteEvent( return fmt.Errorf("d.OutputEvents.InsertEvent: %w", err) } pduPosition = pos - - if err = d.Topology.InsertEventInTopology(ctx, txn, ev, pos); err != nil { + var topoPosition types.StreamPosition + if topoPosition, err = d.Topology.InsertEventInTopology(ctx, txn, ev, pos); err != nil { return fmt.Errorf("d.Topology.InsertEventInTopology: %w", err) } @@ -397,7 +398,7 @@ func (d *Database) WriteEvent( return nil } - return d.updateRoomState(ctx, txn, removeStateEventIDs, addStateEvents, pduPosition) + return d.updateRoomState(ctx, txn, removeStateEventIDs, addStateEvents, pduPosition, topoPosition) }) return pduPosition, returnErr @@ -409,6 +410,7 @@ func (d *Database) updateRoomState( removedEventIDs []string, addedEvents []*gomatrixserverlib.HeaderedEvent, pduPosition types.StreamPosition, + topoPosition types.StreamPosition, ) error { // remove first, then add, as we do not ever delete state, but do replace state which is a remove followed by an add. for _, eventID := range removedEventIDs { @@ -429,6 +431,9 @@ func (d *Database) updateRoomState( return fmt.Errorf("event.Membership: %w", err) } membership = &value + if err = d.Memberships.UpsertMembership(ctx, txn, event, pduPosition, topoPosition); err != nil { + return fmt.Errorf("d.Memberships.UpsertMembership: %w", err) + } } if err := d.CurrentRoomState.UpsertRoomState(ctx, txn, event, membership, pduPosition); err != nil { diff --git a/syncapi/storage/sqlite3/memberships_table.go b/syncapi/storage/sqlite3/memberships_table.go new file mode 100644 index 000000000..e5445e815 --- /dev/null +++ b/syncapi/storage/sqlite3/memberships_table.go @@ -0,0 +1,119 @@ +// Copyright 2021 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" + "fmt" + "strings" + + "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" +) + +// The memberships table is designed to track the last time that +// the user was a given state. This allows us to find out the +// most recent time that a user was invited to, joined or left +// a room, either by choice or otherwise. This is important for +// building history visibility. + +const membershipsSchema = ` +CREATE TABLE IF NOT EXISTS syncapi_memberships ( + -- The 'room_id' key for the state event. + room_id TEXT NOT NULL, + -- The state event ID + user_id TEXT NOT NULL, + -- The status of the membership + membership TEXT NOT NULL, + -- The event ID that last changed the membership + event_id TEXT NOT NULL, + -- The stream position of the change + stream_pos BIGINT NOT NULL, + -- The topological position of the change in the room + topological_pos BIGINT NOT NULL, + -- Unique index + UNIQUE (room_id, user_id, membership) +); +` + +const upsertMembershipSQL = "" + + "INSERT INTO syncapi_memberships (room_id, user_id, membership, event_id, stream_pos, topological_pos)" + + " VALUES ($1, $2, $3, $4, $5, $6)" + + " ON CONFLICT (room_id, user_id, membership)" + + " DO UPDATE SET event_id = $4, stream_pos = $5, topological_pos = $6" + +const selectMembershipSQL = "" + + "SELECT event_id, stream_pos, topological_pos FROM syncapi_memberships" + + " WHERE room_id = $1 AND user_id = $2 AND membership IN ($3)" + + " ORDER BY stream_pos DESC" + + " LIMIT 1" + +type membershipsStatements struct { + db *sql.DB + upsertMembershipStmt *sql.Stmt +} + +func NewSqliteMembershipsTable(db *sql.DB) (tables.Memberships, error) { + s := &membershipsStatements{ + db: db, + } + _, err := db.Exec(membershipsSchema) + if err != nil { + return nil, err + } + if s.upsertMembershipStmt, err = db.Prepare(upsertMembershipSQL); err != nil { + return nil, err + } + return s, nil +} + +func (s *membershipsStatements) UpsertMembership( + ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, + streamPos, topologicalPos types.StreamPosition, +) error { + membership, err := event.Membership() + if err != nil { + return fmt.Errorf("event.Membership: %w", err) + } + _, err = sqlutil.TxStmt(txn, s.upsertMembershipStmt).ExecContext( + ctx, + event.RoomID(), + *event.StateKey(), + membership, + event.EventID(), + streamPos, + topologicalPos, + ) + return err +} + +func (s *membershipsStatements) SelectMembership( + ctx context.Context, txn *sql.Tx, roomID, userID, memberships []string, +) (eventID string, streamPos, topologyPos types.StreamPosition, err error) { + params := []interface{}{roomID, userID} + for _, membership := range memberships { + params = append(params, membership) + } + orig := strings.Replace(selectMembershipSQL, "($3)", sqlutil.QueryVariadicOffset(len(memberships), 2), 1) + stmt, err := s.db.Prepare(orig) + if err != nil { + return "", 0, 0, err + } + err = sqlutil.TxStmt(txn, stmt).QueryRowContext(ctx, params...).Scan(&eventID, &streamPos, &topologyPos) + return +} diff --git a/syncapi/storage/sqlite3/output_room_events_topology_table.go b/syncapi/storage/sqlite3/output_room_events_topology_table.go index d3ba9af62..d34b90500 100644 --- a/syncapi/storage/sqlite3/output_room_events_topology_table.go +++ b/syncapi/storage/sqlite3/output_room_events_topology_table.go @@ -111,12 +111,11 @@ func NewSqliteTopologyTable(db *sql.DB) (tables.Topology, error) { // on the event's depth. func (s *outputRoomEventsTopologyStatements) InsertEventInTopology( ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, pos types.StreamPosition, -) (err error) { - stmt := sqlutil.TxStmt(txn, s.insertEventInTopologyStmt) - _, err = stmt.ExecContext( +) (types.StreamPosition, error) { + _, err := sqlutil.TxStmt(txn, s.insertEventInTopologyStmt).ExecContext( ctx, event.EventID(), event.Depth(), event.RoomID(), pos, ) - return + return types.StreamPosition(event.Depth()), err } func (s *outputRoomEventsTopologyStatements) SelectEventIDsInRange( diff --git a/syncapi/storage/sqlite3/syncserver.go b/syncapi/storage/sqlite3/syncserver.go index fdb6ce4f2..b0e43b68f 100644 --- a/syncapi/storage/sqlite3/syncserver.go +++ b/syncapi/storage/sqlite3/syncserver.go @@ -100,6 +100,10 @@ func (d *SyncServerDatasource) prepare(dbProperties *config.DatabaseOptions) (er if err != nil { return err } + memberships, err := NewSqliteMembershipsTable(d.db) + if err != nil { + return err + } m := sqlutil.NewMigrations() deltas.LoadFixSequences(m) deltas.LoadRemoveSendToDeviceSentColumn(m) @@ -119,6 +123,7 @@ func (d *SyncServerDatasource) prepare(dbProperties *config.DatabaseOptions) (er Filter: filter, SendToDevice: sendToDevice, Receipts: receipts, + Memberships: memberships, } return nil } diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 739676770..997486dd4 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -70,7 +70,7 @@ type Events interface { type Topology interface { // InsertEventInTopology inserts the given event in the room's topology, based on the event's depth. // `pos` is the stream position of this event in the events table, and is used to order events which have the same depth. - InsertEventInTopology(ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, pos types.StreamPosition) (err error) + InsertEventInTopology(ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, pos types.StreamPosition) (topoPos types.StreamPosition, err error) // SelectEventIDsInRange selects the IDs of events whose depths are within a given range in a given room's topological order. // Events with `minDepth` are *exclusive*, as is the event which has exactly `minDepth`,`maxStreamPos`. // `maxStreamPos` is only used when events have the same depth as `maxDepth`, which results in events less than `maxStreamPos` being returned. @@ -162,3 +162,8 @@ type Receipts interface { SelectRoomReceiptsAfter(ctx context.Context, roomIDs []string, streamPos types.StreamPosition) (types.StreamPosition, []eduAPI.OutputReceiptEvent, error) SelectMaxReceiptID(ctx context.Context, txn *sql.Tx) (id int64, err error) } + +type Memberships interface { + UpsertMembership(ctx context.Context, txn *sql.Tx, event *gomatrixserverlib.HeaderedEvent, streamPos, topologicalPos types.StreamPosition) error + SelectMembership(ctx context.Context, txn *sql.Tx, roomID, userID, memberships []string) (eventID string, streamPos, topologyPos types.StreamPosition, err error) +} From 58f78e08afdfb55b4f9b9be3cc1cd008afa73804 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 21 Jan 2021 13:57:41 +0000 Subject: [PATCH 06/33] Limit well-known response sizes (matrix-org/gomatrixserverlib#250) --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 58871dfb0..bf12c5ff4 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-20210121135706-63c66d16c96a 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 diff --git a/go.sum b/go.sum index b79337fae..ed24a077d 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-20210121135706-63c66d16c96a h1:cEuy0TSs4F6tYzgsi77cjx3FMBADsU7WWl+USUztD0k= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210121135706-63c66d16c96a/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= From 745ee20b9049dd9bdfc1c7e84cb2a4aa4fd66f4f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Jan 2021 12:56:20 +0000 Subject: [PATCH 07/33] Update sytest-whitelist after matrix-org/sytest#991 --- sytest-whitelist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sytest-whitelist b/sytest-whitelist index b2dcbf5e4..d53fa899d 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -508,3 +508,6 @@ State from remote users is included in the state in the initial sync Changes to state are included in an gapped incremental sync A full_state incremental update returns all state Can pass a JSON filter as a query parameter +Local room members can get room messages +Remote room members can get room messages +Guest users can send messages to guest_access rooms if joined From 805a74892efcd18fdcaa14a68a1ba7dfa9486172 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Jan 2021 14:16:59 +0000 Subject: [PATCH 08/33] DNS caching (#1728) * Allow configuring DNS cache * Update sample configs * Fix build errors * Fix time resolution * Default 5m * In seconds * Use WithDNScache * Correct field name * Update go.mod/go.sum to matrix-org/gomatrixserverlib#251 --- build/docker/config/dendrite-config.yaml | 11 ++++++++++ dendrite-config.yaml | 11 ++++++++++ go.mod | 2 +- go.sum | 4 ++-- setup/base.go | 28 ++++++++++++++++++++++-- setup/config/config_global.go | 25 +++++++++++++++++++++ 6 files changed, 76 insertions(+), 5 deletions(-) diff --git a/build/docker/config/dendrite-config.yaml b/build/docker/config/dendrite-config.yaml index 94dcd992d..ca59ae03b 100644 --- a/build/docker/config/dendrite-config.yaml +++ b/build/docker/config/dendrite-config.yaml @@ -91,6 +91,17 @@ global: username: metrics password: metrics + # DNS cache options. The DNS cache may reduce the load on DNS servers + # if there is no local caching resolver available for use. + dns_cache: + # Whether or not the DNS cache is enabled. + enabled: false + + # Maximum number of entries to hold in the DNS cache, and + # for how long those items should be considered valid in seconds. + cache_size: 256 + cache_lifetime: 300 + # Configuration for the Appservice API. app_service_api: internal_api: diff --git a/dendrite-config.yaml b/dendrite-config.yaml index 978b18008..a3d1065d4 100644 --- a/dendrite-config.yaml +++ b/dendrite-config.yaml @@ -103,6 +103,17 @@ global: username: metrics password: metrics + # DNS cache options. The DNS cache may reduce the load on DNS servers + # if there is no local caching resolver available for use. + dns_cache: + # Whether or not the DNS cache is enabled. + enabled: false + + # Maximum number of entries to hold in the DNS cache, and + # for how long those items should be considered valid in seconds. + cache_size: 256 + cache_lifetime: 300 + # Configuration for the Appservice API. app_service_api: internal_api: diff --git a/go.mod b/go.mod index bf12c5ff4..a0fee1030 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-20210121135706-63c66d16c96a + github.com/matrix-org/gomatrixserverlib v0.0.0-20210122141547-5c2c047bfdcd 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 diff --git a/go.sum b/go.sum index ed24a077d..7accf175f 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +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-20210121135706-63c66d16c96a h1:cEuy0TSs4F6tYzgsi77cjx3FMBADsU7WWl+USUztD0k= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210121135706-63c66d16c96a/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210122141547-5c2c047bfdcd h1:SbWP7aHgACfAQpI2te6LzxK3l0+UqPOb8NOCOxzwV1c= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210122141547-5c2c047bfdcd/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= diff --git a/setup/base.go b/setup/base.go index acbf2d35f..021dd2b32 100644 --- a/setup/base.go +++ b/setup/base.go @@ -73,6 +73,7 @@ type BaseDendrite struct { httpClient *http.Client Cfg *config.Dendrite Caches *caching.Caches + DNSCache *gomatrixserverlib.DNSCache // KafkaConsumer sarama.Consumer // KafkaProducer sarama.SyncProducer } @@ -111,6 +112,20 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo logrus.WithError(err).Warnf("Failed to create cache") } + var dnsCache *gomatrixserverlib.DNSCache + if cfg.Global.DNSCache.Enabled { + lifetime := time.Second * cfg.Global.DNSCache.CacheLifetime + dnsCache = gomatrixserverlib.NewDNSCache( + cfg.Global.DNSCache.CacheSize, + lifetime, + ) + logrus.Infof( + "DNS cache enabled (size %d, lifetime %s)", + cfg.Global.DNSCache.CacheSize, + lifetime, + ) + } + apiClient := http.Client{ Timeout: time.Minute * 10, Transport: &http2.Transport{ @@ -152,6 +167,7 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo tracerCloser: closer, Cfg: cfg, Caches: cache, + DNSCache: dnsCache, PublicClientAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicClientPathPrefix).Subrouter().UseEncodedPath(), PublicFederationAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicFederationPathPrefix).Subrouter().UseEncodedPath(), PublicKeyAPIMux: mux.NewRouter().SkipClean(true).PathPrefix(httputil.PublicKeyPathPrefix).Subrouter().UseEncodedPath(), @@ -252,8 +268,12 @@ func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client { if b.Cfg.Global.DisableFederation { return gomatrixserverlib.NewClientWithTransport(noOpHTTPTransport) } + opts := []interface{}{} + if b.Cfg.Global.DNSCache.Enabled { + opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) + } client := gomatrixserverlib.NewClient( - b.Cfg.FederationSender.DisableTLSValidation, + b.Cfg.FederationSender.DisableTLSValidation, opts..., ) client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString())) return client @@ -268,9 +288,13 @@ func (b *BaseDendrite) CreateFederationClient() *gomatrixserverlib.FederationCli b.Cfg.FederationSender.DisableTLSValidation, noOpHTTPTransport, ) } + opts := []interface{}{} + if b.Cfg.Global.DNSCache.Enabled { + opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) + } client := gomatrixserverlib.NewFederationClientWithTimeout( b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, b.Cfg.Global.PrivateKey, - b.Cfg.FederationSender.DisableTLSValidation, time.Minute*5, + b.Cfg.FederationSender.DisableTLSValidation, time.Minute*5, opts..., ) client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString())) return client diff --git a/setup/config/config_global.go b/setup/config/config_global.go index 956522176..d4b068dbe 100644 --- a/setup/config/config_global.go +++ b/setup/config/config_global.go @@ -48,6 +48,9 @@ type Global struct { // Metrics configuration Metrics Metrics `yaml:"metrics"` + + // DNS caching options for all outbound HTTP requests + DNSCache DNSCacheOptions `yaml:"dns_cache"` } func (c *Global) Defaults() { @@ -59,6 +62,7 @@ func (c *Global) Defaults() { c.Kafka.Defaults() c.Metrics.Defaults() + c.DNSCache.Defaults() } func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { @@ -67,6 +71,7 @@ func (c *Global) Verify(configErrs *ConfigErrors, isMonolith bool) { c.Kafka.Verify(configErrs, isMonolith) c.Metrics.Verify(configErrs, isMonolith) + c.DNSCache.Verify(configErrs, isMonolith) } type OldVerifyKeys struct { @@ -140,3 +145,23 @@ func (c DatabaseOptions) MaxOpenConns() int { func (c DatabaseOptions) ConnMaxLifetime() time.Duration { return time.Duration(c.ConnMaxLifetimeSeconds) * time.Second } + +type DNSCacheOptions struct { + // Whether the DNS cache is enabled or not + Enabled bool `yaml:"enabled"` + // How many entries to store in the DNS cache at a given time + CacheSize int `yaml:"cache_size"` + // How long a cache entry should be considered valid for + CacheLifetime time.Duration `yaml:"cache_lifetime"` +} + +func (c *DNSCacheOptions) Defaults() { + c.Enabled = false + c.CacheSize = 256 + c.CacheLifetime = time.Minute * 5 +} + +func (c *DNSCacheOptions) Verify(configErrs *ConfigErrors, isMonolith bool) { + checkPositive(configErrs, "cache_size", int64(c.CacheSize)) + checkPositive(configErrs, "cache_lifetime", int64(c.CacheLifetime)) +} From 5d8ec0ff1a7a3d40c54e84ba04d0d7d81aa28440 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Jan 2021 14:18:36 +0000 Subject: [PATCH 09/33] Fix build error --- setup/base.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/base.go b/setup/base.go index 021dd2b32..5f0431e84 100644 --- a/setup/base.go +++ b/setup/base.go @@ -268,7 +268,7 @@ func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client { if b.Cfg.Global.DisableFederation { return gomatrixserverlib.NewClientWithTransport(noOpHTTPTransport) } - opts := []interface{}{} + opts := []gomatrixserverlib.ClientOption{} if b.Cfg.Global.DNSCache.Enabled { opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) } @@ -288,7 +288,7 @@ func (b *BaseDendrite) CreateFederationClient() *gomatrixserverlib.FederationCli b.Cfg.FederationSender.DisableTLSValidation, noOpHTTPTransport, ) } - opts := []interface{}{} + opts := []gomatrixserverlib.ClientOption{} if b.Cfg.Global.DNSCache.Enabled { opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) } From 0571d395b51d7788c692b916cd7ba9249a494ed1 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 22 Jan 2021 14:55:08 +0000 Subject: [PATCH 10/33] Peeking over federation via MSC2444 (#1391) * 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. * make PeekingDeviceSet private * add server_name param * blind stab at adding a `peek` section to /sync * make it build * make it launch * add peeking to getResponseWithPDUsForCompleteSync * cancel any peeks when we join a room * spell out how to runoutside of docker if you want speed * fix SQL * remove unnecessary txn for SelectPeeks * fix s/join/peek/ cargocult fail * HACK: Track goroutine IDs to determine when we write by the wrong thread To use: set `DENDRITE_TRACE_SQL=1` then grep for `unsafe` * Track partition offsets and only log unsafe for non-selects * Put redactions in the writer goroutine * Update filters on writer goroutine * wrap peek storage in goid hack * use exclusive writer, and MarkPeeksAsOld more efficiently * don't log ascii in binary at sql trace... * strip out empty roomd deltas * re-add txn to SelectPeeks * re-add accidentally deleted field * reject peeks for non-worldreadable rooms * move perform_peek * fix package * correctly refactor perform_peek * WIP of implementing MSC2444 * typo * Revert "Merge branch 'kegan/HACK-goid-sqlite-db-is-locked' into matthew/peeking" This reverts commit 3cebd8dbfbccdf82b7930b7b6eda92095ca6ef41, reversing changes made to ed4b3a58a7855acc43530693cc855b439edf9c7c. * (almost) make it build * clean up bad merge * support SendEventWithState with optional event * fix build & lint * fix build & lint * reinstate federated peeks in the roomserver (doh) * fix sql thinko * todo for authenticating state returned by /peek * support returning current state from QueryStateAndAuthChain * handle SS /peek * reimplement SS /peek to prod the RS to tell the FS about the peek * rename RemotePeeks as OutboundPeeks * rename remote_peeks_table as outbound_peeks_table * add perform_handle_remote_peek.go * flesh out federation doc * add inbound peeks table and hook it up * rename ambiguous RemotePeek as InboundPeek * rename FSAPI's PerformPeek as PerformOutboundPeek * setup inbound peeks db correctly * fix api.SendEventWithState with no event * track latestevent on /peek * go fmt * document the peek send stream race better * fix SendEventWithRewrite not to bail if handed a non-state event * add fixme * switch SS /peek to use SendEventWithRewrite * fix comment * use reverse topo ordering to find latest extrem * support postgres for federated peeking * go fmt * back out bogus go.mod change * Fix performOutboundPeekUsingServer * Fix getAuthChain -> GetAuthChain * Fix build issues * Fix build again * Fix getAuthChain -> GetAuthChain * Don't repeat outbound peeks for the same room ID to the same servers * Fix lint * Don't omitempty to appease sytest Co-authored-by: Kegan Dougal Co-authored-by: Neil Alexander --- docs/peeking.md | 21 ++- federationapi/routing/peek.go | 102 ++++++++++ federationapi/routing/routing.go | 38 +++- federationsender/api/api.go | 16 ++ federationsender/consumers/roomserver.go | 40 +++- federationsender/internal/perform.go | 168 ++++++++++++++++- federationsender/internal/perform/join.go | 15 +- federationsender/inthttp/client.go | 14 ++ federationsender/storage/interface.go | 11 ++ .../storage/postgres/inbound_peeks_table.go | 176 ++++++++++++++++++ .../storage/postgres/outbound_peeks_table.go | 176 ++++++++++++++++++ federationsender/storage/postgres/storage.go | 28 ++- federationsender/storage/shared/storage.go | 60 +++++- .../storage/sqlite3/inbound_peeks_table.go | 176 ++++++++++++++++++ .../storage/sqlite3/outbound_peeks_table.go | 176 ++++++++++++++++++ federationsender/storage/sqlite3/storage.go | 28 ++- federationsender/storage/tables/interface.go | 18 ++ federationsender/types/types.go | 20 ++ go.mod | 2 +- go.sum | 4 +- roomserver/api/api.go | 6 + roomserver/api/api_trace.go | 10 + roomserver/api/output.go | 17 ++ roomserver/api/perform.go | 22 +++ roomserver/api/query.go | 2 +- roomserver/api/wrapper.go | 2 +- roomserver/internal/api.go | 5 + .../internal/perform/perform_inbound_peek.go | 129 +++++++++++++ roomserver/internal/perform/perform_peek.go | 23 ++- roomserver/internal/query/query.go | 14 +- roomserver/internal/query/query_test.go | 4 +- roomserver/inthttp/client.go | 29 ++- roomserver/inthttp/server.go | 13 ++ syncapi/types/types.go | 2 +- 34 files changed, 1501 insertions(+), 66 deletions(-) create mode 100644 federationapi/routing/peek.go create mode 100644 federationsender/storage/postgres/inbound_peeks_table.go create mode 100644 federationsender/storage/postgres/outbound_peeks_table.go create mode 100644 federationsender/storage/sqlite3/inbound_peeks_table.go create mode 100644 federationsender/storage/sqlite3/outbound_peeks_table.go create mode 100644 roomserver/internal/perform/perform_inbound_peek.go diff --git a/docs/peeking.md b/docs/peeking.md index 78bd6f797..60f359072 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 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. + +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 diff --git a/federationapi/routing/peek.go b/federationapi/routing/peek.go new file mode 100644 index 000000000..8f83cb157 --- /dev/null +++ b/federationapi/routing/peek.go @@ -0,0 +1,102 @@ +// 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/jsonerror" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +// Peek implements the SS /peek API, handling inbound peeks +func Peek( + httpReq *http.Request, + request *gomatrixserverlib.FederationRequest, + cfg *config.FederationAPI, + rsAPI api.RoomserverInternalAPI, + roomID, peekID string, + remoteVersions []gomatrixserverlib.RoomVersion, +) util.JSONResponse { + // TODO: check if we're just refreshing an existing peek by querying the federationsender + + 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 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 + for _, v := range remoteVersions { + if v == verRes.RoomVersion { + remoteSupportsVersion = true + break + } + } + // If it isn't, stop trying to peek the room. + if !remoteSupportsVersion { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.IncompatibleRoomVersion(verRes.RoomVersion), + } + } + + // TODO: Check history visibility + + // tell the peeking server to renew every hour + renewalInterval := int64(60 * 60 * 1000 * 1000) + + var response api.PerformInboundPeekResponse + err := rsAPI.PerformInboundPeek( + httpReq.Context(), + &api.PerformInboundPeekRequest{ + RoomID: roomID, + PeekID: peekID, + ServerName: request.Origin(), + RenewalInterval: renewalInterval, + }, + &response, + ) + if err != nil { + resErr := util.ErrorResponse(err) + return resErr + } + + if !response.RoomExists { + 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: respPeek, + } +} diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index c957e26d0..7e76a143b 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -229,7 +229,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()) { @@ -239,11 +269,11 @@ func Setup( } } roomID := vars["roomID"] - eventID := vars["eventID"] + userID := vars["userID"] 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)) @@ -255,7 +285,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) diff --git a/federationsender/api/api.go b/federationsender/api/api.go index dfc2dd8a3..a9ebedafa 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -62,6 +62,12 @@ type FederationSenderInternalAPI interface { request *PerformJoinRequest, response *PerformJoinResponse, ) + // Handle an instruction to peek a room on a remote server. + PerformOutboundPeek( + ctx context.Context, + request *PerformOutboundPeekRequest, + response *PerformOutboundPeekResponse, + ) error // Handle an instruction to make_leave & send_leave with a remote server. PerformLeave( ctx context.Context, @@ -111,6 +117,16 @@ type PerformJoinResponse struct { LastError *gomatrix.HTTPError } +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 PerformOutboundPeekResponse struct { + LastError *gomatrix.HTTPError +} + type PerformLeaveRequest struct { RoomID string `json:"room_id"` UserID string `json:"user_id"` diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 513919c6f..846468fa6 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -111,6 +111,14 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { } return nil } + case api.OutputTypeNewInboundPeek: + if err := s.processInboundPeek(*output.NewInboundPeek); err != nil { + log.WithFields(log.Fields{ + "event": output.NewInboundPeek, + 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", @@ -121,6 +129,23 @@ func (s *OutputRoomEventConsumer) onMessage(msg *sarama.ConsumerMessage) error { return nil } +// 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: 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. + // + // This is making the tests flakey. + + 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 // and then sends the event to the hosts that were joined before the event. func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) error { @@ -164,6 +189,10 @@ func (s *OutputRoomEventConsumer) processMessage(ore api.OutputNewRoomEvent) err return err } + // 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, @@ -171,7 +200,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 @@ -222,6 +251,15 @@ func (s *OutputRoomEventConsumer) joinedHostsAtEvent( joined[joinedHost.ServerName] = true } + // handle peeking hosts + inboundPeeks, err := s.db.GetInboundPeeks(context.TODO(), ore.Event.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/internal/perform.go b/federationsender/internal/perform.go index 45f33ff70..3adf8fc94 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -234,7 +234,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 { logrus.WithFields(logrus.Fields{ @@ -266,6 +266,172 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( return nil } +// PerformOutboundPeekRequest implements api.FederationSenderInternalAPI +func (r *FederationSenderInternalAPI) PerformOutboundPeek( + ctx context.Context, + request *api.PerformOutboundPeekRequest, + response *api.PerformOutboundPeekResponse, +) 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 + + // 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 + for _, serverName := range request.ServerNames { + if err := r.performOutboundPeekUsingServer( + 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 nil + } + + // 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, + ) + + return lastErr +} + +func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( + ctx context.Context, + roomID string, + 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. + outboundPeek, err := r.db.GetOutboundPeek(ctx, serverName, roomID, peekID) + if err != nil { + return err + } + renewing := false + if outboundPeek != nil { + nowMilli := time.Now().UnixNano() / int64(time.Millisecond) + 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 outbound peek to %s for %s already exists", serverName, roomID) + return nil + } + } + + // Try to perform an outbound /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.Peek: %w", err) + } + r.statistics.ForServer(serverName).Success() + + // Work out if we support the room version that has been supplied in + // the peek response. + if respPeek.RoomVersion == "" { + respPeek.RoomVersion = gomatrixserverlib.RoomVersionV1 + } + if _, err = respPeek.RoomVersion.EventFormat(); err != nil { + 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.RenewOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { + return err + } + } else { + if err = r.db.AddOutboundPeek(ctx, serverName, roomID, peekID, respPeek.RenewalInterval); err != nil { + return err + } + } + + 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.SendEventWithState( + ctx, r.rsAPI, + roomserverAPI.KindNew, + &respState, + respPeek.LatestEvent.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 2fa3d4bff..c23f6fa3e 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, ) (*gomatrixserverlib.RespState, error) { // A list of events that we have retried, if they were not included in diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index 81d3fc514..3f86a2d06 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" + FederationSenderPerformOutboundPeekRequestPath = "/federationsender/performOutboundPeekRequest" FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" @@ -76,6 +77,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) PerformOutboundPeek( + ctx context.Context, + request *api.PerformOutboundPeekRequest, + response *api.PerformOutboundPeekResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformOutboundPeekRequest") + defer span.Finish() + + apiURL := h.federationSenderURL + FederationSenderPerformOutboundPeekRequestPath + 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 03d616f1b..b83613047 100644 --- a/federationsender/storage/interface.go +++ b/federationsender/storage/interface.go @@ -51,7 +51,18 @@ type Database interface { GetPendingPDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) GetPendingEDUServerNames(ctx context.Context) ([]gomatrixserverlib.ServerName, error) + // 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) + + 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/postgres/inbound_peeks_table.go b/federationsender/storage/postgres/inbound_peeks_table.go new file mode 100644 index 000000000..fe35ce44c --- /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 000000000..596b4bcc7 --- /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 75b54bbcb..b9827ca19 100644 --- a/federationsender/storage/postgres/storage.go +++ b/federationsender/storage/postgres/storage.go @@ -64,16 +64,26 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS 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, - Cache: cache, - Writer: d.writer, - FederationSenderJoinedHosts: joinedHosts, - FederationSenderQueuePDUs: queuePDUs, - FederationSenderQueueEDUs: queueEDUs, - FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, - FederationSenderBlacklist: blacklist, + DB: d.db, + Cache: cache, + 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 { return nil, err diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index fbf84c705..4c9490424 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -27,15 +27,17 @@ import ( ) type Database struct { - DB *sql.DB - Cache caching.FederationSenderCache - Writer sqlutil.Writer - FederationSenderQueuePDUs tables.FederationSenderQueuePDUs - FederationSenderQueueEDUs tables.FederationSenderQueueEDUs - FederationSenderQueueJSON tables.FederationSenderQueueJSON - FederationSenderJoinedHosts tables.FederationSenderJoinedHosts - FederationSenderRooms tables.FederationSenderRooms - FederationSenderBlacklist tables.FederationSenderBlacklist + DB *sql.DB + Cache caching.FederationSenderCache + Writer sqlutil.Writer + FederationSenderQueuePDUs tables.FederationSenderQueuePDUs + FederationSenderQueueEDUs tables.FederationSenderQueueEDUs + FederationSenderQueueJSON tables.FederationSenderQueueJSON + FederationSenderJoinedHosts tables.FederationSenderJoinedHosts + FederationSenderRooms tables.FederationSenderRooms + FederationSenderBlacklist tables.FederationSenderBlacklist + FederationSenderOutboundPeeks tables.FederationSenderOutboundPeeks + FederationSenderInboundPeeks tables.FederationSenderInboundPeeks } // An Receipt contains the NIDs of a call to GetNextTransactionPDUs/EDUs. @@ -173,3 +175,43 @@ 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) 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 int64) error { + return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + return d.FederationSenderOutboundPeeks.RenewOutboundPeek(ctx, txn, serverName, roomID, peekID, renewalInterval) + }) +} + +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) 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 000000000..d5eacf9e4 --- /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 new file mode 100644 index 000000000..02aefce79 --- /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 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/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index e66d76909..2b1358587 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -66,16 +66,26 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS if err != nil { return nil, err } + outboundPeeks, err := NewSQLiteOutboundPeeksTable(d.db) + if err != nil { + return nil, err + } + inboundPeeks, err := NewSQLiteInboundPeeksTable(d.db) + if err != nil { + return nil, err + } d.Database = shared.Database{ - DB: d.db, - Cache: cache, - Writer: d.writer, - FederationSenderJoinedHosts: joinedHosts, - FederationSenderQueuePDUs: queuePDUs, - FederationSenderQueueEDUs: queueEDUs, - FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, - FederationSenderBlacklist: blacklist, + DB: d.db, + Cache: cache, + Writer: d.writer, + FederationSenderJoinedHosts: joinedHosts, + FederationSenderQueuePDUs: queuePDUs, + FederationSenderQueueEDUs: queueEDUs, + FederationSenderQueueJSON: queueJSON, + FederationSenderRooms: rooms, + FederationSenderBlacklist: blacklist, + FederationSenderOutboundPeeks: outboundPeeks, + FederationSenderInboundPeeks: inboundPeeks, } 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 69e952de2..22fd5554f 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -67,3 +67,21 @@ 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 FederationSenderOutboundPeeks interface { + 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 398d32677..90da310c9 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -49,3 +49,23 @@ func (e EventIDMismatchError) Error() string { e.DatabaseID, e.RoomServerID, ) } + +// tracks peeks we're performing on another server over federation +type OutboundPeek struct { + PeekID string + RoomID string + ServerName gomatrixserverlib.ServerName + CreationTimestamp int64 + 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/go.mod b/go.mod index a0fee1030..bfe605fdd 100644 --- a/go.mod +++ b/go.mod @@ -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 7accf175f..f411660fa 100644 --- a/go.sum +++ b/go.sum @@ -995,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/roomserver/api/api.go b/roomserver/api/api.go index cedd61930..72e406ee8 100644 --- a/roomserver/api/api.go +++ b/roomserver/api/api.go @@ -56,6 +56,12 @@ type RoomserverInternalAPI interface { res *PerformPublishResponse, ) + PerformInboundPeek( + ctx context.Context, + req *PerformInboundPeekRequest, + res *PerformInboundPeekResponse, + ) error + QueryPublishedRooms( ctx context.Context, req *QueryPublishedRoomsRequest, diff --git a/roomserver/api/api_trace.go b/roomserver/api/api_trace.go index 40745975e..1a2b9a490 100644 --- a/roomserver/api/api_trace.go +++ b/roomserver/api/api_trace.go @@ -88,6 +88,16 @@ func (t *RoomserverInternalAPITrace) PerformPublish( util.GetLogger(ctx).Infof("PerformPublish req=%+v res=%+v", js(req), js(res)) } +func (t *RoomserverInternalAPITrace) PerformInboundPeek( + ctx context.Context, + req *PerformInboundPeekRequest, + res *PerformInboundPeekResponse, +) error { + err := t.Impl.PerformInboundPeek(ctx, req, res) + util.GetLogger(ctx).Infof("PerformInboundPeek 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 2993813cb..d60d1cc86 100644 --- a/roomserver/api/output.go +++ b/roomserver/api/output.go @@ -51,6 +51,8 @@ const ( // OutputTypeNewPeek indicates that the kafka event is an OutputNewPeek OutputTypeNewPeek OutputType = "new_peek" + // OutputTypeNewInboundPeek indicates that the kafka event is an OutputNewInboundPeek + OutputTypeNewInboundPeek OutputType = "new_inbound_peek" // OutputTypeRetirePeek indicates that the kafka event is an OutputRetirePeek OutputTypeRetirePeek OutputType = "retire_peek" ) @@ -72,6 +74,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 OutputTypeNewInboundPeek + NewInboundPeek *OutputNewInboundPeek `json:"new_inbound_peek,omitempty"` // The content of event with type OutputTypeRetirePeek RetirePeek *OutputRetirePeek `json:"retire_peek,omitempty"` } @@ -245,6 +249,19 @@ type OutputNewPeek struct { DeviceID string } +// An OutputNewInboundPeek is written whenever a server starts peeking into a room +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 +} + // An OutputRetirePeek is written whenever a user stops peeking into a room. type OutputRetirePeek struct { RoomID string diff --git a/roomserver/api/perform.go b/roomserver/api/perform.go index ae2d6d975..51cbcb1ad 100644 --- a/roomserver/api/perform.go +++ b/roomserver/api/perform.go @@ -172,6 +172,28 @@ type PerformPublishResponse struct { Error *PerformError } +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"` +} + +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"` + // 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"` + // The event at which this state was captured + LatestEvent *gomatrixserverlib.HeaderedEvent `json:"latest_event"` +} + // PerformForgetRequest is a request to PerformForget type PerformForgetRequest struct { RoomID string `json:"room_id"` diff --git a/roomserver/api/query.go b/roomserver/api/query.go index 43e562a98..43bbfd16d 100644 --- a/roomserver/api/query.go +++ b/roomserver/api/query.go @@ -221,7 +221,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. 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/api/wrapper.go b/roomserver/api/wrapper.go index 7779dbde0..a6ef735ce 100644 --- a/roomserver/api/wrapper.go +++ b/roomserver/api/wrapper.go @@ -43,7 +43,7 @@ func SendEvents( // SendEventWithState writes an event with the specified kind 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. func SendEventWithState( ctx context.Context, rsAPI RoomserverInternalAPI, kind Kind, state *gomatrixserverlib.RespState, event *gomatrixserverlib.HeaderedEvent, diff --git a/roomserver/internal/api.go b/roomserver/internal/api.go index 91caa0bdc..e10bdb464 100644 --- a/roomserver/internal/api.go +++ b/roomserver/internal/api.go @@ -24,6 +24,7 @@ type RoomserverInternalAPI struct { *perform.Inviter *perform.Joiner *perform.Peeker + *perform.InboundPeeker *perform.Unpeeker *perform.Leaver *perform.Publisher @@ -97,6 +98,10 @@ func (r *RoomserverInternalAPI) SetFederationSenderAPI(fsAPI fsAPI.FederationSen FSAPI: r.fsAPI, Inputer: r.Inputer, } + 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_inbound_peek.go b/roomserver/internal/perform/perform_inbound_peek.go new file mode 100644 index 000000000..eb3c9727d --- /dev/null +++ b/roomserver/internal/perform/perform_inbound_peek.go @@ -0,0 +1,129 @@ +// 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/input" + "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 + Inputer *input.Inputer +} + +// 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 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 *InboundPeeker) PerformInboundPeek( + 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 + } + latestEvents, err := r.DB.EventsFromIDs(ctx, []string{latestEventRefs[0].EventID}) + if err != nil { + return err + } + 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) + + 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)) + } + + 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, + }, + }, + }) + return err +} diff --git a/roomserver/internal/perform/perform_peek.go b/roomserver/internal/perform/perform_peek.go index 2f4694c86..443276cd7 100644 --- a/roomserver/internal/perform/perform_peek.go +++ b/roomserver/internal/perform/perform_peek.go @@ -151,11 +151,28 @@ 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 + // 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 + // 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.PerformOutboundPeekRequest{ + RoomID: req.RoomIDOrAlias, // the room ID to try and peek + ServerNames: req.ServerNames, // the servers to try peeking via + } + fedRes := fsAPI.PerformOutboundPeekResponse{} + _ = r.FSAPI.PerformOutboundPeek(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. diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index 7346c7a77..2a361641a 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) } @@ -447,10 +447,12 @@ func (r *Queryer) QueryStateAndAuthChain( response.RoomExists = true response.RoomVersion = info.RoomVersion - stateEvents, err := r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) + var stateEvents []*gomatrixserverlib.Event + stateEvents, err = r.loadStateAtEventIDs(ctx, *info, request.PrevEventIDs) if err != nil { return err } + response.PrevEventsExist = true // add the auth event IDs for the current state events too @@ -461,7 +463,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 } @@ -510,11 +512,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 @@ -718,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 } diff --git a/roomserver/internal/query/query_test.go b/roomserver/internal/query/query_test.go index 4e761d8ec..ba5bb9f55 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) } diff --git a/roomserver/inthttp/client.go b/roomserver/inthttp/client.go index cac813ffe..6774d102d 100644 --- a/roomserver/inthttp/client.go +++ b/roomserver/inthttp/client.go @@ -26,14 +26,15 @@ const ( RoomserverInputRoomEventsPath = "/roomserver/inputRoomEvents" // Perform operations - RoomserverPerformInvitePath = "/roomserver/performInvite" - RoomserverPerformPeekPath = "/roomserver/performPeek" - RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" - RoomserverPerformJoinPath = "/roomserver/performJoin" - RoomserverPerformLeavePath = "/roomserver/performLeave" - RoomserverPerformBackfillPath = "/roomserver/performBackfill" - RoomserverPerformPublishPath = "/roomserver/performPublish" - RoomserverPerformForgetPath = "/roomserver/performForget" + RoomserverPerformInvitePath = "/roomserver/performInvite" + RoomserverPerformPeekPath = "/roomserver/performPeek" + RoomserverPerformUnpeekPath = "/roomserver/performUnpeek" + RoomserverPerformJoinPath = "/roomserver/performJoin" + RoomserverPerformLeavePath = "/roomserver/performLeave" + RoomserverPerformBackfillPath = "/roomserver/performBackfill" + RoomserverPerformPublishPath = "/roomserver/performPublish" + RoomserverPerformInboundPeekPath = "/roomserver/performInboundPeek" + RoomserverPerformForgetPath = "/roomserver/performForget" // Query operations RoomserverQueryLatestEventsAndStatePath = "/roomserver/queryLatestEventsAndState" @@ -216,6 +217,18 @@ func (h *httpRoomserverInternalAPI) PerformPeek( } } +func (h *httpRoomserverInternalAPI) PerformInboundPeek( + ctx context.Context, + request *api.PerformInboundPeekRequest, + response *api.PerformInboundPeekResponse, +) error { + span, ctx := opentracing.StartSpanFromContext(ctx, "PerformInboundPeek") + defer span.Finish() + + apiURL := h.roomserverURL + RoomserverPerformInboundPeekPath + return httputil.PostJSON(ctx, span, h.httpClient, apiURL, request, response) +} + func (h *httpRoomserverInternalAPI) PerformUnpeek( ctx context.Context, request *api.PerformUnpeekRequest, diff --git a/roomserver/inthttp/server.go b/roomserver/inthttp/server.go index f9c8ef9fd..bf319262f 100644 --- a/roomserver/inthttp/server.go +++ b/roomserver/inthttp/server.go @@ -72,6 +72,19 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) { return util.JSONResponse{Code: http.StatusOK, JSON: &response} }), ) + 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.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 diff --git a/syncapi/types/types.go b/syncapi/types/types.go index 4ccc8a489..49fa1a166 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"` From 93942f8ab62ffdb55888d5d35adef6fe73e3ea59 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Fri, 22 Jan 2021 16:08:47 +0000 Subject: [PATCH 11/33] Gate peeking behind msc flags (#1731) --- clientapi/clientapi.go | 3 +- clientapi/routing/routing.go | 32 ++++++----- .../personalities/clientapi.go | 1 + .../personalities/federationapi.go | 1 + cmd/generate-config/main.go | 2 +- federationapi/federationapi.go | 3 +- federationapi/federationapi_test.go | 2 +- federationapi/routing/routing.go | 57 ++++++++++--------- setup/config/config_mscs.go | 16 +++++- setup/monolith.go | 3 +- setup/mscs/mscs.go | 3 + 11 files changed, 76 insertions(+), 47 deletions(-) diff --git a/clientapi/clientapi.go b/clientapi/clientapi.go index 8a2ea8fc4..2c4fa5d64 100644 --- a/clientapi/clientapi.go +++ b/clientapi/clientapi.go @@ -46,6 +46,7 @@ func AddPublicRoutes( userAPI userapi.UserInternalAPI, keyAPI keyserverAPI.KeyInternalAPI, extRoomsProvider api.ExtraPublicRoomsProvider, + mscCfg *config.MSCs, ) { _, producer := kafka.SetupConsumerProducer(&cfg.Matrix.Kafka) @@ -57,6 +58,6 @@ func AddPublicRoutes( routing.Setup( router, cfg, eduInputAPI, rsAPI, asAPI, accountsDB, userAPI, federation, - syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, + syncProducer, transactionsCache, fsAPI, keyAPI, extRoomsProvider, mscCfg, ) } diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index 7c320253d..a56359b4c 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -58,6 +58,7 @@ func Setup( federationSender federationSenderAPI.FederationSenderInternalAPI, keyAPI keyserverAPI.KeyInternalAPI, extRoomsProvider api.ExtraPublicRoomsProvider, + mscCfg *config.MSCs, ) { rateLimits := newRateLimits(&cfg.RateLimiting) userInteractiveAuth := auth.NewUserInteractive(accountDB.GetAccountByPassword, cfg) @@ -110,20 +111,23 @@ func Setup( ) }), ).Methods(http.MethodPost, http.MethodOptions) - r0mux.Handle("/peek/{roomIDOrAlias}", - httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { - if r := rateLimits.rateLimit(req); r != nil { - return *r - } - 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) + + if mscCfg.Enabled("msc2753") { + r0mux.Handle("/peek/{roomIDOrAlias}", + httputil.MakeAuthAPI(gomatrixserverlib.Peek, userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse { + if r := rateLimits.rateLimit(req); r != nil { + return *r + } + 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, rsAPI) diff --git a/cmd/dendrite-polylith-multi/personalities/clientapi.go b/cmd/dendrite-polylith-multi/personalities/clientapi.go index b3cc411b3..ec445ceb7 100644 --- a/cmd/dendrite-polylith-multi/personalities/clientapi.go +++ b/cmd/dendrite-polylith-multi/personalities/clientapi.go @@ -35,6 +35,7 @@ func ClientAPI(base *setup.BaseDendrite, cfg *config.Dendrite) { clientapi.AddPublicRoutes( base.PublicClientAPIMux, &base.Cfg.ClientAPI, accountDB, federation, rsAPI, eduInputAPI, asQuery, transactions.New(), fsAPI, userAPI, keyAPI, nil, + &cfg.MSCs, ) base.SetupAndServeHTTP( diff --git a/cmd/dendrite-polylith-multi/personalities/federationapi.go b/cmd/dendrite-polylith-multi/personalities/federationapi.go index 7957b211f..498be3c43 100644 --- a/cmd/dendrite-polylith-multi/personalities/federationapi.go +++ b/cmd/dendrite-polylith-multi/personalities/federationapi.go @@ -33,6 +33,7 @@ func FederationAPI(base *setup.BaseDendrite, cfg *config.Dendrite) { base.PublicFederationAPIMux, base.PublicKeyAPIMux, &base.Cfg.FederationAPI, userAPI, federation, keyRing, rsAPI, fsAPI, base.EDUServerClient(), keyAPI, + &base.Cfg.MSCs, ) base.SetupAndServeHTTP( diff --git a/cmd/generate-config/main.go b/cmd/generate-config/main.go index ff0b311aa..fa0da10c5 100644 --- a/cmd/generate-config/main.go +++ b/cmd/generate-config/main.go @@ -63,7 +63,7 @@ func main() { if *defaultsForCI { cfg.ClientAPI.RateLimiting.Enabled = false cfg.FederationSender.DisableTLSValidation = true - cfg.MSCs.MSCs = []string{"msc2836", "msc2946"} + cfg.MSCs.MSCs = []string{"msc2836", "msc2946", "msc2444", "msc2753"} cfg.Logging[0].Level = "trace" // don't hit matrix.org when running tests!!! cfg.SigningKeyServer.KeyPerspectives = config.KeyPerspectives{} diff --git a/federationapi/federationapi.go b/federationapi/federationapi.go index 350d58538..6188b283e 100644 --- a/federationapi/federationapi.go +++ b/federationapi/federationapi.go @@ -38,10 +38,11 @@ func AddPublicRoutes( federationSenderAPI federationSenderAPI.FederationSenderInternalAPI, eduAPI eduserverAPI.EDUServerInputAPI, keyAPI keyserverAPI.KeyInternalAPI, + mscCfg *config.MSCs, ) { routing.Setup( fedRouter, keyRouter, cfg, rsAPI, eduAPI, federationSenderAPI, keyRing, - federation, userAPI, keyAPI, + federation, userAPI, keyAPI, mscCfg, ) } diff --git a/federationapi/federationapi_test.go b/federationapi/federationapi_test.go index aed47a362..923cd0eca 100644 --- a/federationapi/federationapi_test.go +++ b/federationapi/federationapi_test.go @@ -31,7 +31,7 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) { fsAPI := base.FederationSenderHTTPClient() // TODO: This is pretty fragile, as if anything calls anything on these nils this test will break. // Unfortunately, it makes little sense to instantiate these dependencies when we just want to test routing. - federationapi.AddPublicRoutes(base.PublicFederationAPIMux, base.PublicKeyAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, nil) + federationapi.AddPublicRoutes(base.PublicFederationAPIMux, base.PublicKeyAPIMux, &cfg.FederationAPI, nil, nil, keyRing, nil, fsAPI, nil, nil, &cfg.MSCs) baseURL, cancel := test.ListenAndServe(t, base.PublicFederationAPIMux, true) defer cancel() serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://")) diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 7e76a143b..b579ae1fa 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -48,6 +48,7 @@ func Setup( federation *gomatrixserverlib.FederationClient, userAPI userapi.UserInternalAPI, keyAPI keyserverAPI.KeyInternalAPI, + mscCfg *config.MSCs, ) { v2keysmux := keyMux.PathPrefix("/v2").Subrouter() v1fedmux := fedMux.PathPrefix("/v1").Subrouter() @@ -229,35 +230,37 @@ func Setup( }, )).Methods(http.MethodGet) - 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"), + if mscCfg.Enabled("msc2444") { + 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)) + 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) } - } 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) + 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, diff --git a/setup/config/config_mscs.go b/setup/config/config_mscs.go index 4b53495f0..764273ecc 100644 --- a/setup/config/config_mscs.go +++ b/setup/config/config_mscs.go @@ -3,7 +3,11 @@ package config type MSCs struct { Matrix *Global `yaml:"-"` - // The MSCs to enable + // The MSCs to enable. Supported MSCs include: + // 'msc2444': Peeking over federation - https://github.com/matrix-org/matrix-doc/pull/2444 + // 'msc2753': Peeking via /sync - https://github.com/matrix-org/matrix-doc/pull/2753 + // 'msc2836': Threading - https://github.com/matrix-org/matrix-doc/pull/2836 + // 'msc2946': Spaces Summary - https://github.com/matrix-org/matrix-doc/pull/2946 MSCs []string `yaml:"mscs"` Database DatabaseOptions `yaml:"database"` @@ -14,6 +18,16 @@ func (c *MSCs) Defaults() { c.Database.ConnectionString = "file:mscs.db" } +// Enabled returns true if the given msc is enabled. Should in the form 'msc12345'. +func (c *MSCs) Enabled(msc string) bool { + for _, m := range c.MSCs { + if m == msc { + return true + } + } + return false +} + func (c *MSCs) Verify(configErrs *ConfigErrors, isMonolith bool) { checkNotEmpty(configErrs, "mscs.database.connection_string", string(c.Database.ConnectionString)) } diff --git a/setup/monolith.go b/setup/monolith.go index 2403f57fa..fd84ef538 100644 --- a/setup/monolith.go +++ b/setup/monolith.go @@ -62,11 +62,12 @@ func (m *Monolith) AddAllPublicRoutes(csMux, ssMux, keyMux, mediaMux *mux.Router m.FedClient, m.RoomserverAPI, m.EDUInternalAPI, m.AppserviceAPI, transactions.New(), m.FederationSenderAPI, m.UserAPI, m.KeyAPI, m.ExtPublicRoomsProvider, + &m.Config.MSCs, ) federationapi.AddPublicRoutes( ssMux, keyMux, &m.Config.FederationAPI, m.UserAPI, m.FedClient, m.KeyRing, m.RoomserverAPI, m.FederationSenderAPI, - m.EDUInternalAPI, m.KeyAPI, + m.EDUInternalAPI, m.KeyAPI, &m.Config.MSCs, ) mediaapi.AddPublicRoutes(mediaMux, &m.Config.MediaAPI, m.UserAPI, m.Client) syncapi.AddPublicRoutes( diff --git a/setup/mscs/mscs.go b/setup/mscs/mscs.go index 027885c8d..da02956b0 100644 --- a/setup/mscs/mscs.go +++ b/setup/mscs/mscs.go @@ -42,7 +42,10 @@ func EnableMSC(base *setup.BaseDendrite, monolith *setup.Monolith, msc string) e return msc2836.Enable(base, monolith.RoomserverAPI, monolith.FederationSenderAPI, monolith.UserAPI, monolith.KeyRing) case "msc2946": return msc2946.Enable(base, monolith.RoomserverAPI, monolith.UserAPI, monolith.FederationSenderAPI, monolith.KeyRing) + case "msc2444": // enabled inside federationapi + case "msc2753": // enabled inside clientapi default: return fmt.Errorf("EnableMSC: unknown msc '%s'", msc) } + return nil } From 6757b67a321c684769ad022351541c7c6417c693 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 22 Jan 2021 16:09:05 +0000 Subject: [PATCH 12/33] NewClient and NewFederationClient updates (#1730) * Use matrix-org/gomatrixserverlib#252 * Add missing WithSkipVerify to test * Functions instead * Update gomatrixserverlib to matrix-org/gomatrixserverlib#252 * Fix disabling TLS validation --- cmd/dendrite-demo-libp2p/main.go | 9 +++-- cmd/dendrite-demo-yggdrasil/yggconn/client.go | 9 +++-- cmd/dendritejs/main.go | 8 +++-- cmd/furl/main.go | 1 - federationapi/federationapi_test.go | 5 ++- go.mod | 2 +- go.sum | 4 +-- keyserver/internal/device_list_update_test.go | 6 ++-- setup/base.go | 33 +++++++++++-------- signingkeyserver/serverkeyapi_test.go | 5 +-- 10 files changed, 50 insertions(+), 32 deletions(-) diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index 3acec2fd0..31e7739a9 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -76,9 +76,10 @@ func createFederationClient( "matrix", p2phttp.NewTransport(base.LibP2P, p2phttp.ProtocolOption("/matrix")), ) - return gomatrixserverlib.NewFederationClientWithTransport( + return gomatrixserverlib.NewFederationClient( base.Base.Cfg.Global.ServerName, base.Base.Cfg.Global.KeyID, - base.Base.Cfg.Global.PrivateKey, true, tr, + base.Base.Cfg.Global.PrivateKey, + gomatrixserverlib.WithTransport(tr), ) } @@ -90,7 +91,9 @@ func createClient( "matrix", p2phttp.NewTransport(base.LibP2P, p2phttp.ProtocolOption("/matrix")), ) - return gomatrixserverlib.NewClientWithTransport(tr) + return gomatrixserverlib.NewClient( + gomatrixserverlib.WithTransport(tr), + ) } func main() { diff --git a/cmd/dendrite-demo-yggdrasil/yggconn/client.go b/cmd/dendrite-demo-yggdrasil/yggconn/client.go index ea51f4b17..157a9bf2c 100644 --- a/cmd/dendrite-demo-yggdrasil/yggconn/client.go +++ b/cmd/dendrite-demo-yggdrasil/yggconn/client.go @@ -33,7 +33,9 @@ func (n *Node) CreateClient( }, }, ) - return gomatrixserverlib.NewClientWithTransport(tr) + return gomatrixserverlib.NewClient( + gomatrixserverlib.WithTransport(tr), + ) } func (n *Node) CreateFederationClient( @@ -53,8 +55,9 @@ func (n *Node) CreateFederationClient( }, }, ) - return gomatrixserverlib.NewFederationClientWithTransport( + return gomatrixserverlib.NewFederationClient( base.Cfg.Global.ServerName, base.Cfg.Global.KeyID, - base.Cfg.Global.PrivateKey, true, tr, + base.Cfg.Global.PrivateKey, + gomatrixserverlib.WithTransport(tr), ) } diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index 1ffb1667b..b77d141d8 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -139,16 +139,18 @@ func createFederationClient(cfg *config.Dendrite, node *go_http_js_libp2p.P2pLoc tr := go_http_js_libp2p.NewP2pTransport(node) fed := gomatrixserverlib.NewFederationClient( - cfg.Global.ServerName, cfg.Global.KeyID, cfg.Global.PrivateKey, true, + cfg.Global.ServerName, cfg.Global.KeyID, cfg.Global.PrivateKey, + gomatrixserverlib.WithTransport(tr), ) - fed.Client = *gomatrixserverlib.NewClientWithTransport(tr) return fed } func createClient(node *go_http_js_libp2p.P2pLocalNode) *gomatrixserverlib.Client { tr := go_http_js_libp2p.NewP2pTransport(node) - return gomatrixserverlib.NewClientWithTransport(tr) + return gomatrixserverlib.NewClient( + gomatrixserverlib.WithTransport(tr), + ) } func createP2PNode(privKey ed25519.PrivateKey) (serverName string, node *go_http_js_libp2p.P2pLocalNode) { diff --git a/cmd/furl/main.go b/cmd/furl/main.go index 3955ef0cd..bec04f0aa 100644 --- a/cmd/furl/main.go +++ b/cmd/furl/main.go @@ -54,7 +54,6 @@ func main() { gomatrixserverlib.ServerName(*requestFrom), gomatrixserverlib.KeyID(keyBlock.Headers["Key-ID"]), privateKey, - false, ) u, err := url.Parse(flag.Arg(0)) diff --git a/federationapi/federationapi_test.go b/federationapi/federationapi_test.go index 923cd0eca..b97876d3d 100644 --- a/federationapi/federationapi_test.go +++ b/federationapi/federationapi_test.go @@ -36,7 +36,10 @@ func TestRoomsV3URLEscapeDoNot404(t *testing.T) { defer cancel() serverName := gomatrixserverlib.ServerName(strings.TrimPrefix(baseURL, "https://")) - fedCli := gomatrixserverlib.NewFederationClient(serverName, cfg.Global.KeyID, cfg.Global.PrivateKey, true) + fedCli := gomatrixserverlib.NewFederationClient( + serverName, cfg.Global.KeyID, cfg.Global.PrivateKey, + gomatrixserverlib.WithSkipVerify(true), + ) testCases := []struct { roomVer gomatrixserverlib.RoomVersion diff --git a/go.mod b/go.mod index bfe605fdd..891d0f57d 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-20210122141547-5c2c047bfdcd + github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37 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 diff --git a/go.sum b/go.sum index f411660fa..acdfaf2c0 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +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-20210122141547-5c2c047bfdcd h1:SbWP7aHgACfAQpI2te6LzxK3l0+UqPOb8NOCOxzwV1c= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210122141547-5c2c047bfdcd/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37 h1:si2CZZpwOLWZfDXfgHPkaTlaAkdJvpJzr1zVqyKXd0I= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37/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= diff --git a/keyserver/internal/device_list_update_test.go b/keyserver/internal/device_list_update_test.go index 9c4cc1165..eab2a78d8 100644 --- a/keyserver/internal/device_list_update_test.go +++ b/keyserver/internal/device_list_update_test.go @@ -106,9 +106,11 @@ func (t *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { func newFedClient(tripper func(*http.Request) (*http.Response, error)) *gomatrixserverlib.FederationClient { _, pkey, _ := ed25519.GenerateKey(nil) fedClient := gomatrixserverlib.NewFederationClient( - gomatrixserverlib.ServerName("example.test"), gomatrixserverlib.KeyID("ed25519:test"), pkey, true, + gomatrixserverlib.ServerName("example.test"), gomatrixserverlib.KeyID("ed25519:test"), pkey, + ) + fedClient.Client = *gomatrixserverlib.NewClient( + gomatrixserverlib.WithTransport(&roundTripper{tripper}), ) - fedClient.Client = *gomatrixserverlib.NewClientWithTransport(&roundTripper{tripper}) return fedClient } diff --git a/setup/base.go b/setup/base.go index 5f0431e84..c30e69105 100644 --- a/setup/base.go +++ b/setup/base.go @@ -266,15 +266,17 @@ func (b *BaseDendrite) CreateAccountsDB() accounts.Database { // Should only be called once per component. func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client { if b.Cfg.Global.DisableFederation { - return gomatrixserverlib.NewClientWithTransport(noOpHTTPTransport) + return gomatrixserverlib.NewClient( + gomatrixserverlib.WithTransport(noOpHTTPTransport), + ) + } + opts := []gomatrixserverlib.ClientOption{ + gomatrixserverlib.WithSkipVerify(b.Cfg.FederationSender.DisableTLSValidation), } - opts := []gomatrixserverlib.ClientOption{} if b.Cfg.Global.DNSCache.Enabled { - opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) + opts = append(opts, gomatrixserverlib.WithDNSCache(b.DNSCache)) } - client := gomatrixserverlib.NewClient( - b.Cfg.FederationSender.DisableTLSValidation, opts..., - ) + client := gomatrixserverlib.NewClient(opts...) client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString())) return client } @@ -283,18 +285,21 @@ func (b *BaseDendrite) CreateClient() *gomatrixserverlib.Client { // once per component. func (b *BaseDendrite) CreateFederationClient() *gomatrixserverlib.FederationClient { if b.Cfg.Global.DisableFederation { - return gomatrixserverlib.NewFederationClientWithTransport( + return gomatrixserverlib.NewFederationClient( b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, b.Cfg.Global.PrivateKey, - b.Cfg.FederationSender.DisableTLSValidation, noOpHTTPTransport, + gomatrixserverlib.WithTransport(noOpHTTPTransport), ) } - opts := []gomatrixserverlib.ClientOption{} - if b.Cfg.Global.DNSCache.Enabled { - opts = append(opts, gomatrixserverlib.WithDNSCache{DNSCache: b.DNSCache}) + opts := []gomatrixserverlib.ClientOption{ + gomatrixserverlib.WithTimeout(time.Minute * 5), + gomatrixserverlib.WithSkipVerify(b.Cfg.FederationSender.DisableTLSValidation), } - client := gomatrixserverlib.NewFederationClientWithTimeout( - b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, b.Cfg.Global.PrivateKey, - b.Cfg.FederationSender.DisableTLSValidation, time.Minute*5, opts..., + if b.Cfg.Global.DNSCache.Enabled { + opts = append(opts, gomatrixserverlib.WithDNSCache(b.DNSCache)) + } + client := gomatrixserverlib.NewFederationClient( + b.Cfg.Global.ServerName, b.Cfg.Global.KeyID, + b.Cfg.Global.PrivateKey, opts..., ) client.SetUserAgent(fmt.Sprintf("Dendrite/%s", internal.VersionString())) return client diff --git a/signingkeyserver/serverkeyapi_test.go b/signingkeyserver/serverkeyapi_test.go index e59deb4d7..bd6119aae 100644 --- a/signingkeyserver/serverkeyapi_test.go +++ b/signingkeyserver/serverkeyapi_test.go @@ -87,8 +87,9 @@ func TestMain(m *testing.M) { transport.RegisterProtocol("matrix", &MockRoundTripper{}) // Create the federation client. - s.fedclient = gomatrixserverlib.NewFederationClientWithTransport( - s.config.Matrix.ServerName, serverKeyID, testPriv, true, transport, + s.fedclient = gomatrixserverlib.NewFederationClient( + s.config.Matrix.ServerName, serverKeyID, testPriv, + gomatrixserverlib.WithTransport(transport), ) // Finally, build the server key APIs. From ef9d5ad4fe96b61972a5c0c1cd53d65a612c21f1 Mon Sep 17 00:00:00 2001 From: Kegsay Date: Fri, 22 Jan 2021 17:16:35 +0000 Subject: [PATCH 13/33] Check peek state response and refactor checking send_join response (#1732) --- federationsender/internal/perform.go | 102 ++++++++++++++++--- federationsender/internal/perform/join.go | 118 ---------------------- 2 files changed, 88 insertions(+), 132 deletions(-) delete mode 100644 federationsender/internal/perform/join.go diff --git a/federationsender/internal/perform.go b/federationsender/internal/perform.go index 3adf8fc94..6a2531a03 100644 --- a/federationsender/internal/perform.go +++ b/federationsender/internal/perform.go @@ -8,7 +8,6 @@ import ( "time" "github.com/matrix-org/dendrite/federationsender/api" - "github.com/matrix-org/dendrite/federationsender/internal/perform" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/roomserver/version" "github.com/matrix-org/gomatrix" @@ -218,9 +217,9 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( // Sanity-check the join response to ensure that it has a create // event, that the room version is known, etc. - if err := sanityCheckSendJoinResponse(respSendJoin); err != nil { + if err := sanityCheckAuthChain(respSendJoin.AuthEvents); err != nil { cancel() - return fmt.Errorf("sanityCheckSendJoinResponse: %w", err) + return fmt.Errorf("sanityCheckAuthChain: %w", err) } // Process the join response in a goroutine. The idea here is @@ -231,11 +230,9 @@ func (r *FederationSenderInternalAPI) performJoinUsingServer( go func() { defer cancel() - // Check that the send_join response was valid. - joinCtx := perform.JoinContext(r.federation, r.keyRing) - respState, err := joinCtx.CheckSendJoinResponse( - ctx, event, serverName, respSendJoin, - ) + // TODO: Can we expand Check here to return a list of missing auth + // events rather than failing one at a time? + respState, err := respSendJoin.Check(ctx, r.keyRing, event, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName)) if err != nil { logrus.WithFields(logrus.Fields{ "room_id": roomID, @@ -402,8 +399,18 @@ func (r *FederationSenderInternalAPI) performOutboundPeekUsingServer( return fmt.Errorf("respPeek.RoomVersion.EventFormat: %w", err) } - // TODO: authenticate the state returned (check its auth events etc) + // we have the peek state now so let's process regardless of whether upstream gives up + ctx = context.Background() + + respState := respPeek.ToRespState() + // authenticate the state returned (check its auth events etc) // the equivalent of CheckSendJoinResponse() + if err = sanityCheckAuthChain(respState.AuthEvents); err != nil { + return fmt.Errorf("sanityCheckAuthChain: %w", err) + } + if err = respState.Check(ctx, r.keyRing, federatedAuthProvider(ctx, r.federation, r.keyRing, serverName)); err != nil { + return fmt.Errorf("Error checking state returned from peeking: %w", err) + } // If we've got this far, the remote server is peeking. if renewing { @@ -416,7 +423,6 @@ 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.SendEventWithState( @@ -607,9 +613,9 @@ func (r *FederationSenderInternalAPI) PerformBroadcastEDU( return nil } -func sanityCheckSendJoinResponse(respSendJoin gomatrixserverlib.RespSendJoin) error { +func sanityCheckAuthChain(authChain []*gomatrixserverlib.Event) error { // sanity check we have a create event and it has a known room version - for _, ev := range respSendJoin.AuthEvents { + for _, ev := range authChain { if ev.Type() == gomatrixserverlib.MRoomCreate && ev.StateKeyEquals("") { // make sure the room version is known content := ev.Content() @@ -627,12 +633,12 @@ func sanityCheckSendJoinResponse(respSendJoin gomatrixserverlib.RespSendJoin) er } knownVersions := gomatrixserverlib.RoomVersions() if _, ok := knownVersions[gomatrixserverlib.RoomVersion(verBody.Version)]; !ok { - return fmt.Errorf("send_join m.room.create event has an unknown room version: %s", verBody.Version) + return fmt.Errorf("auth chain m.room.create event has an unknown room version: %s", verBody.Version) } return nil } } - return fmt.Errorf("send_join response is missing m.room.create event") + return fmt.Errorf("auth chain response is missing m.room.create event") } func setDefaultRoomVersionFromJoinEvent(joinEvent gomatrixserverlib.EventBuilder) gomatrixserverlib.RoomVersion { @@ -656,3 +662,71 @@ func setDefaultRoomVersionFromJoinEvent(joinEvent gomatrixserverlib.EventBuilder } return gomatrixserverlib.RoomVersionV4 } + +// FederatedAuthProvider is an auth chain provider which fetches events from the server provided +func federatedAuthProvider( + ctx context.Context, federation *gomatrixserverlib.FederationClient, + keyRing gomatrixserverlib.JSONVerifier, server gomatrixserverlib.ServerName, +) gomatrixserverlib.AuthChainProvider { + // A list of events that we have retried, if they were not included in + // the auth events supplied in the send_join. + retries := map[string][]*gomatrixserverlib.Event{} + + // Define a function which we can pass to Check to retrieve missing + // auth events inline. This greatly increases our chances of not having + // to repeat the entire set of checks just for a missing event or two. + return func(roomVersion gomatrixserverlib.RoomVersion, eventIDs []string) ([]*gomatrixserverlib.Event, error) { + returning := []*gomatrixserverlib.Event{} + + // See if we have retry entries for each of the supplied event IDs. + for _, eventID := range eventIDs { + // If we've already satisfied a request for this event ID before then + // just append the results. We won't retry the request. + if retry, ok := retries[eventID]; ok { + if retry == nil { + return nil, fmt.Errorf("missingAuth: not retrying failed event ID %q", eventID) + } + returning = append(returning, retry...) + continue + } + + // Make a note of the fact that we tried to do something with this + // event ID, even if we don't succeed. + retries[eventID] = nil + + // Try to retrieve the event from the server that sent us the send + // join response. + tx, txerr := federation.GetEvent(ctx, server, eventID) + if txerr != nil { + return nil, fmt.Errorf("missingAuth r.federation.GetEvent: %w", txerr) + } + + // For each event returned, add it to the set of return events. We + // also will populate the retries, in case someone asks for this + // event ID again. + for _, pdu := range tx.PDUs { + // Try to parse the event. + ev, everr := gomatrixserverlib.NewEventFromUntrustedJSON(pdu, roomVersion) + if everr != nil { + return nil, fmt.Errorf("missingAuth gomatrixserverlib.NewEventFromUntrustedJSON: %w", everr) + } + + // Check the signatures of the event. + if res, err := gomatrixserverlib.VerifyEventSignatures(ctx, []*gomatrixserverlib.Event{ev}, keyRing); err != nil { + return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err) + } else { + for _, err := range res { + if err != nil { + return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err) + } + } + } + + // If the event is OK then add it to the results and the retry map. + returning = append(returning, ev) + retries[ev.EventID()] = append(retries[ev.EventID()], ev) + } + } + return returning, nil + } +} diff --git a/federationsender/internal/perform/join.go b/federationsender/internal/perform/join.go deleted file mode 100644 index c23f6fa3e..000000000 --- a/federationsender/internal/perform/join.go +++ /dev/null @@ -1,118 +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 perform - -import ( - "context" - "fmt" - - "github.com/matrix-org/gomatrixserverlib" -) - -// This file contains helpers for the PerformJoin function. - -type joinContext struct { - federation *gomatrixserverlib.FederationClient - keyRing *gomatrixserverlib.KeyRing -} - -// Returns a new join context. -func JoinContext(f *gomatrixserverlib.FederationClient, k *gomatrixserverlib.KeyRing) *joinContext { - return &joinContext{ - federation: f, - keyRing: k, - } -} - -// checkSendJoinResponse checks that all of the signatures are correct -// and that the join is allowed by the supplied state. -func (r joinContext) CheckSendJoinResponse( - ctx context.Context, - event *gomatrixserverlib.Event, - server gomatrixserverlib.ServerName, - respSendJoin gomatrixserverlib.RespSendJoin, -) (*gomatrixserverlib.RespState, error) { - // A list of events that we have retried, if they were not included in - // the auth events supplied in the send_join. - retries := map[string][]*gomatrixserverlib.Event{} - - // Define a function which we can pass to Check to retrieve missing - // auth events inline. This greatly increases our chances of not having - // to repeat the entire set of checks just for a missing event or two. - missingAuth := func(roomVersion gomatrixserverlib.RoomVersion, eventIDs []string) ([]*gomatrixserverlib.Event, error) { - returning := []*gomatrixserverlib.Event{} - - // See if we have retry entries for each of the supplied event IDs. - for _, eventID := range eventIDs { - // If we've already satisfied a request for this event ID before then - // just append the results. We won't retry the request. - if retry, ok := retries[eventID]; ok { - if retry == nil { - return nil, fmt.Errorf("missingAuth: not retrying failed event ID %q", eventID) - } - returning = append(returning, retry...) - continue - } - - // Make a note of the fact that we tried to do something with this - // event ID, even if we don't succeed. - retries[event.EventID()] = nil - - // Try to retrieve the event from the server that sent us the send - // join response. - tx, txerr := r.federation.GetEvent(ctx, server, eventID) - if txerr != nil { - return nil, fmt.Errorf("missingAuth r.federation.GetEvent: %w", txerr) - } - - // For each event returned, add it to the set of return events. We - // also will populate the retries, in case someone asks for this - // event ID again. - for _, pdu := range tx.PDUs { - // Try to parse the event. - ev, everr := gomatrixserverlib.NewEventFromUntrustedJSON(pdu, roomVersion) - if everr != nil { - return nil, fmt.Errorf("missingAuth gomatrixserverlib.NewEventFromUntrustedJSON: %w", everr) - } - - // Check the signatures of the event. - if res, err := gomatrixserverlib.VerifyEventSignatures(ctx, []*gomatrixserverlib.Event{ev}, r.keyRing); err != nil { - return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err) - } else { - for _, err := range res { - if err != nil { - return nil, fmt.Errorf("missingAuth VerifyEventSignatures: %w", err) - } - } - } - - // If the event is OK then add it to the results and the retry map. - returning = append(returning, ev) - retries[event.EventID()] = append(retries[event.EventID()], ev) - retries[ev.EventID()] = append(retries[ev.EventID()], ev) - } - } - - return returning, nil - } - - // TODO: Can we expand Check here to return a list of missing auth - // events rather than failing one at a time? - rs, err := respSendJoin.Check(ctx, r.keyRing, event, missingAuth) - if err != nil { - return nil, fmt.Errorf("respSendJoin: %w", err) - } - return rs, nil -} From 64fb6de6d4f0860cc2b7503cfc36eb743552395b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 26 Jan 2021 09:12:17 +0000 Subject: [PATCH 14/33] Don't retrieve same state events over and over again (#1737) --- roomserver/state/state.go | 63 ++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/roomserver/state/state.go b/roomserver/state/state.go index 953276b24..7f0bc37db 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -33,19 +33,21 @@ import ( type StateResolution struct { db storage.Database roomInfo types.RoomInfo + events map[types.EventNID]*gomatrixserverlib.Event } func NewStateResolution(db storage.Database, roomInfo types.RoomInfo) StateResolution { return StateResolution{ db: db, roomInfo: roomInfo, + events: make(map[types.EventNID]*gomatrixserverlib.Event), } } // LoadStateAtSnapshot loads the full state of a room at a particular snapshot. // This is typically the state before an event or the current state of a room. // Returns a sorted list of state entries or an error if there was a problem talking to the database. -func (v StateResolution) LoadStateAtSnapshot( +func (v *StateResolution) LoadStateAtSnapshot( ctx context.Context, stateNID types.StateSnapshotNID, ) ([]types.StateEntry, error) { stateBlockNIDLists, err := v.db.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID}) @@ -83,7 +85,7 @@ func (v StateResolution) LoadStateAtSnapshot( } // LoadStateAtEvent loads the full state of a room before a particular event. -func (v StateResolution) LoadStateAtEvent( +func (v *StateResolution) LoadStateAtEvent( ctx context.Context, eventID string, ) ([]types.StateEntry, error) { snapshotNID, err := v.db.SnapshotNIDFromEventID(ctx, eventID) @@ -105,7 +107,7 @@ func (v StateResolution) LoadStateAtEvent( // LoadCombinedStateAfterEvents loads a snapshot of the state after each of the events // and combines those snapshots together into a single list. At this point it is // possible to run into duplicate (type, state key) tuples. -func (v StateResolution) LoadCombinedStateAfterEvents( +func (v *StateResolution) LoadCombinedStateAfterEvents( ctx context.Context, prevStates []types.StateAtEvent, ) ([]types.StateEntry, error) { stateNIDs := make([]types.StateSnapshotNID, len(prevStates)) @@ -177,7 +179,7 @@ func (v StateResolution) LoadCombinedStateAfterEvents( } // DifferenceBetweeenStateSnapshots works out which state entries have been added and removed between two snapshots. -func (v StateResolution) DifferenceBetweeenStateSnapshots( +func (v *StateResolution) DifferenceBetweeenStateSnapshots( ctx context.Context, oldStateNID, newStateNID types.StateSnapshotNID, ) (removed, added []types.StateEntry, err error) { if oldStateNID == newStateNID { @@ -236,7 +238,7 @@ func (v StateResolution) DifferenceBetweeenStateSnapshots( // If there is no entry for a given event type and state key pair then it will be discarded. // This is typically the state before an event or the current state of a room. // Returns a sorted list of state entries or an error if there was a problem talking to the database. -func (v StateResolution) LoadStateAtSnapshotForStringTuples( +func (v *StateResolution) LoadStateAtSnapshotForStringTuples( ctx context.Context, stateNID types.StateSnapshotNID, stateKeyTuples []gomatrixserverlib.StateKeyTuple, @@ -251,7 +253,7 @@ func (v StateResolution) LoadStateAtSnapshotForStringTuples( // stringTuplesToNumericTuples converts the string state key tuples into numeric IDs // If there isn't a numeric ID for either the event type or the event state key then the tuple is discarded. // Returns an error if there was a problem talking to the database. -func (v StateResolution) stringTuplesToNumericTuples( +func (v *StateResolution) stringTuplesToNumericTuples( ctx context.Context, stringTuples []gomatrixserverlib.StateKeyTuple, ) ([]types.StateKeyTuple, error) { @@ -292,7 +294,7 @@ func (v StateResolution) stringTuplesToNumericTuples( // If there is no entry for a given event type and state key pair then it will be discarded. // This is typically the state before an event or the current state of a room. // Returns a sorted list of state entries or an error if there was a problem talking to the database. -func (v StateResolution) loadStateAtSnapshotForNumericTuples( +func (v *StateResolution) loadStateAtSnapshotForNumericTuples( ctx context.Context, stateNID types.StateSnapshotNID, stateKeyTuples []types.StateKeyTuple, @@ -340,7 +342,7 @@ func (v StateResolution) loadStateAtSnapshotForNumericTuples( // If there is no entry for a given event type and state key pair then it will be discarded. // This is typically the state before an event. // Returns a sorted list of state entries or an error if there was a problem talking to the database. -func (v StateResolution) LoadStateAfterEventsForStringTuples( +func (v *StateResolution) LoadStateAfterEventsForStringTuples( ctx context.Context, prevStates []types.StateAtEvent, stateKeyTuples []gomatrixserverlib.StateKeyTuple, @@ -352,7 +354,7 @@ func (v StateResolution) LoadStateAfterEventsForStringTuples( return v.loadStateAfterEventsForNumericTuples(ctx, prevStates, numericTuples) } -func (v StateResolution) loadStateAfterEventsForNumericTuples( +func (v *StateResolution) loadStateAfterEventsForNumericTuples( ctx context.Context, prevStates []types.StateAtEvent, stateKeyTuples []types.StateKeyTuple, @@ -520,7 +522,7 @@ func init() { // CalculateAndStoreStateBeforeEvent calculates a snapshot of the state of a room before an event. // Stores the snapshot of the state in the database. // Returns a numeric ID for the snapshot of the state before the event. -func (v StateResolution) CalculateAndStoreStateBeforeEvent( +func (v *StateResolution) CalculateAndStoreStateBeforeEvent( ctx context.Context, event *gomatrixserverlib.Event, isRejected bool, @@ -537,7 +539,7 @@ func (v StateResolution) CalculateAndStoreStateBeforeEvent( // CalculateAndStoreStateAfterEvents finds the room state after the given events. // Stores the resulting state in the database and returns a numeric ID for that snapshot. -func (v StateResolution) CalculateAndStoreStateAfterEvents( +func (v *StateResolution) CalculateAndStoreStateAfterEvents( ctx context.Context, prevStates []types.StateAtEvent, ) (types.StateSnapshotNID, error) { @@ -607,7 +609,7 @@ const maxStateBlockNIDs = 64 // calculateAndStoreStateAfterManyEvents finds the room state after the given events. // This handles the slow path of calculateAndStoreStateAfterEvents for when there is more than one event. // Stores the resulting state and returns a numeric ID for the snapshot. -func (v StateResolution) calculateAndStoreStateAfterManyEvents( +func (v *StateResolution) calculateAndStoreStateAfterManyEvents( ctx context.Context, roomNID types.RoomNID, prevStates []types.StateAtEvent, @@ -627,7 +629,7 @@ func (v StateResolution) calculateAndStoreStateAfterManyEvents( return metrics.stop(v.db.AddState(ctx, roomNID, nil, state)) } -func (v StateResolution) calculateStateAfterManyEvents( +func (v *StateResolution) calculateStateAfterManyEvents( ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, prevStates []types.StateAtEvent, ) (state []types.StateEntry, algorithm string, conflictLength int, err error) { @@ -754,7 +756,7 @@ func ResolveConflictsAdhoc( return resolved, nil } -func (v StateResolution) resolveConflicts( +func (v *StateResolution) resolveConflicts( ctx context.Context, version gomatrixserverlib.RoomVersion, notConflicted, conflicted []types.StateEntry, ) ([]types.StateEntry, error) { @@ -778,7 +780,7 @@ func (v StateResolution) resolveConflicts( // Returns a list that combines the entries without conflicts with the result of state resolution for the entries with conflicts. // The returned list is sorted by state key tuple. // Returns an error if there was a problem talking to the database. -func (v StateResolution) resolveConflictsV1( +func (v *StateResolution) resolveConflictsV1( ctx context.Context, notConflicted, conflicted []types.StateEntry, ) ([]types.StateEntry, error) { @@ -842,7 +844,7 @@ func (v StateResolution) resolveConflictsV1( // The returned list is sorted by state key tuple. // Returns an error if there was a problem talking to the database. // nolint:gocyclo -func (v StateResolution) resolveConflictsV2( +func (v *StateResolution) resolveConflictsV2( ctx context.Context, notConflicted, conflicted []types.StateEntry, ) ([]types.StateEntry, error) { @@ -959,7 +961,7 @@ func (v StateResolution) resolveConflictsV2( } // stateKeyTuplesNeeded works out which numeric state key tuples we need to authenticate some events. -func (v StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.EventStateKeyNID, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKeyTuple { +func (v *StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.EventStateKeyNID, stateNeeded gomatrixserverlib.StateNeeded) []types.StateKeyTuple { var keyTuples []types.StateKeyTuple if stateNeeded.Create { keyTuples = append(keyTuples, types.StateKeyTuple{ @@ -1004,26 +1006,33 @@ func (v StateResolution) stateKeyTuplesNeeded(stateKeyNIDMap map[string]types.Ev // Returns a list of state events in no particular order and a map from string event ID back to state entry. // The map can be used to recover which numeric state entry a given event is for. // Returns an error if there was a problem talking to the database. -func (v StateResolution) loadStateEvents( +func (v *StateResolution) loadStateEvents( ctx context.Context, entries []types.StateEntry, ) ([]*gomatrixserverlib.Event, map[string]types.StateEntry, error) { - eventNIDs := make([]types.EventNID, len(entries)) - for i := range entries { - eventNIDs[i] = entries[i].EventNID + result := make([]*gomatrixserverlib.Event, 0, len(entries)) + eventEntries := make([]types.StateEntry, 0, len(entries)) + eventNIDs := make([]types.EventNID, 0, len(entries)) + for _, entry := range entries { + if e, ok := v.events[entry.EventNID]; ok { + result = append(result, e) + } else { + eventEntries = append(eventEntries, entry) + eventNIDs = append(eventNIDs, entry.EventNID) + } } events, err := v.db.Events(ctx, eventNIDs) if err != nil { return nil, nil, err } eventIDMap := map[string]types.StateEntry{} - result := make([]*gomatrixserverlib.Event, len(entries)) - for i := range entries { - event, ok := eventMap(events).lookup(entries[i].EventNID) + for _, entry := range eventEntries { + event, ok := eventMap(events).lookup(entry.EventNID) if !ok { - panic(fmt.Errorf("Corrupt DB: Missing event numeric ID %d", entries[i].EventNID)) + panic(fmt.Errorf("Corrupt DB: Missing event numeric ID %d", entry.EventNID)) } - result[i] = event.Event - eventIDMap[event.Event.EventID()] = entries[i] + result = append(result, event.Event) + eventIDMap[event.Event.EventID()] = entry + v.events[entry.EventNID] = event.Event } return result, eventIDMap, nil } From 9f443317bc578e1897c7eab9b4911f952f39fdbc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 26 Jan 2021 12:56:20 +0000 Subject: [PATCH 15/33] Graceful shutdowns (#1734) * Initial graceful stop * Fix dendritejs * Use process context for outbound federation requests in destination queues * Reduce logging * Fix log level --- appservice/appservice.go | 2 +- appservice/consumers/roomserver.go | 3 + build/gobind/monolith.go | 1 + cmd/dendrite-demo-libp2p/main.go | 3 +- cmd/dendrite-demo-yggdrasil/main.go | 4 +- cmd/dendrite-monolith-server/main.go | 3 +- cmd/dendrite-polylith-multi/main.go | 3 +- .../personalities/syncapi.go | 1 + cmd/dendritejs/main.go | 1 + federationsender/consumers/eduserver.go | 5 ++ federationsender/consumers/keychange.go | 3 + federationsender/consumers/roomserver.go | 3 + federationsender/federationsender.go | 9 +-- federationsender/queue/destinationqueue.go | 4 +- federationsender/queue/queue.go | 5 ++ internal/consumers.go | 14 ++++ setup/base.go | 64 +++++++++++++++++-- setup/monolith.go | 5 +- setup/process/process.go | 45 +++++++++++++ syncapi/consumers/clientapi.go | 4 +- syncapi/consumers/eduserver_receipts.go | 3 + syncapi/consumers/eduserver_sendtodevice.go | 3 + syncapi/consumers/eduserver_typing.go | 3 + syncapi/consumers/keychange.go | 3 + syncapi/consumers/roomserver.go | 3 + syncapi/syncapi.go | 14 ++-- 26 files changed, 187 insertions(+), 24 deletions(-) create mode 100644 setup/process/process.go diff --git a/appservice/appservice.go b/appservice/appservice.go index 7a438041a..d783c7eb7 100644 --- a/appservice/appservice.go +++ b/appservice/appservice.go @@ -89,7 +89,7 @@ func NewInternalAPI( // We can't add ASes at runtime so this is safe to do. if len(workerStates) > 0 { consumer := consumers.NewOutputRoomEventConsumer( - base.Cfg, consumer, appserviceDB, + base.ProcessContext, base.Cfg, consumer, appserviceDB, rsAPI, workerStates, ) if err := consumer.Start(); err != nil { diff --git a/appservice/consumers/roomserver.go b/appservice/consumers/roomserver.go index 0b251d43d..5cbffa353 100644 --- a/appservice/consumers/roomserver.go +++ b/appservice/consumers/roomserver.go @@ -23,6 +23,7 @@ import ( "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrixserverlib" "github.com/Shopify/sarama" @@ -41,6 +42,7 @@ type OutputRoomEventConsumer struct { // NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call // Start() to begin consuming from room servers. func NewOutputRoomEventConsumer( + process *process.ProcessContext, cfg *config.Dendrite, kafkaConsumer sarama.Consumer, appserviceDB storage.Database, @@ -48,6 +50,7 @@ func NewOutputRoomEventConsumer( workerStates []types.ApplicationServiceWorkerState, ) *OutputRoomEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "appservice/roomserver", Topic: cfg.Global.Kafka.TopicFor(config.TopicOutputRoomEvent), Consumer: kafkaConsumer, diff --git a/build/gobind/monolith.go b/build/gobind/monolith.go index 8cd5cb8ba..332d156bd 100644 --- a/build/gobind/monolith.go +++ b/build/gobind/monolith.go @@ -166,6 +166,7 @@ func (m *DendriteMonolith) Start() { ), } monolith.AddAllPublicRoutes( + base.ProcessContext, base.PublicClientAPIMux, base.PublicFederationAPIMux, base.PublicKeyAPIMux, diff --git a/cmd/dendrite-demo-libp2p/main.go b/cmd/dendrite-demo-libp2p/main.go index 31e7739a9..0610ec777 100644 --- a/cmd/dendrite-demo-libp2p/main.go +++ b/cmd/dendrite-demo-libp2p/main.go @@ -192,6 +192,7 @@ func main() { ExtPublicRoomsProvider: provider, } monolith.AddAllPublicRoutes( + base.Base.ProcessContext, base.Base.PublicClientAPIMux, base.Base.PublicFederationAPIMux, base.Base.PublicKeyAPIMux, @@ -234,5 +235,5 @@ func main() { } // We want to block forever to let the HTTP and HTTPS handler serve the APIs - select {} + base.Base.WaitForShutdown() } diff --git a/cmd/dendrite-demo-yggdrasil/main.go b/cmd/dendrite-demo-yggdrasil/main.go index aea6f7c48..8091298bd 100644 --- a/cmd/dendrite-demo-yggdrasil/main.go +++ b/cmd/dendrite-demo-yggdrasil/main.go @@ -150,6 +150,7 @@ func main() { ), } monolith.AddAllPublicRoutes( + base.ProcessContext, base.PublicClientAPIMux, base.PublicFederationAPIMux, base.PublicKeyAPIMux, @@ -200,5 +201,6 @@ func main() { } }() - select {} + // We want to block forever to let the HTTP and HTTPS handler serve the APIs + base.WaitForShutdown() } diff --git a/cmd/dendrite-monolith-server/main.go b/cmd/dendrite-monolith-server/main.go index 55bac6fef..b82f73211 100644 --- a/cmd/dendrite-monolith-server/main.go +++ b/cmd/dendrite-monolith-server/main.go @@ -144,6 +144,7 @@ func main() { KeyAPI: keyAPI, } monolith.AddAllPublicRoutes( + base.ProcessContext, base.PublicClientAPIMux, base.PublicFederationAPIMux, base.PublicKeyAPIMux, @@ -176,5 +177,5 @@ func main() { } // We want to block forever to let the HTTP and HTTPS handler serve the APIs - select {} + base.WaitForShutdown() } diff --git a/cmd/dendrite-polylith-multi/main.go b/cmd/dendrite-polylith-multi/main.go index 979ab4367..d3c529672 100644 --- a/cmd/dendrite-polylith-multi/main.go +++ b/cmd/dendrite-polylith-multi/main.go @@ -74,5 +74,6 @@ func main() { base := setup.NewBaseDendrite(cfg, component, false) // TODO defer base.Close() // nolint: errcheck - start(base, cfg) + go start(base, cfg) + base.WaitForShutdown() } diff --git a/cmd/dendrite-polylith-multi/personalities/syncapi.go b/cmd/dendrite-polylith-multi/personalities/syncapi.go index 1c33286e2..b9b202294 100644 --- a/cmd/dendrite-polylith-multi/personalities/syncapi.go +++ b/cmd/dendrite-polylith-multi/personalities/syncapi.go @@ -27,6 +27,7 @@ func SyncAPI(base *setup.BaseDendrite, cfg *config.Dendrite) { rsAPI := base.RoomserverHTTPClient() syncapi.AddPublicRoutes( + base.ProcessContext, base.PublicClientAPIMux, userAPI, rsAPI, base.KeyServerHTTPClient(), federation, &cfg.SyncAPI, diff --git a/cmd/dendritejs/main.go b/cmd/dendritejs/main.go index b77d141d8..0dfa46818 100644 --- a/cmd/dendritejs/main.go +++ b/cmd/dendritejs/main.go @@ -231,6 +231,7 @@ func main() { ExtPublicRoomsProvider: p2pPublicRoomProvider, } monolith.AddAllPublicRoutes( + base.ProcessContext, base.PublicClientAPIMux, base.PublicFederationAPIMux, base.PublicKeyAPIMux, diff --git a/federationsender/consumers/eduserver.go b/federationsender/consumers/eduserver.go index 6d11eb88a..639cd7315 100644 --- a/federationsender/consumers/eduserver.go +++ b/federationsender/consumers/eduserver.go @@ -25,6 +25,7 @@ import ( "github.com/matrix-org/dendrite/federationsender/storage" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" log "github.com/sirupsen/logrus" @@ -44,6 +45,7 @@ type OutputEDUConsumer struct { // NewOutputEDUConsumer creates a new OutputEDUConsumer. Call Start() to begin consuming from EDU servers. func NewOutputEDUConsumer( + process *process.ProcessContext, cfg *config.FederationSender, kafkaConsumer sarama.Consumer, queues *queue.OutgoingQueues, @@ -51,18 +53,21 @@ func NewOutputEDUConsumer( ) *OutputEDUConsumer { c := &OutputEDUConsumer{ typingConsumer: &internal.ContinualConsumer{ + Process: process, ComponentName: "eduserver/typing", Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputTypingEvent), Consumer: kafkaConsumer, PartitionStore: store, }, sendToDeviceConsumer: &internal.ContinualConsumer{ + Process: process, ComponentName: "eduserver/sendtodevice", Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputSendToDeviceEvent), Consumer: kafkaConsumer, PartitionStore: store, }, receiptConsumer: &internal.ContinualConsumer{ + Process: process, ComponentName: "eduserver/receipt", Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputReceiptEvent), Consumer: kafkaConsumer, diff --git a/federationsender/consumers/keychange.go b/federationsender/consumers/keychange.go index 5006ac28d..9e146390a 100644 --- a/federationsender/consumers/keychange.go +++ b/federationsender/consumers/keychange.go @@ -26,6 +26,7 @@ import ( "github.com/matrix-org/dendrite/keyserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrixserverlib" log "github.com/sirupsen/logrus" ) @@ -41,6 +42,7 @@ type KeyChangeConsumer struct { // NewKeyChangeConsumer creates a new KeyChangeConsumer. Call Start() to begin consuming from key servers. func NewKeyChangeConsumer( + process *process.ProcessContext, cfg *config.KeyServer, kafkaConsumer sarama.Consumer, queues *queue.OutgoingQueues, @@ -49,6 +51,7 @@ func NewKeyChangeConsumer( ) *KeyChangeConsumer { c := &KeyChangeConsumer{ consumer: &internal.ContinualConsumer{ + Process: process, ComponentName: "federationsender/keychange", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputKeyChangeEvent)), Consumer: kafkaConsumer, diff --git a/federationsender/consumers/roomserver.go b/federationsender/consumers/roomserver.go index 846468fa6..f9c4a5c27 100644 --- a/federationsender/consumers/roomserver.go +++ b/federationsender/consumers/roomserver.go @@ -26,6 +26,7 @@ import ( "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrixserverlib" log "github.com/sirupsen/logrus" ) @@ -41,6 +42,7 @@ type OutputRoomEventConsumer struct { // NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers. func NewOutputRoomEventConsumer( + process *process.ProcessContext, cfg *config.FederationSender, kafkaConsumer sarama.Consumer, queues *queue.OutgoingQueues, @@ -48,6 +50,7 @@ func NewOutputRoomEventConsumer( rsAPI api.RoomserverInternalAPI, ) *OutputRoomEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "federationsender/roomserver", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputRoomEvent)), Consumer: kafkaConsumer, diff --git a/federationsender/federationsender.go b/federationsender/federationsender.go index a24e0f488..9aab91d48 100644 --- a/federationsender/federationsender.go +++ b/federationsender/federationsender.go @@ -59,7 +59,8 @@ func NewInternalAPI( consumer, _ := kafka.SetupConsumerProducer(&cfg.Matrix.Kafka) queues := queue.NewOutgoingQueues( - federationSenderDB, cfg.Matrix.DisableFederation, + federationSenderDB, base.ProcessContext, + cfg.Matrix.DisableFederation, cfg.Matrix.ServerName, federation, rsAPI, stats, &queue.SigningInfo{ KeyID: cfg.Matrix.KeyID, @@ -69,7 +70,7 @@ func NewInternalAPI( ) rsConsumer := consumers.NewOutputRoomEventConsumer( - cfg, consumer, queues, + base.ProcessContext, cfg, consumer, queues, federationSenderDB, rsAPI, ) if err = rsConsumer.Start(); err != nil { @@ -77,13 +78,13 @@ func NewInternalAPI( } tsConsumer := consumers.NewOutputEDUConsumer( - cfg, consumer, queues, federationSenderDB, + base.ProcessContext, cfg, consumer, queues, federationSenderDB, ) if err := tsConsumer.Start(); err != nil { logrus.WithError(err).Panic("failed to start typing server consumer") } keyConsumer := consumers.NewKeyChangeConsumer( - &base.Cfg.KeyServer, consumer, queues, federationSenderDB, rsAPI, + base.ProcessContext, &base.Cfg.KeyServer, consumer, queues, federationSenderDB, rsAPI, ) if err := keyConsumer.Start(); err != nil { logrus.WithError(err).Panic("failed to start key server consumer") diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index c8b0bf658..99b9e4497 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -25,6 +25,7 @@ import ( "github.com/matrix-org/dendrite/federationsender/storage" "github.com/matrix-org/dendrite/federationsender/storage/shared" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrix" "github.com/matrix-org/gomatrixserverlib" "github.com/sirupsen/logrus" @@ -46,6 +47,7 @@ const ( // at a time. type destinationQueue struct { db storage.Database + process *process.ProcessContext signing *SigningInfo rsAPI api.RoomserverInternalAPI client *gomatrixserverlib.FederationClient // federation client @@ -411,7 +413,7 @@ func (oq *destinationQueue) nextTransaction( // TODO: we should check for 500-ish fails vs 400-ish here, // since we shouldn't queue things indefinitely in response // to a 400-ish error - ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) + ctx, cancel := context.WithTimeout(oq.process.Context(), time.Minute*5) defer cancel() _, err := oq.client.SendTransaction(ctx, t) switch err.(type) { diff --git a/federationsender/queue/queue.go b/federationsender/queue/queue.go index 8054856e3..4453ddb01 100644 --- a/federationsender/queue/queue.go +++ b/federationsender/queue/queue.go @@ -26,6 +26,7 @@ import ( "github.com/matrix-org/dendrite/federationsender/storage" "github.com/matrix-org/dendrite/federationsender/storage/shared" "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/gomatrixserverlib" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" @@ -36,6 +37,7 @@ import ( // matrix servers type OutgoingQueues struct { db storage.Database + process *process.ProcessContext disabled bool rsAPI api.RoomserverInternalAPI origin gomatrixserverlib.ServerName @@ -80,6 +82,7 @@ var destinationQueueBackingOff = prometheus.NewGauge( // NewOutgoingQueues makes a new OutgoingQueues func NewOutgoingQueues( db storage.Database, + process *process.ProcessContext, disabled bool, origin gomatrixserverlib.ServerName, client *gomatrixserverlib.FederationClient, @@ -89,6 +92,7 @@ func NewOutgoingQueues( ) *OutgoingQueues { queues := &OutgoingQueues{ disabled: disabled, + process: process, db: db, rsAPI: rsAPI, origin: origin, @@ -151,6 +155,7 @@ func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *d destinationQueueTotal.Inc() oq = &destinationQueue{ db: oqs.db, + process: oqs.process, rsAPI: oqs.rsAPI, origin: oqs.origin, destination: destination, diff --git a/internal/consumers.go b/internal/consumers.go index 807cf5899..3a4e0b7f8 100644 --- a/internal/consumers.go +++ b/internal/consumers.go @@ -20,6 +20,8 @@ import ( "github.com/Shopify/sarama" "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/matrix-org/dendrite/setup/process" + "github.com/sirupsen/logrus" ) // A PartitionStorer has the storage APIs needed by the consumer. @@ -33,6 +35,9 @@ type PartitionStorer interface { // A ContinualConsumer continually consumes logs even across restarts. It requires a PartitionStorer to // remember the offset it reached. type ContinualConsumer struct { + // The parent context for the listener, stop consuming when this context is done + Process *process.ProcessContext + // The component name ComponentName string // The kafkaesque topic to consume events from. // This is the name used in kafka to identify the stream to consume events from. @@ -100,6 +105,15 @@ func (c *ContinualConsumer) StartOffsets() ([]sqlutil.PartitionOffset, error) { } for _, pc := range partitionConsumers { go c.consumePartition(pc) + if c.Process != nil { + c.Process.ComponentStarted() + go func(pc sarama.PartitionConsumer) { + <-c.Process.WaitForShutdown() + _ = pc.Close() + c.Process.ComponentFinished() + logrus.Infof("Stopped consumer for %q topic %q", c.ComponentName, c.Topic) + }(pc) + } } return storedOffsets, nil diff --git a/setup/base.go b/setup/base.go index c30e69105..6522426cd 100644 --- a/setup/base.go +++ b/setup/base.go @@ -15,22 +15,28 @@ package setup import ( + "context" "crypto/tls" "fmt" "io" "net" "net/http" "net/url" + "os" + "os/signal" + "syscall" "time" "github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/httputil" "github.com/matrix-org/gomatrixserverlib" "github.com/prometheus/client_golang/prometheus/promhttp" + "go.uber.org/atomic" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "github.com/matrix-org/dendrite/internal" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/userapi/storage/accounts" "github.com/gorilla/mux" @@ -61,6 +67,7 @@ import ( // should only be used during start up. // Must be closed when shutting down. type BaseDendrite struct { + *process.ProcessContext componentName string tracerCloser io.Closer PublicClientAPIMux *mux.Router @@ -161,7 +168,9 @@ func NewBaseDendrite(cfg *config.Dendrite, componentName string, useHTTPAPIs boo // We need to be careful with media APIs if they read from a filesystem to make sure they // are not inadvertently reading paths without cleaning, else this could introduce a // directory traversal attack e.g /../../../etc/passwd + return &BaseDendrite{ + ProcessContext: process.NewProcessContext(), componentName: componentName, UseHTTPAPIs: useHTTPAPIs, tracerCloser: closer, @@ -354,14 +363,26 @@ func (b *BaseDendrite) SetupAndServeHTTP( if internalAddr != NoListener && internalAddr != externalAddr { go func() { + var internalShutdown atomic.Bool // RegisterOnShutdown can be called more than once logrus.Infof("Starting internal %s listener on %s", b.componentName, internalServ.Addr) + b.ProcessContext.ComponentStarted() + internalServ.RegisterOnShutdown(func() { + if internalShutdown.CAS(false, true) { + b.ProcessContext.ComponentFinished() + logrus.Infof("Stopped internal HTTP listener") + } + }) if certFile != nil && keyFile != nil { if err := internalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTPS") + if err != http.ErrServerClosed { + logrus.WithError(err).Fatal("failed to serve HTTPS") + } } } else { if err := internalServ.ListenAndServe(); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTP") + if err != http.ErrServerClosed { + logrus.WithError(err).Fatal("failed to serve HTTP") + } } } logrus.Infof("Stopped internal %s listener on %s", b.componentName, internalServ.Addr) @@ -370,19 +391,52 @@ func (b *BaseDendrite) SetupAndServeHTTP( if externalAddr != NoListener { go func() { + var externalShutdown atomic.Bool // RegisterOnShutdown can be called more than once logrus.Infof("Starting external %s listener on %s", b.componentName, externalServ.Addr) + b.ProcessContext.ComponentStarted() + externalServ.RegisterOnShutdown(func() { + if externalShutdown.CAS(false, true) { + b.ProcessContext.ComponentFinished() + logrus.Infof("Stopped external HTTP listener") + } + }) if certFile != nil && keyFile != nil { if err := externalServ.ListenAndServeTLS(*certFile, *keyFile); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTPS") + if err != http.ErrServerClosed { + logrus.WithError(err).Fatal("failed to serve HTTPS") + } } } else { if err := externalServ.ListenAndServe(); err != nil { - logrus.WithError(err).Fatal("failed to serve HTTP") + if err != http.ErrServerClosed { + logrus.WithError(err).Fatal("failed to serve HTTP") + } } } logrus.Infof("Stopped external %s listener on %s", b.componentName, externalServ.Addr) }() } - select {} + <-b.ProcessContext.WaitForShutdown() + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _ = internalServ.Shutdown(ctx) + _ = externalServ.Shutdown(ctx) + logrus.Infof("Stopped HTTP listeners") +} + +func (b *BaseDendrite) WaitForShutdown() { + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + signal.Reset(syscall.SIGINT, syscall.SIGTERM) + + logrus.Warnf("Shutdown signal received") + + b.ProcessContext.ShutdownDendrite() + b.ProcessContext.WaitForComponentsToFinish() + + logrus.Warnf("Dendrite is exiting now") } diff --git a/setup/monolith.go b/setup/monolith.go index fd84ef538..a740ebb7f 100644 --- a/setup/monolith.go +++ b/setup/monolith.go @@ -27,6 +27,7 @@ import ( "github.com/matrix-org/dendrite/mediaapi" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" serverKeyAPI "github.com/matrix-org/dendrite/signingkeyserver/api" "github.com/matrix-org/dendrite/syncapi" userapi "github.com/matrix-org/dendrite/userapi/api" @@ -56,7 +57,7 @@ type Monolith struct { } // AddAllPublicRoutes attaches all public paths to the given router -func (m *Monolith) AddAllPublicRoutes(csMux, ssMux, keyMux, mediaMux *mux.Router) { +func (m *Monolith) AddAllPublicRoutes(process *process.ProcessContext, csMux, ssMux, keyMux, mediaMux *mux.Router) { clientapi.AddPublicRoutes( csMux, &m.Config.ClientAPI, m.AccountDB, m.FedClient, m.RoomserverAPI, @@ -71,7 +72,7 @@ func (m *Monolith) AddAllPublicRoutes(csMux, ssMux, keyMux, mediaMux *mux.Router ) mediaapi.AddPublicRoutes(mediaMux, &m.Config.MediaAPI, m.UserAPI, m.Client) syncapi.AddPublicRoutes( - csMux, m.UserAPI, m.RoomserverAPI, + process, csMux, m.UserAPI, m.RoomserverAPI, m.KeyAPI, m.FedClient, &m.Config.SyncAPI, ) } diff --git a/setup/process/process.go b/setup/process/process.go new file mode 100644 index 000000000..d55751d77 --- /dev/null +++ b/setup/process/process.go @@ -0,0 +1,45 @@ +package process + +import ( + "context" + "sync" +) + +type ProcessContext struct { + wg *sync.WaitGroup // used to wait for components to shutdown + ctx context.Context // cancelled when Stop is called + shutdown context.CancelFunc // shut down Dendrite +} + +func NewProcessContext() *ProcessContext { + ctx, shutdown := context.WithCancel(context.Background()) + return &ProcessContext{ + ctx: ctx, + shutdown: shutdown, + wg: &sync.WaitGroup{}, + } +} + +func (b *ProcessContext) Context() context.Context { + return context.WithValue(b.ctx, "scope", "process") // nolint:staticcheck +} + +func (b *ProcessContext) ComponentStarted() { + b.wg.Add(1) +} + +func (b *ProcessContext) ComponentFinished() { + b.wg.Done() +} + +func (b *ProcessContext) ShutdownDendrite() { + b.shutdown() +} + +func (b *ProcessContext) WaitForShutdown() <-chan struct{} { + return b.ctx.Done() +} + +func (b *ProcessContext) WaitForComponentsToFinish() { + b.wg.Wait() +} diff --git a/syncapi/consumers/clientapi.go b/syncapi/consumers/clientapi.go index 4958f2216..8dab513c3 100644 --- a/syncapi/consumers/clientapi.go +++ b/syncapi/consumers/clientapi.go @@ -22,6 +22,7 @@ import ( "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -38,14 +39,15 @@ type OutputClientDataConsumer struct { // NewOutputClientDataConsumer creates a new OutputClientData consumer. Call Start() to begin consuming from room servers. func NewOutputClientDataConsumer( + process *process.ProcessContext, cfg *config.SyncAPI, kafkaConsumer sarama.Consumer, store storage.Database, notifier *notifier.Notifier, stream types.StreamProvider, ) *OutputClientDataConsumer { - consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/clientapi", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputClientData)), Consumer: kafkaConsumer, diff --git a/syncapi/consumers/eduserver_receipts.go b/syncapi/consumers/eduserver_receipts.go index bd538eff2..598908070 100644 --- a/syncapi/consumers/eduserver_receipts.go +++ b/syncapi/consumers/eduserver_receipts.go @@ -22,6 +22,7 @@ import ( "github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -39,6 +40,7 @@ type OutputReceiptEventConsumer struct { // NewOutputReceiptEventConsumer creates a new OutputReceiptEventConsumer. // Call Start() to begin consuming from the EDU server. func NewOutputReceiptEventConsumer( + process *process.ProcessContext, cfg *config.SyncAPI, kafkaConsumer sarama.Consumer, store storage.Database, @@ -47,6 +49,7 @@ func NewOutputReceiptEventConsumer( ) *OutputReceiptEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/eduserver/receipt", Topic: cfg.Matrix.Kafka.TopicFor(config.TopicOutputReceiptEvent), Consumer: kafkaConsumer, diff --git a/syncapi/consumers/eduserver_sendtodevice.go b/syncapi/consumers/eduserver_sendtodevice.go index 6e774b5b4..668d30784 100644 --- a/syncapi/consumers/eduserver_sendtodevice.go +++ b/syncapi/consumers/eduserver_sendtodevice.go @@ -22,6 +22,7 @@ import ( "github.com/matrix-org/dendrite/eduserver/api" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -42,6 +43,7 @@ type OutputSendToDeviceEventConsumer struct { // NewOutputSendToDeviceEventConsumer creates a new OutputSendToDeviceEventConsumer. // Call Start() to begin consuming from the EDU server. func NewOutputSendToDeviceEventConsumer( + process *process.ProcessContext, cfg *config.SyncAPI, kafkaConsumer sarama.Consumer, store storage.Database, @@ -50,6 +52,7 @@ func NewOutputSendToDeviceEventConsumer( ) *OutputSendToDeviceEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/eduserver/sendtodevice", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputSendToDeviceEvent)), Consumer: kafkaConsumer, diff --git a/syncapi/consumers/eduserver_typing.go b/syncapi/consumers/eduserver_typing.go index 3edf6675d..7d7ab3bfb 100644 --- a/syncapi/consumers/eduserver_typing.go +++ b/syncapi/consumers/eduserver_typing.go @@ -22,6 +22,7 @@ import ( "github.com/matrix-org/dendrite/eduserver/cache" "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -39,6 +40,7 @@ type OutputTypingEventConsumer struct { // NewOutputTypingEventConsumer creates a new OutputTypingEventConsumer. // Call Start() to begin consuming from the EDU server. func NewOutputTypingEventConsumer( + process *process.ProcessContext, cfg *config.SyncAPI, kafkaConsumer sarama.Consumer, store storage.Database, @@ -48,6 +50,7 @@ func NewOutputTypingEventConsumer( ) *OutputTypingEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/eduserver/typing", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputTypingEvent)), Consumer: kafkaConsumer, diff --git a/syncapi/consumers/keychange.go b/syncapi/consumers/keychange.go index af7b280fa..0e1a790d0 100644 --- a/syncapi/consumers/keychange.go +++ b/syncapi/consumers/keychange.go @@ -23,6 +23,7 @@ import ( "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/keyserver/api" roomserverAPI "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -46,6 +47,7 @@ type OutputKeyChangeEventConsumer struct { // NewOutputKeyChangeEventConsumer creates a new OutputKeyChangeEventConsumer. // Call Start() to begin consuming from the key server. func NewOutputKeyChangeEventConsumer( + process *process.ProcessContext, serverName gomatrixserverlib.ServerName, topic string, kafkaConsumer sarama.Consumer, @@ -57,6 +59,7 @@ func NewOutputKeyChangeEventConsumer( ) *OutputKeyChangeEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/keychange", Topic: topic, Consumer: kafkaConsumer, diff --git a/syncapi/consumers/roomserver.go b/syncapi/consumers/roomserver.go index a8cc5f710..85e73df62 100644 --- a/syncapi/consumers/roomserver.go +++ b/syncapi/consumers/roomserver.go @@ -23,6 +23,7 @@ import ( "github.com/matrix-org/dendrite/internal" "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" + "github.com/matrix-org/dendrite/setup/process" "github.com/matrix-org/dendrite/syncapi/notifier" "github.com/matrix-org/dendrite/syncapi/storage" "github.com/matrix-org/dendrite/syncapi/types" @@ -43,6 +44,7 @@ type OutputRoomEventConsumer struct { // NewOutputRoomEventConsumer creates a new OutputRoomEventConsumer. Call Start() to begin consuming from room servers. func NewOutputRoomEventConsumer( + process *process.ProcessContext, cfg *config.SyncAPI, kafkaConsumer sarama.Consumer, store storage.Database, @@ -53,6 +55,7 @@ func NewOutputRoomEventConsumer( ) *OutputRoomEventConsumer { consumer := internal.ContinualConsumer{ + Process: process, ComponentName: "syncapi/roomserver", Topic: string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputRoomEvent)), Consumer: kafkaConsumer, diff --git a/syncapi/syncapi.go b/syncapi/syncapi.go index 4a09940d9..84c7140ca 100644 --- a/syncapi/syncapi.go +++ b/syncapi/syncapi.go @@ -25,6 +25,7 @@ import ( "github.com/matrix-org/dendrite/roomserver/api" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/dendrite/setup/kafka" + "github.com/matrix-org/dendrite/setup/process" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" @@ -39,6 +40,7 @@ import ( // AddPublicRoutes sets up and registers HTTP handlers for the SyncAPI // component. func AddPublicRoutes( + process *process.ProcessContext, router *mux.Router, userAPI userapi.UserInternalAPI, rsAPI api.RoomserverInternalAPI, @@ -63,7 +65,7 @@ func AddPublicRoutes( requestPool := sync.NewRequestPool(syncDB, cfg, userAPI, keyAPI, rsAPI, streams, notifier) keyChangeConsumer := consumers.NewOutputKeyChangeEventConsumer( - cfg.Matrix.ServerName, string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputKeyChangeEvent)), + process, cfg.Matrix.ServerName, string(cfg.Matrix.Kafka.TopicFor(config.TopicOutputKeyChangeEvent)), consumer, keyAPI, rsAPI, syncDB, notifier, streams.DeviceListStreamProvider, ) if err = keyChangeConsumer.Start(); err != nil { @@ -71,7 +73,7 @@ func AddPublicRoutes( } roomConsumer := consumers.NewOutputRoomEventConsumer( - cfg, consumer, syncDB, notifier, streams.PDUStreamProvider, + process, cfg, consumer, syncDB, notifier, streams.PDUStreamProvider, streams.InviteStreamProvider, rsAPI, ) if err = roomConsumer.Start(); err != nil { @@ -79,28 +81,28 @@ func AddPublicRoutes( } clientConsumer := consumers.NewOutputClientDataConsumer( - cfg, consumer, syncDB, notifier, streams.AccountDataStreamProvider, + process, cfg, consumer, syncDB, notifier, streams.AccountDataStreamProvider, ) if err = clientConsumer.Start(); err != nil { logrus.WithError(err).Panicf("failed to start client data consumer") } typingConsumer := consumers.NewOutputTypingEventConsumer( - cfg, consumer, syncDB, eduCache, notifier, streams.TypingStreamProvider, + process, cfg, consumer, syncDB, eduCache, notifier, streams.TypingStreamProvider, ) if err = typingConsumer.Start(); err != nil { logrus.WithError(err).Panicf("failed to start typing consumer") } sendToDeviceConsumer := consumers.NewOutputSendToDeviceEventConsumer( - cfg, consumer, syncDB, notifier, streams.SendToDeviceStreamProvider, + process, cfg, consumer, syncDB, notifier, streams.SendToDeviceStreamProvider, ) if err = sendToDeviceConsumer.Start(); err != nil { logrus.WithError(err).Panicf("failed to start send-to-device consumer") } receiptConsumer := consumers.NewOutputReceiptEventConsumer( - cfg, consumer, syncDB, notifier, streams.ReceiptStreamProvider, + process, cfg, consumer, syncDB, notifier, streams.ReceiptStreamProvider, ) if err = receiptConsumer.Start(); err != nil { logrus.WithError(err).Panicf("failed to start receipts consumer") From e14e9a16067111d4c4e6e5f3ea0d6dcf8fb52e51 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 26 Jan 2021 13:13:48 +0000 Subject: [PATCH 16/33] Version 0.3.7 --- CHANGES.md | 14 ++++++++++++++ internal/version.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a91dea644..6d5c0fcb6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,19 @@ # Changelog +## Dendrite 0.3.7 (2021-01-26) + +### Features + +* Sync filtering support (for event types, senders and limits) +* In-process DNS caching support for deployments where a local DNS caching resolver is not available (disabled by default) +* Experimental support for MSC2444 (Peeking over Federation) has been merged +* Experimental federation support for MSC2946 (Spaces Summary) has been merged + +### Fixes + +* Dendrite will no longer load a given event more than once for state resolution, which may help to reduce memory usage and database I/O slightly in some cases +* Large well-known responses will no longer use significant amounts of memory + ## Dendrite 0.3.6 (2021-01-18) ### Features diff --git a/internal/version.go b/internal/version.go index bb6d7038b..639fd3df0 100644 --- a/internal/version.go +++ b/internal/version.go @@ -17,7 +17,7 @@ var build string const ( VersionMajor = 0 VersionMinor = 3 - VersionPatch = 6 + VersionPatch = 7 VersionTag = "" // example: "rc1" ) From d56a5a65097572e9388afaec813eaced301f99f8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Jan 2021 13:25:45 +0000 Subject: [PATCH 17/33] Fix well-known lookups --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 891d0f57d..48d0b00d7 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-20210122154608-a38974bd8a37 + github.com/matrix-org/gomatrixserverlib v0.0.0-20210128131744-c803b6ee2b68 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 diff --git a/go.sum b/go.sum index acdfaf2c0..999f13c1f 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +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-20210122154608-a38974bd8a37 h1:si2CZZpwOLWZfDXfgHPkaTlaAkdJvpJzr1zVqyKXd0I= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210122154608-a38974bd8a37/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210128131744-c803b6ee2b68 h1:zZouhQEqn2J0eiRlFwkANVmLXTltpCanEmDdJyPIOhk= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210128131744-c803b6ee2b68/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= From 61090cbe351f4ea9e43a87ce6c7d80565dc62ea4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 28 Jan 2021 13:37:50 +0000 Subject: [PATCH 18/33] Version 0.3.8 --- CHANGES.md | 6 ++++++ internal/version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6d5c0fcb6..b11c3d7ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changelog +## Dendrite 0.3.8 (2021-01-28) + +### Fixes + +* A well-known lookup regression in version 0.3.7 has been fixed + ## Dendrite 0.3.7 (2021-01-26) ### Features diff --git a/internal/version.go b/internal/version.go index 639fd3df0..f5c6a423f 100644 --- a/internal/version.go +++ b/internal/version.go @@ -17,7 +17,7 @@ var build string const ( VersionMajor = 0 VersionMinor = 3 - VersionPatch = 7 + VersionPatch = 8 VersionTag = "" // example: "rc1" ) From 6d1c6f29e05a11830d45f5a229578e91fc012d4b Mon Sep 17 00:00:00 2001 From: Kegsay Date: Fri, 29 Jan 2021 11:36:26 +0000 Subject: [PATCH 19/33] Add m.room.create to invite stripped state (#1740) MSC1772 needs this because the create event contains info on if the room is a space or not. The create event itself isn't sensitive so other people may find this useful too. --- roomserver/internal/perform/perform_invite.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roomserver/internal/perform/perform_invite.go b/roomserver/internal/perform/perform_invite.go index 085cb02ed..93a52350c 100644 --- a/roomserver/internal/perform/perform_invite.go +++ b/roomserver/internal/perform/perform_invite.go @@ -225,7 +225,7 @@ func buildInviteStrippedState( for _, t := range []string{ gomatrixserverlib.MRoomName, gomatrixserverlib.MRoomCanonicalAlias, gomatrixserverlib.MRoomAliases, gomatrixserverlib.MRoomJoinRules, - "m.room.avatar", "m.room.encryption", + "m.room.avatar", "m.room.encryption", gomatrixserverlib.MRoomCreate, } { stateWanted = append(stateWanted, gomatrixserverlib.StateKeyTuple{ EventType: t, From 62a325ded8d6d4fc72553179da81b509179cc342 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Jan 2021 16:32:54 +0000 Subject: [PATCH 20/33] Complete sync performance (#1741) * Parallelise PDU stream fetching for complete sync * Fixes * Fixes * Worker queue * Workers * Don't populate device list changes on complete sync * Don't fast-forward typing notifications either on complete sync * Revert "Don't fast-forward typing notifications either on complete sync" This reverts commit 01471f78431cdd840915111f71bd2b5176e584a8. * Comments --- syncapi/streams/stream_devicelist.go | 2 +- syncapi/streams/stream_pdu.go | 71 +++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/syncapi/streams/stream_devicelist.go b/syncapi/streams/stream_devicelist.go index c43d50a49..9ea9d088f 100644 --- a/syncapi/streams/stream_devicelist.go +++ b/syncapi/streams/stream_devicelist.go @@ -19,7 +19,7 @@ func (p *DeviceListStreamProvider) CompleteSync( ctx context.Context, req *types.SyncRequest, ) types.LogPosition { - return p.IncrementalSync(ctx, req, types.LogPosition{}, p.LatestPosition(ctx)) + return p.LatestPosition(ctx) } func (p *DeviceListStreamProvider) IncrementalSync( diff --git a/syncapi/streams/stream_pdu.go b/syncapi/streams/stream_pdu.go index d6d7ff444..ae38dc30e 100644 --- a/syncapi/streams/stream_pdu.go +++ b/syncapi/streams/stream_pdu.go @@ -2,18 +2,54 @@ package streams import ( "context" + "sync" + "time" "github.com/matrix-org/dendrite/syncapi/types" userapi "github.com/matrix-org/dendrite/userapi/api" "github.com/matrix-org/gomatrixserverlib" + "go.uber.org/atomic" ) +// The max number of per-room goroutines to have running. +// Too high and this will consume lots of CPU, too low and complete +// sync responses will take longer to process. +const PDU_STREAM_WORKERS = 256 + +// The maximum number of tasks that can be queued in total before +// backpressure will build up and the rests will start to block. +const PDU_STREAM_QUEUESIZE = PDU_STREAM_WORKERS * 8 + type PDUStreamProvider struct { StreamProvider + + tasks chan func() + workers atomic.Int32 +} + +func (p *PDUStreamProvider) worker() { + defer p.workers.Dec() + for { + select { + case f := <-p.tasks: + f() + case <-time.After(time.Second * 10): + return + } + } +} + +func (p *PDUStreamProvider) queue(f func()) { + if p.workers.Load() < PDU_STREAM_WORKERS { + p.workers.Inc() + go p.worker() + } + p.tasks <- f } func (p *PDUStreamProvider) Setup() { p.StreamProvider.Setup() + p.tasks = make(chan func(), PDU_STREAM_QUEUESIZE) p.latestMutex.Lock() defer p.latestMutex.Unlock() @@ -52,19 +88,32 @@ func (p *PDUStreamProvider) CompleteSync( eventFilter := req.Filter.Room.Timeline // Build up a /sync response. Add joined rooms. - for _, roomID := range joinedRoomIDs { - var jr *types.JoinResponse - jr, err = p.getJoinResponseForCompleteSync( - ctx, roomID, r, &stateFilter, &eventFilter, req.Device, - ) - if err != nil { - req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") - return from - } - req.Response.Rooms.Join[roomID] = *jr - req.Rooms[roomID] = gomatrixserverlib.Join + var reqMutex sync.Mutex + var reqWaitGroup sync.WaitGroup + reqWaitGroup.Add(len(joinedRoomIDs)) + for _, room := range joinedRoomIDs { + roomID := room + p.queue(func() { + defer reqWaitGroup.Done() + + var jr *types.JoinResponse + jr, err = p.getJoinResponseForCompleteSync( + ctx, roomID, r, &stateFilter, &eventFilter, req.Device, + ) + if err != nil { + req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") + return + } + + reqMutex.Lock() + defer reqMutex.Unlock() + req.Response.Rooms.Join[roomID] = *jr + req.Rooms[roomID] = gomatrixserverlib.Join + }) } + reqWaitGroup.Wait() + // Add peeked rooms. peeks, err := p.DB.PeeksInRange(ctx, req.Device.UserID, req.Device.ID, r) if err != nil { From 369d3939fdf88546dfdaf8c185125c684c55e991 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 29 Jan 2021 16:33:59 +0000 Subject: [PATCH 21/33] Drop state events we can't auth instead of failing altogether --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 48d0b00d7..96aa881c6 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-20210128131744-c803b6ee2b68 + github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead 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 diff --git a/go.sum b/go.sum index 999f13c1f..a6464cc8e 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +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-20210128131744-c803b6ee2b68 h1:zZouhQEqn2J0eiRlFwkANVmLXTltpCanEmDdJyPIOhk= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210128131744-c803b6ee2b68/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead h1:VmGJybKUQin8+NyA9ZkrHJpE8ygXzcON9peQH9LC92c= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead/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= From de5f22a46960308b3fe8efe7ca1813460b2c9c09 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 11:12:52 +0000 Subject: [PATCH 22/33] Remove redundant check (#1748) --- roomserver/internal/input/input_membership.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/roomserver/internal/input/input_membership.go b/roomserver/internal/input/input_membership.go index 692d8147a..bc646c3c6 100644 --- a/roomserver/internal/input/input_membership.go +++ b/roomserver/internal/input/input_membership.go @@ -107,13 +107,6 @@ func (r *Inputer) updateMembership( return updates, nil } - if add == nil { - // This can happen when we have rejoined a room and suddenly we have a - // divergence between the former state and the new one. We don't want to - // act on removals and apparently there are no adds, so stop here. - return updates, nil - } - mu, err := updater.MembershipUpdater(targetUserNID, r.isLocalTarget(add)) if err != nil { return nil, err From b7e3b81a22775697ad8ff463247d83077ea6cbe3 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 11:45:49 +0000 Subject: [PATCH 23/33] Fix ON CONFLICT on sync API account data (#1745) (#1750) --- syncapi/storage/postgres/account_data_table.go | 2 +- syncapi/storage/sqlite3/account_data_table.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/syncapi/storage/postgres/account_data_table.go b/syncapi/storage/postgres/account_data_table.go index 67eb1e863..25bdb1da3 100644 --- a/syncapi/storage/postgres/account_data_table.go +++ b/syncapi/storage/postgres/account_data_table.go @@ -53,7 +53,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS syncapi_account_data_id_idx ON syncapi_account const insertAccountDataSQL = "" + "INSERT INTO syncapi_account_data_type (user_id, room_id, type) VALUES ($1, $2, $3)" + " ON CONFLICT ON CONSTRAINT syncapi_account_data_unique" + - " DO UPDATE SET id = EXCLUDED.id" + + " DO UPDATE SET id = nextval('syncapi_stream_id')" + " RETURNING id" const selectAccountDataInRangeSQL = "" + diff --git a/syncapi/storage/sqlite3/account_data_table.go b/syncapi/storage/sqlite3/account_data_table.go index 1c65cb6a9..24c442240 100644 --- a/syncapi/storage/sqlite3/account_data_table.go +++ b/syncapi/storage/sqlite3/account_data_table.go @@ -39,7 +39,7 @@ CREATE TABLE IF NOT EXISTS syncapi_account_data_type ( const insertAccountDataSQL = "" + "INSERT INTO syncapi_account_data_type (id, user_id, room_id, type) VALUES ($1, $2, $3, $4)" + " ON CONFLICT (user_id, room_id, type) DO UPDATE" + - " SET id = EXCLUDED.id" + " SET id = $5" const selectAccountDataInRangeSQL = "" + "SELECT room_id, type FROM syncapi_account_data_type" + @@ -86,7 +86,7 @@ func (s *accountDataStatements) InsertAccountData( if err != nil { return } - _, err = sqlutil.TxStmt(txn, s.insertAccountDataStmt).ExecContext(ctx, pos, userID, roomID, dataType) + _, err = sqlutil.TxStmt(txn, s.insertAccountDataStmt).ExecContext(ctx, pos, userID, roomID, dataType, pos) return } From 6099379ea48cf47f9570e9bc51aba4bb3fa8c066 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 11:52:49 +0000 Subject: [PATCH 24/33] Remove rooms table from federation sender (#1751) * Remove last sent event ID column from federation sender * Remove EventIDMismatchError * Remove the federationsender rooms table altogether, it's useless * Add migration * Fix migrations * Fix migrations --- .../postgres/deltas/2021020411080000_rooms.go | 46 ++++++++ .../storage/postgres/room_table.go | 104 ----------------- federationsender/storage/postgres/storage.go | 11 +- federationsender/storage/shared/storage.go | 26 +---- .../sqlite3/deltas/2021020411080000_rooms.go | 46 ++++++++ .../storage/sqlite3/room_table.go | 105 ------------------ federationsender/storage/sqlite3/storage.go | 11 +- federationsender/storage/tables/interface.go | 6 - federationsender/types/types.go | 18 --- 9 files changed, 105 insertions(+), 268 deletions(-) create mode 100644 federationsender/storage/postgres/deltas/2021020411080000_rooms.go delete mode 100644 federationsender/storage/postgres/room_table.go create mode 100644 federationsender/storage/sqlite3/deltas/2021020411080000_rooms.go delete mode 100644 federationsender/storage/sqlite3/room_table.go diff --git a/federationsender/storage/postgres/deltas/2021020411080000_rooms.go b/federationsender/storage/postgres/deltas/2021020411080000_rooms.go new file mode 100644 index 000000000..cc4bdadfd --- /dev/null +++ b/federationsender/storage/postgres/deltas/2021020411080000_rooms.go @@ -0,0 +1,46 @@ +// Copyright 2021 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 deltas + +import ( + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/pressly/goose" +) + +func LoadFromGoose() { + goose.AddMigration(UpRemoveRoomsTable, DownRemoveRoomsTable) +} + +func LoadRemoveRoomsTable(m *sqlutil.Migrations) { + m.AddMigration(UpRemoveRoomsTable, DownRemoveRoomsTable) +} + +func UpRemoveRoomsTable(tx *sql.Tx) error { + _, err := tx.Exec(` + DROP TABLE IF EXISTS federationsender_rooms; + `) + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + return nil +} + +func DownRemoveRoomsTable(tx *sql.Tx) error { + // We can't reverse this. + return nil +} diff --git a/federationsender/storage/postgres/room_table.go b/federationsender/storage/postgres/room_table.go deleted file mode 100644 index 8d3ed20ff..000000000 --- a/federationsender/storage/postgres/room_table.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-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" - - "github.com/matrix-org/dendrite/internal/sqlutil" -) - -const roomSchema = ` -CREATE TABLE IF NOT EXISTS federationsender_rooms ( - -- The string ID of the room - room_id TEXT PRIMARY KEY, - -- The most recent event state by the room server. - -- We can use this to tell if our view of the room state has become - -- desynchronised. - last_event_id TEXT NOT NULL -);` - -const insertRoomSQL = "" + - "INSERT INTO federationsender_rooms (room_id, last_event_id) VALUES ($1, '')" + - " ON CONFLICT DO NOTHING" - -const selectRoomForUpdateSQL = "" + - "SELECT last_event_id FROM federationsender_rooms WHERE room_id = $1 FOR UPDATE" - -const updateRoomSQL = "" + - "UPDATE federationsender_rooms SET last_event_id = $2 WHERE room_id = $1" - -type roomStatements struct { - db *sql.DB - insertRoomStmt *sql.Stmt - selectRoomForUpdateStmt *sql.Stmt - updateRoomStmt *sql.Stmt -} - -func NewPostgresRoomsTable(db *sql.DB) (s *roomStatements, err error) { - s = &roomStatements{ - db: db, - } - _, err = s.db.Exec(roomSchema) - if err != nil { - return - } - if s.insertRoomStmt, err = s.db.Prepare(insertRoomSQL); err != nil { - return - } - if s.selectRoomForUpdateStmt, err = s.db.Prepare(selectRoomForUpdateSQL); err != nil { - return - } - if s.updateRoomStmt, err = s.db.Prepare(updateRoomSQL); err != nil { - return - } - return -} - -// insertRoom inserts the room if it didn't already exist. -// If the room didn't exist then last_event_id is set to the empty string. -func (s *roomStatements) InsertRoom( - ctx context.Context, txn *sql.Tx, roomID string, -) error { - _, err := sqlutil.TxStmt(txn, s.insertRoomStmt).ExecContext(ctx, roomID) - return err -} - -// selectRoomForUpdate locks the row for the room and returns the last_event_id. -// The row must already exist in the table. Callers can ensure that the row -// exists by calling insertRoom first. -func (s *roomStatements) SelectRoomForUpdate( - ctx context.Context, txn *sql.Tx, roomID string, -) (string, error) { - var lastEventID string - stmt := sqlutil.TxStmt(txn, s.selectRoomForUpdateStmt) - err := stmt.QueryRowContext(ctx, roomID).Scan(&lastEventID) - if err != nil { - return "", err - } - return lastEventID, nil -} - -// updateRoom updates the last_event_id for the room. selectRoomForUpdate should -// have already been called earlier within the transaction. -func (s *roomStatements) UpdateRoom( - ctx context.Context, txn *sql.Tx, roomID, lastEventID string, -) error { - stmt := sqlutil.TxStmt(txn, s.updateRoomStmt) - _, err := stmt.ExecContext(ctx, roomID, lastEventID) - return err -} diff --git a/federationsender/storage/postgres/storage.go b/federationsender/storage/postgres/storage.go index b9827ca19..5edc08ad7 100644 --- a/federationsender/storage/postgres/storage.go +++ b/federationsender/storage/postgres/storage.go @@ -18,6 +18,7 @@ package postgres import ( "database/sql" + "github.com/matrix-org/dendrite/federationsender/storage/postgres/deltas" "github.com/matrix-org/dendrite/federationsender/storage/shared" "github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/sqlutil" @@ -56,10 +57,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS if err != nil { return nil, err } - rooms, err := NewPostgresRoomsTable(d.db) - if err != nil { - return nil, err - } blacklist, err := NewPostgresBlacklistTable(d.db) if err != nil { return nil, err @@ -72,6 +69,11 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS if err != nil { return nil, err } + m := sqlutil.NewMigrations() + deltas.LoadRemoveRoomsTable(m) + if err = m.RunDeltas(d.db, dbProperties); err != nil { + return nil, err + } d.Database = shared.Database{ DB: d.db, Cache: cache, @@ -80,7 +82,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS FederationSenderQueuePDUs: queuePDUs, FederationSenderQueueEDUs: queueEDUs, FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, FederationSenderBlacklist: blacklist, FederationSenderInboundPeeks: inboundPeeks, FederationSenderOutboundPeeks: outboundPeeks, diff --git a/federationsender/storage/shared/storage.go b/federationsender/storage/shared/storage.go index 4c9490424..2e74e9d6a 100644 --- a/federationsender/storage/shared/storage.go +++ b/federationsender/storage/shared/storage.go @@ -34,7 +34,6 @@ type Database struct { FederationSenderQueueEDUs tables.FederationSenderQueueEDUs FederationSenderQueueJSON tables.FederationSenderQueueJSON FederationSenderJoinedHosts tables.FederationSenderJoinedHosts - FederationSenderRooms tables.FederationSenderRooms FederationSenderBlacklist tables.FederationSenderBlacklist FederationSenderOutboundPeeks tables.FederationSenderOutboundPeeks FederationSenderInboundPeeks tables.FederationSenderInboundPeeks @@ -64,29 +63,6 @@ func (d *Database) UpdateRoom( removeHosts []string, ) (joinedHosts []types.JoinedHost, err error) { err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { - err = d.FederationSenderRooms.InsertRoom(ctx, txn, roomID) - if err != nil { - return err - } - - lastSentEventID, err := d.FederationSenderRooms.SelectRoomForUpdate(ctx, txn, roomID) - if err != nil { - return err - } - - if lastSentEventID == newEventID { - // We've handled this message before, so let's just ignore it. - // We can only get a duplicate for the last message we processed, - // so its enough just to compare the newEventID with lastSentEventID - return nil - } - - if lastSentEventID != "" && lastSentEventID != oldEventID { - return types.EventIDMismatchError{ - DatabaseID: lastSentEventID, RoomServerID: oldEventID, - } - } - joinedHosts, err = d.FederationSenderJoinedHosts.SelectJoinedHostsWithTx(ctx, txn, roomID) if err != nil { return err @@ -101,7 +77,7 @@ func (d *Database) UpdateRoom( if err = d.FederationSenderJoinedHosts.DeleteJoinedHosts(ctx, txn, removeHosts); err != nil { return err } - return d.FederationSenderRooms.UpdateRoom(ctx, txn, roomID, newEventID) + return nil }) return } diff --git a/federationsender/storage/sqlite3/deltas/2021020411080000_rooms.go b/federationsender/storage/sqlite3/deltas/2021020411080000_rooms.go new file mode 100644 index 000000000..cc4bdadfd --- /dev/null +++ b/federationsender/storage/sqlite3/deltas/2021020411080000_rooms.go @@ -0,0 +1,46 @@ +// Copyright 2021 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 deltas + +import ( + "database/sql" + "fmt" + + "github.com/matrix-org/dendrite/internal/sqlutil" + "github.com/pressly/goose" +) + +func LoadFromGoose() { + goose.AddMigration(UpRemoveRoomsTable, DownRemoveRoomsTable) +} + +func LoadRemoveRoomsTable(m *sqlutil.Migrations) { + m.AddMigration(UpRemoveRoomsTable, DownRemoveRoomsTable) +} + +func UpRemoveRoomsTable(tx *sql.Tx) error { + _, err := tx.Exec(` + DROP TABLE IF EXISTS federationsender_rooms; + `) + if err != nil { + return fmt.Errorf("failed to execute upgrade: %w", err) + } + return nil +} + +func DownRemoveRoomsTable(tx *sql.Tx) error { + // We can't reverse this. + return nil +} diff --git a/federationsender/storage/sqlite3/room_table.go b/federationsender/storage/sqlite3/room_table.go deleted file mode 100644 index 0710ccca3..000000000 --- a/federationsender/storage/sqlite3/room_table.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2017-2018 New Vector Ltd -// Copyright 2019-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" -) - -const roomSchema = ` -CREATE TABLE IF NOT EXISTS federationsender_rooms ( - -- The string ID of the room - room_id TEXT PRIMARY KEY, - -- The most recent event state by the room server. - -- We can use this to tell if our view of the room state has become - -- desynchronised. - last_event_id TEXT NOT NULL -);` - -const insertRoomSQL = "" + - "INSERT INTO federationsender_rooms (room_id, last_event_id) VALUES ($1, '')" + - " ON CONFLICT DO NOTHING" - -const selectRoomForUpdateSQL = "" + - "SELECT last_event_id FROM federationsender_rooms WHERE room_id = $1" - -const updateRoomSQL = "" + - "UPDATE federationsender_rooms SET last_event_id = $2 WHERE room_id = $1" - -type roomStatements struct { - db *sql.DB - insertRoomStmt *sql.Stmt - selectRoomForUpdateStmt *sql.Stmt - updateRoomStmt *sql.Stmt -} - -func NewSQLiteRoomsTable(db *sql.DB) (s *roomStatements, err error) { - s = &roomStatements{ - db: db, - } - _, err = db.Exec(roomSchema) - if err != nil { - return - } - - if s.insertRoomStmt, err = db.Prepare(insertRoomSQL); err != nil { - return - } - if s.selectRoomForUpdateStmt, err = db.Prepare(selectRoomForUpdateSQL); err != nil { - return - } - if s.updateRoomStmt, err = db.Prepare(updateRoomSQL); err != nil { - return - } - return -} - -// insertRoom inserts the room if it didn't already exist. -// If the room didn't exist then last_event_id is set to the empty string. -func (s *roomStatements) InsertRoom( - ctx context.Context, txn *sql.Tx, roomID string, -) error { - _, err := sqlutil.TxStmt(txn, s.insertRoomStmt).ExecContext(ctx, roomID) - return err -} - -// selectRoomForUpdate locks the row for the room and returns the last_event_id. -// The row must already exist in the table. Callers can ensure that the row -// exists by calling insertRoom first. -func (s *roomStatements) SelectRoomForUpdate( - ctx context.Context, txn *sql.Tx, roomID string, -) (string, error) { - var lastEventID string - stmt := sqlutil.TxStmt(txn, s.selectRoomForUpdateStmt) - err := stmt.QueryRowContext(ctx, roomID).Scan(&lastEventID) - if err != nil { - return "", err - } - return lastEventID, nil -} - -// updateRoom updates the last_event_id for the room. selectRoomForUpdate should -// have already been called earlier within the transaction. -func (s *roomStatements) UpdateRoom( - ctx context.Context, txn *sql.Tx, roomID, lastEventID string, -) error { - stmt := sqlutil.TxStmt(txn, s.updateRoomStmt) - _, err := stmt.ExecContext(ctx, roomID, lastEventID) - return err -} diff --git a/federationsender/storage/sqlite3/storage.go b/federationsender/storage/sqlite3/storage.go index 2b1358587..84a9ff860 100644 --- a/federationsender/storage/sqlite3/storage.go +++ b/federationsender/storage/sqlite3/storage.go @@ -21,6 +21,7 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/matrix-org/dendrite/federationsender/storage/shared" + "github.com/matrix-org/dendrite/federationsender/storage/sqlite3/deltas" "github.com/matrix-org/dendrite/internal/caching" "github.com/matrix-org/dendrite/internal/sqlutil" "github.com/matrix-org/dendrite/setup/config" @@ -46,10 +47,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS if err != nil { return nil, err } - rooms, err := NewSQLiteRoomsTable(d.db) - if err != nil { - return nil, err - } queuePDUs, err := NewSQLiteQueuePDUsTable(d.db) if err != nil { return nil, err @@ -74,6 +71,11 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS if err != nil { return nil, err } + m := sqlutil.NewMigrations() + deltas.LoadRemoveRoomsTable(m) + if err = m.RunDeltas(d.db, dbProperties); err != nil { + return nil, err + } d.Database = shared.Database{ DB: d.db, Cache: cache, @@ -82,7 +84,6 @@ func NewDatabase(dbProperties *config.DatabaseOptions, cache caching.FederationS FederationSenderQueuePDUs: queuePDUs, FederationSenderQueueEDUs: queueEDUs, FederationSenderQueueJSON: queueJSON, - FederationSenderRooms: rooms, FederationSenderBlacklist: blacklist, FederationSenderOutboundPeeks: outboundPeeks, FederationSenderInboundPeeks: inboundPeeks, diff --git a/federationsender/storage/tables/interface.go b/federationsender/storage/tables/interface.go index 22fd5554f..34ff0b97e 100644 --- a/federationsender/storage/tables/interface.go +++ b/federationsender/storage/tables/interface.go @@ -56,12 +56,6 @@ type FederationSenderJoinedHosts interface { SelectJoinedHostsForRooms(ctx context.Context, roomIDs []string) ([]gomatrixserverlib.ServerName, error) } -type FederationSenderRooms interface { - InsertRoom(ctx context.Context, txn *sql.Tx, roomID string) error - SelectRoomForUpdate(ctx context.Context, txn *sql.Tx, roomID string) (string, error) - UpdateRoom(ctx context.Context, txn *sql.Tx, roomID, lastEventID string) error -} - type FederationSenderBlacklist interface { InsertBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) error SelectBlacklist(ctx context.Context, txn *sql.Tx, serverName gomatrixserverlib.ServerName) (bool, error) diff --git a/federationsender/types/types.go b/federationsender/types/types.go index 90da310c9..c486c05c4 100644 --- a/federationsender/types/types.go +++ b/federationsender/types/types.go @@ -15,8 +15,6 @@ package types import ( - "fmt" - "github.com/matrix-org/gomatrixserverlib" ) @@ -34,22 +32,6 @@ func (s ServerNames) Len() int { return len(s) } func (s ServerNames) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ServerNames) Less(i, j int) bool { return s[i] < s[j] } -// A EventIDMismatchError indicates that we have got out of sync with the -// room server. -type EventIDMismatchError struct { - // The event ID we have stored in our local database. - DatabaseID string - // The event ID received from the room server. - RoomServerID string -} - -func (e EventIDMismatchError) Error() string { - return fmt.Sprintf( - "mismatched last sent event ID: had %q in database got %q from room server", - e.DatabaseID, e.RoomServerID, - ) -} - // tracks peeks we're performing on another server over federation type OutboundPeek struct { PeekID string From 6e44450cc9b541e5e79b7e601462fd3d3cf35fe5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 12:20:37 +0000 Subject: [PATCH 25/33] Don't re-request state events that are already in the timeline (#1739) * Don't request state events if we already have the timeline events (Postgres only) * Rename variable * nocyclo * Add SQLite * Tweaks * Revert query change * Don't dedupe if asking for full state * Update query --- syncapi/storage/interface.go | 2 +- .../postgres/current_room_state_table.go | 5 ++- syncapi/storage/shared/syncserver.go | 8 ++--- .../sqlite3/current_room_state_table.go | 3 +- syncapi/storage/sqlite3/filtering.go | 9 ++++- .../sqlite3/output_room_events_table.go | 6 ++-- syncapi/storage/tables/interface.go | 2 +- syncapi/streams/stream_pdu.go | 33 ++++++++++++------- 8 files changed, 45 insertions(+), 23 deletions(-) diff --git a/syncapi/storage/interface.go b/syncapi/storage/interface.go index 22d801617..9cff4cad1 100644 --- a/syncapi/storage/interface.go +++ b/syncapi/storage/interface.go @@ -35,7 +35,7 @@ type Database interface { MaxStreamPositionForAccountData(ctx context.Context) (types.StreamPosition, error) MaxStreamPositionForSendToDeviceMessages(ctx context.Context) (types.StreamPosition, error) - CurrentState(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter) ([]*gomatrixserverlib.HeaderedEvent, error) + CurrentState(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter, excludeEventIDs []string) ([]*gomatrixserverlib.HeaderedEvent, error) GetStateDeltasForFullStateSync(ctx context.Context, device *userapi.Device, r types.Range, userID string, stateFilter *gomatrixserverlib.StateFilter) ([]types.StateDelta, []string, error) GetStateDeltas(ctx context.Context, device *userapi.Device, r types.Range, userID string, stateFilter *gomatrixserverlib.StateFilter) ([]types.StateDelta, []string, error) RoomIDsWithMembership(ctx context.Context, userID string, membership string) ([]string, error) diff --git a/syncapi/storage/postgres/current_room_state_table.go b/syncapi/storage/postgres/current_room_state_table.go index 77e1e363f..ee649c165 100644 --- a/syncapi/storage/postgres/current_room_state_table.go +++ b/syncapi/storage/postgres/current_room_state_table.go @@ -84,7 +84,8 @@ const selectCurrentStateSQL = "" + " AND ( $4::text[] IS NULL OR type LIKE ANY($4) )" + " AND ( $5::text[] IS NULL OR NOT(type LIKE ANY($5)) )" + " AND ( $6::bool IS NULL OR contains_url = $6 )" + - " LIMIT $7" + " AND (event_id = ANY($7)) IS NOT TRUE" + + " LIMIT $8" const selectJoinedUsersSQL = "" + "SELECT room_id, state_key FROM syncapi_current_room_state WHERE type = 'm.room.member' AND membership = 'join'" @@ -197,6 +198,7 @@ func (s *currentRoomStateStatements) SelectRoomIDsWithMembership( func (s *currentRoomStateStatements) SelectCurrentState( ctx context.Context, txn *sql.Tx, roomID string, stateFilter *gomatrixserverlib.StateFilter, + excludeEventIDs []string, ) ([]*gomatrixserverlib.HeaderedEvent, error) { stmt := sqlutil.TxStmt(txn, s.selectCurrentStateStmt) rows, err := stmt.QueryContext(ctx, roomID, @@ -205,6 +207,7 @@ func (s *currentRoomStateStatements) SelectCurrentState( pq.StringArray(filterConvertTypeWildcardToSQL(stateFilter.Types)), pq.StringArray(filterConvertTypeWildcardToSQL(stateFilter.NotTypes)), stateFilter.ContainsURL, + pq.StringArray(excludeEventIDs), stateFilter.Limit, ) if err != nil { diff --git a/syncapi/storage/shared/syncserver.go b/syncapi/storage/shared/syncserver.go index 239f68129..ee2c176ca 100644 --- a/syncapi/storage/shared/syncserver.go +++ b/syncapi/storage/shared/syncserver.go @@ -103,8 +103,8 @@ func (d *Database) MaxStreamPositionForAccountData(ctx context.Context) (types.S return types.StreamPosition(id), nil } -func (d *Database) CurrentState(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter) ([]*gomatrixserverlib.HeaderedEvent, error) { - return d.CurrentRoomState.SelectCurrentState(ctx, nil, roomID, stateFilterPart) +func (d *Database) CurrentState(ctx context.Context, roomID string, stateFilterPart *gomatrixserverlib.StateFilter, excludeEventIDs []string) ([]*gomatrixserverlib.HeaderedEvent, error) { + return d.CurrentRoomState.SelectCurrentState(ctx, nil, roomID, stateFilterPart, excludeEventIDs) } func (d *Database) RoomIDsWithMembership(ctx context.Context, userID string, membership string) ([]string, error) { @@ -195,7 +195,7 @@ func (d *Database) GetStateEvent( func (d *Database) GetStateEventsForRoom( ctx context.Context, roomID string, stateFilter *gomatrixserverlib.StateFilter, ) (stateEvents []*gomatrixserverlib.HeaderedEvent, err error) { - stateEvents, err = d.CurrentRoomState.SelectCurrentState(ctx, nil, roomID, stateFilter) + stateEvents, err = d.CurrentRoomState.SelectCurrentState(ctx, nil, roomID, stateFilter, nil) return } @@ -870,7 +870,7 @@ func (d *Database) currentStateStreamEventsForRoom( ctx context.Context, txn *sql.Tx, roomID string, stateFilter *gomatrixserverlib.StateFilter, ) ([]types.StreamEvent, error) { - allState, err := d.CurrentRoomState.SelectCurrentState(ctx, txn, roomID, stateFilter) + allState, err := d.CurrentRoomState.SelectCurrentState(ctx, txn, roomID, stateFilter, nil) if err != nil { return nil, err } diff --git a/syncapi/storage/sqlite3/current_room_state_table.go b/syncapi/storage/sqlite3/current_room_state_table.go index 55ed27a41..4fbbf45cf 100644 --- a/syncapi/storage/sqlite3/current_room_state_table.go +++ b/syncapi/storage/sqlite3/current_room_state_table.go @@ -178,6 +178,7 @@ func (s *currentRoomStateStatements) SelectRoomIDsWithMembership( func (s *currentRoomStateStatements) SelectCurrentState( ctx context.Context, txn *sql.Tx, roomID string, stateFilter *gomatrixserverlib.StateFilter, + excludeEventIDs []string, ) ([]*gomatrixserverlib.HeaderedEvent, error) { stmt, params, err := prepareWithFilters( s.db, txn, selectCurrentStateSQL, @@ -186,7 +187,7 @@ func (s *currentRoomStateStatements) SelectCurrentState( }, stateFilter.Senders, stateFilter.NotSenders, stateFilter.Types, stateFilter.NotTypes, - stateFilter.Limit, FilterOrderNone, + excludeEventIDs, stateFilter.Limit, FilterOrderNone, ) if err != nil { return nil, fmt.Errorf("s.prepareWithFilters: %w", err) diff --git a/syncapi/storage/sqlite3/filtering.go b/syncapi/storage/sqlite3/filtering.go index 0faf5297a..050f3a26b 100644 --- a/syncapi/storage/sqlite3/filtering.go +++ b/syncapi/storage/sqlite3/filtering.go @@ -23,9 +23,10 @@ const ( // fields might come from either a StateFilter or an EventFilter, // and it's easier just to have the caller extract the relevant // parts. +// nolint:gocyclo func prepareWithFilters( db *sql.DB, txn *sql.Tx, query string, params []interface{}, - senders, notsenders, types, nottypes []string, + senders, notsenders, types, nottypes []string, excludeEventIDs []string, limit int, order FilterOrder, ) (*sql.Stmt, []interface{}, error) { offset := len(params) @@ -53,6 +54,12 @@ func prepareWithFilters( params, offset = append(params, v), offset+1 } } + if count := len(excludeEventIDs); count > 0 { + query += " AND event_id NOT IN " + sqlutil.QueryVariadicOffset(count, offset) + for _, v := range excludeEventIDs { + params, offset = append(params, v), offset+1 + } + } switch order { case FilterOrderAsc: query += " ORDER BY id ASC" diff --git a/syncapi/storage/sqlite3/output_room_events_table.go b/syncapi/storage/sqlite3/output_room_events_table.go index 019aba8b3..3c70a499b 100644 --- a/syncapi/storage/sqlite3/output_room_events_table.go +++ b/syncapi/storage/sqlite3/output_room_events_table.go @@ -150,7 +150,7 @@ func (s *outputRoomEventsStatements) SelectStateInRange( }, stateFilter.Senders, stateFilter.NotSenders, stateFilter.Types, stateFilter.NotTypes, - stateFilter.Limit, FilterOrderAsc, + nil, stateFilter.Limit, FilterOrderAsc, ) if err != nil { return nil, nil, fmt.Errorf("s.prepareWithFilters: %w", err) @@ -326,7 +326,7 @@ func (s *outputRoomEventsStatements) SelectRecentEvents( }, eventFilter.Senders, eventFilter.NotSenders, eventFilter.Types, eventFilter.NotTypes, - eventFilter.Limit+1, FilterOrderDesc, + nil, eventFilter.Limit+1, FilterOrderDesc, ) if err != nil { return nil, false, fmt.Errorf("s.prepareWithFilters: %w", err) @@ -374,7 +374,7 @@ func (s *outputRoomEventsStatements) SelectEarlyEvents( }, eventFilter.Senders, eventFilter.NotSenders, eventFilter.Types, eventFilter.NotTypes, - eventFilter.Limit, FilterOrderAsc, + nil, eventFilter.Limit, FilterOrderAsc, ) if err != nil { return nil, fmt.Errorf("s.prepareWithFilters: %w", err) diff --git a/syncapi/storage/tables/interface.go b/syncapi/storage/tables/interface.go index 997486dd4..028872716 100644 --- a/syncapi/storage/tables/interface.go +++ b/syncapi/storage/tables/interface.go @@ -91,7 +91,7 @@ type CurrentRoomState interface { DeleteRoomStateByEventID(ctx context.Context, txn *sql.Tx, eventID string) error DeleteRoomStateForRoom(ctx context.Context, txn *sql.Tx, roomID string) error // SelectCurrentState returns all the current state events for the given room. - SelectCurrentState(ctx context.Context, txn *sql.Tx, roomID string, stateFilter *gomatrixserverlib.StateFilter) ([]*gomatrixserverlib.HeaderedEvent, error) + SelectCurrentState(ctx context.Context, txn *sql.Tx, roomID string, stateFilter *gomatrixserverlib.StateFilter, excludeEventIDs []string) ([]*gomatrixserverlib.HeaderedEvent, error) // SelectRoomIDsWithMembership returns the list of room IDs which have the given user in the given membership state. SelectRoomIDsWithMembership(ctx context.Context, txn *sql.Tx, userID string, membership string) ([]string, error) // SelectJoinedUsers returns a map of room ID to a list of joined user IDs. diff --git a/syncapi/streams/stream_pdu.go b/syncapi/streams/stream_pdu.go index ae38dc30e..e11ac8dd7 100644 --- a/syncapi/streams/stream_pdu.go +++ b/syncapi/streams/stream_pdu.go @@ -98,7 +98,7 @@ func (p *PDUStreamProvider) CompleteSync( var jr *types.JoinResponse jr, err = p.getJoinResponseForCompleteSync( - ctx, roomID, r, &stateFilter, &eventFilter, req.Device, + ctx, roomID, r, &stateFilter, &eventFilter, req.WantFullState, req.Device, ) if err != nil { req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") @@ -124,7 +124,7 @@ func (p *PDUStreamProvider) CompleteSync( if !peek.Deleted { var jr *types.JoinResponse jr, err = p.getJoinResponseForCompleteSync( - ctx, peek.RoomID, r, &stateFilter, &eventFilter, req.Device, + ctx, peek.RoomID, r, &stateFilter, &eventFilter, req.WantFullState, req.Device, ) if err != nil { req.Log.WithError(err).Error("p.getJoinResponseForCompleteSync failed") @@ -254,26 +254,37 @@ func (p *PDUStreamProvider) addRoomDeltaToResponse( return nil } +// nolint:gocyclo func (p *PDUStreamProvider) getJoinResponseForCompleteSync( ctx context.Context, roomID string, r types.Range, stateFilter *gomatrixserverlib.StateFilter, eventFilter *gomatrixserverlib.RoomEventFilter, + wantFullState bool, device *userapi.Device, ) (jr *types.JoinResponse, err error) { - var stateEvents []*gomatrixserverlib.HeaderedEvent - stateEvents, err = p.DB.CurrentState(ctx, roomID, stateFilter) + // 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 + recentStreamEvents, limited, err := p.DB.RecentEvents( + ctx, roomID, r, eventFilter, true, true, + ) 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 = p.DB.RecentEvents( - ctx, roomID, r, eventFilter, true, true, - ) + + // Get the event IDs of the stream events we fetched. There's no point in us + var excludingEventIDs []string + if !wantFullState { + excludingEventIDs = make([]string, 0, len(recentStreamEvents)) + for _, event := range recentStreamEvents { + if event.StateKey() != nil { + excludingEventIDs = append(excludingEventIDs, event.EventID()) + } + } + } + + stateEvents, err := p.DB.CurrentState(ctx, roomID, stateFilter, excludingEventIDs) if err != nil { return } From bd72ed50d426f6025004a2beb1372912ecf1d500 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 12:25:31 +0000 Subject: [PATCH 26/33] Reduce log level of 'Failed to send transaction' log line, since quite often it is flooding logs for dead servers --- federationsender/queue/destinationqueue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/federationsender/queue/destinationqueue.go b/federationsender/queue/destinationqueue.go index 99b9e4497..d9567eeba 100644 --- a/federationsender/queue/destinationqueue.go +++ b/federationsender/queue/destinationqueue.go @@ -444,7 +444,7 @@ func (oq *destinationQueue) nextTransaction( log.WithFields(log.Fields{ "destination": oq.destination, log.ErrorKey: err, - }).Infof("Failed to send transaction %q", t.TransactionID) + }).Debugf("Failed to send transaction %q", t.TransactionID) return false, 0, 0, err } } From 397b158c05be7ee395257913820f86616302e6ed Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Thu, 4 Feb 2021 12:39:43 +0000 Subject: [PATCH 27/33] Version 0.3.9 --- CHANGES.md | 14 ++++++++++++++ internal/version.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b11c3d7ac..b76a6acf4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,19 @@ # Changelog +## Dendrite 0.3.9 (2021-02-04) + +### Features + +* Performance of initial/complete syncs has been improved dramatically +* State events that can't be authed are now dropped when joining a room rather than unexpectedly causing the room join to fail +* State events that already appear in the timeline will no longer be requested from the sync API database more than once, which may reduce memory usage in some cases + +### Fixes + +* A crash at startup due to a conflict in the sync API account data has been fixed +* A crash at startup due to mismatched event IDs in the federation sender has been fixed +* A redundant check which may cause the roomserver memberships table to get out of sync has been removed + ## Dendrite 0.3.8 (2021-01-28) ### Fixes diff --git a/internal/version.go b/internal/version.go index f5c6a423f..7d0980379 100644 --- a/internal/version.go +++ b/internal/version.go @@ -17,7 +17,7 @@ var build string const ( VersionMajor = 0 VersionMinor = 3 - VersionPatch = 8 + VersionPatch = 9 VersionTag = "" // example: "rc1" ) From 02e6d89cc2bde930be36323e626ce54643d56a7f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sat, 6 Feb 2021 11:49:18 +0000 Subject: [PATCH 28/33] Fix crash in membership updater (#1753) * Fix nil pointer exception in membership updater * goimports --- roomserver/internal/input/input_membership.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/roomserver/internal/input/input_membership.go b/roomserver/internal/input/input_membership.go index bc646c3c6..44435bfd9 100644 --- a/roomserver/internal/input/input_membership.go +++ b/roomserver/internal/input/input_membership.go @@ -107,6 +107,23 @@ func (r *Inputer) updateMembership( return updates, nil } + // In an ideal world, we shouldn't ever have "add" be nil and "remove" be + // set, as this implies that we're deleting a state event without replacing + // it (a thing that ordinarily shouldn't happen in Matrix). However, state + // resets are sadly a thing occasionally and we have to account for that. + // Beforehand there used to be a check here which stopped dead if we hit + // this scenario, but that meant that the membership table got out of sync + // after a state reset, often thinking that the user was still joined to + // the room even though the room state said otherwise, and this would prevent + // the user from being able to attempt to rejoin the room without modifying + // the database. So instead what we'll do is we'll just update the membership + // table to say that the user is "leave" and we'll use the old event to + // avoid nil pointer exceptions on the code path that follows. + if add == nil { + add = remove + newMembership = gomatrixserverlib.Leave + } + mu, err := updater.MembershipUpdater(targetUserNID, r.isLocalTarget(add)) if err != nil { return nil, err From 82df1948588919800866151cde569c726577b0c3 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Sat, 6 Feb 2021 16:56:55 +0000 Subject: [PATCH 29/33] Increase limit --- setup/mscs/msc2946/msc2946.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 3580d4d2e..694c97322 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -46,7 +46,7 @@ const ( // Defaults sets the request defaults func Defaults(r *gomatrixserverlib.MSC2946SpacesRequest) { - r.Limit = 100 + r.Limit = 2000 r.MaxRoomsPerSpace = -1 } @@ -108,9 +108,6 @@ func federatedSpacesHandler( JSON: jsonerror.BadJSON("The request body could not be decoded into valid JSON. " + err.Error()), } } - if r.Limit > 100 { - r.Limit = 100 - } w := walker{ req: &r, rootRoomID: roomID, From 9a199ba179273250330c85f6d543381b2ac1474c Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Sat, 6 Feb 2021 17:05:00 +0000 Subject: [PATCH 30/33] Remove 100 default --- setup/mscs/msc2946/msc2946.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup/mscs/msc2946/msc2946.go b/setup/mscs/msc2946/msc2946.go index 694c97322..03af06669 100644 --- a/setup/mscs/msc2946/msc2946.go +++ b/setup/mscs/msc2946/msc2946.go @@ -144,9 +144,6 @@ func spacesHandler( if resErr := chttputil.UnmarshalJSONRequest(req, &r); resErr != nil { return *resErr } - if r.Limit > 100 { - r.Limit = 100 - } w := walker{ req: &r, rootRoomID: roomID, From 85aaaf9bb964c92a0eb7cd236509d5db0c164aec Mon Sep 17 00:00:00 2001 From: David Florness Date: Fri, 12 Feb 2021 04:25:35 -0500 Subject: [PATCH 31/33] Fix a few indentation mistakes (#1752) Signed-off-by: David Florness --- build/docker/docker-compose.polylith.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/docker/docker-compose.polylith.yml b/build/docker/docker-compose.polylith.yml index f377e36fc..e95e19574 100644 --- a/build/docker/docker-compose.polylith.yml +++ b/build/docker/docker-compose.polylith.yml @@ -70,7 +70,7 @@ services: volumes: - ./config:/etc/dendrite networks: - - internal + - internal signing_key_server: hostname: signing_key_server @@ -86,9 +86,9 @@ services: image: matrixdotorg/dendrite-polylith:latest command: userapi volumes: - - ./config:/etc/dendrite + - ./config:/etc/dendrite networks: - - internal + - internal appservice_api: hostname: appservice_api From f448e8972a1a6974916afc3ab686b342eaf568e2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 12 Feb 2021 09:43:31 +0000 Subject: [PATCH 32/33] Implement /createRoom power_level_content_override (#1761) * Implement /createRoom power_level_content_override * delint * Skip having to re-marshal PowerLevelContentOverride by making use of `json.RawMessage` --- clientapi/routing/createroom.go | 35 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/clientapi/routing/createroom.go b/clientapi/routing/createroom.go index 5a2ffea34..9be0b5126 100644 --- a/clientapi/routing/createroom.go +++ b/clientapi/routing/createroom.go @@ -38,16 +38,17 @@ import ( // https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom type createRoomRequest struct { - Invite []string `json:"invite"` - Name string `json:"name"` - Visibility string `json:"visibility"` - Topic string `json:"topic"` - Preset string `json:"preset"` - CreationContent map[string]interface{} `json:"creation_content"` - InitialState []fledglingEvent `json:"initial_state"` - RoomAliasName string `json:"room_alias_name"` - GuestCanJoin bool `json:"guest_can_join"` - RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"` + Invite []string `json:"invite"` + Name string `json:"name"` + Visibility string `json:"visibility"` + Topic string `json:"topic"` + Preset string `json:"preset"` + CreationContent map[string]interface{} `json:"creation_content"` + InitialState []fledglingEvent `json:"initial_state"` + RoomAliasName string `json:"room_alias_name"` + GuestCanJoin bool `json:"guest_can_join"` + RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"` + PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"` } const ( @@ -257,6 +258,18 @@ func createRoom( var builtEvents []*gomatrixserverlib.HeaderedEvent + powerLevelContent := eventutil.InitialPowerLevelsContent(userID) + if r.PowerLevelContentOverride != nil { + // Merge powerLevelContentOverride fields by unmarshalling it atop the defaults + err = json.Unmarshal(r.PowerLevelContentOverride, &powerLevelContent) + if err != nil { + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.BadJSON("malformed power_level_content_override"), + } + } + } + // send events into the room in order of: // 1- m.room.create // 2- room creator join member @@ -278,7 +291,7 @@ func createRoom( eventsToMake := []fledglingEvent{ {"m.room.create", "", r.CreationContent}, {"m.room.member", userID, membershipContent}, - {"m.room.power_levels", "", eventutil.InitialPowerLevelsContent(userID)}, + {"m.room.power_levels", "", powerLevelContent}, {"m.room.join_rules", "", gomatrixserverlib.JoinRuleContent{JoinRule: joinRules}}, {"m.room.history_visibility", "", eventutil.HistoryVisibilityContent{HistoryVisibility: historyVisibility}}, } From 5d74a1757f652f1e367a036f931e71bd3da612dd Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 16 Feb 2021 17:12:17 +0000 Subject: [PATCH 33/33] Don't query for servers so often in /send (#1766) * Look up servers less often, don't hit API for missing auth events unless there are actually missing auth events * Remove ResolveConflictsAdhoc (since it is already in GMSL), other tweaks * Update gomatrixserverlib to matrix-org/gomatrixserverlib#254 * Fix resolve-state * Initialise t.servers on first use --- cmd/resolve-state/main.go | 3 +- federationapi/routing/send.go | 108 ++++++++++++----------------- go.mod | 2 +- go.sum | 4 +- roomserver/internal/query/query.go | 4 +- roomserver/state/state.go | 73 ------------------- 6 files changed, 50 insertions(+), 144 deletions(-) diff --git a/cmd/resolve-state/main.go b/cmd/resolve-state/main.go index efa583331..69c3489d5 100644 --- a/cmd/resolve-state/main.go +++ b/cmd/resolve-state/main.go @@ -8,7 +8,6 @@ import ( "strconv" "github.com/matrix-org/dendrite/internal/caching" - "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/setup" @@ -105,7 +104,7 @@ func main() { } fmt.Println("Resolving state") - resolved, err := state.ResolveConflictsAdhoc( + resolved, err := gomatrixserverlib.ResolveConflicts( gomatrixserverlib.RoomVersion(*roomVersion), events, authEvents, diff --git a/federationapi/routing/send.go b/federationapi/routing/send.go index 96b5355ea..02683aeaf 100644 --- a/federationapi/routing/send.go +++ b/federationapi/routing/send.go @@ -102,11 +102,13 @@ func Send( type txnReq struct { gomatrixserverlib.Transaction - rsAPI api.RoomserverInternalAPI - eduAPI eduserverAPI.EDUServerInputAPI - keyAPI keyapi.KeyInternalAPI - keys gomatrixserverlib.JSONVerifier - federation txnFederationClient + rsAPI api.RoomserverInternalAPI + eduAPI eduserverAPI.EDUServerInputAPI + keyAPI keyapi.KeyInternalAPI + keys gomatrixserverlib.JSONVerifier + federation txnFederationClient + servers []gomatrixserverlib.ServerName + serversMutex sync.RWMutex // local cache of events for auth checks, etc - this may include events // which the roomserver is unaware of. haveEvents map[string]*gomatrixserverlib.HeaderedEvent @@ -404,16 +406,21 @@ func (t *txnReq) processDeviceListUpdate(ctx context.Context, e gomatrixserverli } func (t *txnReq) getServers(ctx context.Context, roomID string) []gomatrixserverlib.ServerName { - servers := []gomatrixserverlib.ServerName{t.Origin} + t.serversMutex.Lock() + defer t.serversMutex.Unlock() + if t.servers != nil { + return t.servers + } + t.servers = []gomatrixserverlib.ServerName{t.Origin} serverReq := &api.QueryServerJoinedToRoomRequest{ RoomID: roomID, } serverRes := &api.QueryServerJoinedToRoomResponse{} if err := t.rsAPI.QueryServerJoinedToRoom(ctx, serverReq, serverRes); err == nil { - servers = append(servers, serverRes.ServerNames...) - util.GetLogger(ctx).Infof("Found %d server(s) to query for missing events in %q", len(servers), roomID) + t.servers = append(t.servers, serverRes.ServerNames...) + util.GetLogger(ctx).Infof("Found %d server(s) to query for missing events in %q", len(t.servers), roomID) } - return servers + return t.servers } func (t *txnReq) processEvent(ctx context.Context, e *gomatrixserverlib.Event) error { @@ -482,14 +489,10 @@ func (t *txnReq) retrieveMissingAuthEvents( missingAuthEvents[missingAuthEventID] = struct{}{} } - servers := t.getServers(ctx, e.RoomID()) - if len(servers) > 5 { - servers = servers[:5] - } withNextEvent: for missingAuthEventID := range missingAuthEvents { withNextServer: - for _, server := range servers { + for _, server := range t.getServers(ctx, e.RoomID()) { logger.Infof("Retrieving missing auth event %q from %q", missingAuthEventID, server) tx, err := t.federation.GetEvent(ctx, server, missingAuthEventID) if err != nil { @@ -692,13 +695,8 @@ func (t *txnReq) lookupStateAfterEvent(ctx context.Context, roomVersion gomatrix return nil, false, fmt.Errorf("t.lookupStateBeforeEvent: %w", err) } - servers := t.getServers(ctx, roomID) - if len(servers) > 5 { - servers = servers[:5] - } - // fetch the event we're missing and add it to the pile - h, err := t.lookupEvent(ctx, roomVersion, eventID, false, servers) + h, err := t.lookupEvent(ctx, roomVersion, roomID, eventID, false) switch err.(type) { case verifySigError: return respState, false, nil @@ -740,11 +738,10 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event t.haveEvents[ev.EventID()] = res.StateEvents[i] } var authEvents []*gomatrixserverlib.Event - missingAuthEvents := make(map[string]bool) + missingAuthEvents := map[string]bool{} for _, ev := range res.StateEvents { for _, ae := range ev.AuthEventIDs() { - aev, ok := t.haveEvents[ae] - if ok { + if aev, ok := t.haveEvents[ae]; ok { authEvents = append(authEvents, aev.Unwrap()) } else { missingAuthEvents[ae] = true @@ -753,27 +750,28 @@ func (t *txnReq) lookupStateAfterEventLocally(ctx context.Context, roomID, event } // QueryStateAfterEvents does not return the auth events, so fetch them now. We know the roomserver has them else it wouldn't // have stored the event. - var missingEventList []string - for evID := range missingAuthEvents { - missingEventList = append(missingEventList, evID) - } - queryReq := api.QueryEventsByIDRequest{ - EventIDs: missingEventList, - } - util.GetLogger(ctx).Infof("Fetching missing auth events: %v", missingEventList) - var queryRes api.QueryEventsByIDResponse - if err = t.rsAPI.QueryEventsByID(ctx, &queryReq, &queryRes); err != nil { - return nil - } - for i := range queryRes.Events { - evID := queryRes.Events[i].EventID() - t.haveEvents[evID] = queryRes.Events[i] - authEvents = append(authEvents, queryRes.Events[i].Unwrap()) + if len(missingAuthEvents) > 0 { + var missingEventList []string + for evID := range missingAuthEvents { + missingEventList = append(missingEventList, evID) + } + queryReq := api.QueryEventsByIDRequest{ + EventIDs: missingEventList, + } + util.GetLogger(ctx).Infof("Fetching missing auth events: %v", missingEventList) + var queryRes api.QueryEventsByIDResponse + if err = t.rsAPI.QueryEventsByID(ctx, &queryReq, &queryRes); err != nil { + return nil + } + for i := range queryRes.Events { + evID := queryRes.Events[i].EventID() + t.haveEvents[evID] = queryRes.Events[i] + authEvents = append(authEvents, queryRes.Events[i].Unwrap()) + } } - evs := gomatrixserverlib.UnwrapEventHeaders(res.StateEvents) return &gomatrixserverlib.RespState{ - StateEvents: evs, + StateEvents: gomatrixserverlib.UnwrapEventHeaders(res.StateEvents), AuthEvents: authEvents, } } @@ -805,11 +803,7 @@ retryAllowedState: if err = checkAllowedByState(backwardsExtremity, resolvedStateEvents); err != nil { switch missing := err.(type) { case gomatrixserverlib.MissingAuthEventError: - servers := t.getServers(ctx, backwardsExtremity.RoomID()) - if len(servers) > 5 { - servers = servers[:5] - } - h, err2 := t.lookupEvent(ctx, roomVersion, missing.AuthEventID, true, servers) + h, err2 := t.lookupEvent(ctx, roomVersion, backwardsExtremity.RoomID(), missing.AuthEventID, true) switch err2.(type) { case verifySigError: return &gomatrixserverlib.RespState{ @@ -857,17 +851,8 @@ func (t *txnReq) getMissingEvents(ctx context.Context, e *gomatrixserverlib.Even latestEvents[i] = res.LatestEvents[i].EventID } - servers := []gomatrixserverlib.ServerName{t.Origin} - serverReq := &api.QueryServerJoinedToRoomRequest{ - RoomID: e.RoomID(), - } - serverRes := &api.QueryServerJoinedToRoomResponse{} - if err = t.rsAPI.QueryServerJoinedToRoom(ctx, serverReq, serverRes); err == nil { - servers = append(servers, serverRes.ServerNames...) - logger.Infof("Found %d server(s) to query for missing events", len(servers)) - } - var missingResp *gomatrixserverlib.RespMissingEvents + servers := t.getServers(ctx, e.RoomID()) for _, server := range servers { var m gomatrixserverlib.RespMissingEvents if m, err = t.federation.LookupMissingEvents(ctx, server, e.RoomID(), gomatrixserverlib.MissingEvents{ @@ -1015,12 +1000,6 @@ func (t *txnReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, even "concurrent_requests": concurrentRequests, }).Info("Fetching missing state at event") - // Get a list of servers to fetch from. - servers := t.getServers(ctx, roomID) - if len(servers) > 5 { - servers = servers[:5] - } - // Create a queue containing all of the missing event IDs that we want // to retrieve. pending := make(chan string, missingCount) @@ -1046,7 +1025,7 @@ func (t *txnReq) lookupMissingStateViaStateIDs(ctx context.Context, roomID, even // Define what we'll do in order to fetch the missing event ID. fetch := func(missingEventID string) { var h *gomatrixserverlib.HeaderedEvent - h, err = t.lookupEvent(ctx, roomVersion, missingEventID, false, servers) + h, err = t.lookupEvent(ctx, roomVersion, roomID, missingEventID, false) switch err.(type) { case verifySigError: return @@ -1112,7 +1091,7 @@ func (t *txnReq) createRespStateFromStateIDs(stateIDs gomatrixserverlib.RespStat return &respState, nil } -func (t *txnReq) lookupEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, missingEventID string, localFirst bool, servers []gomatrixserverlib.ServerName) (*gomatrixserverlib.HeaderedEvent, error) { +func (t *txnReq) lookupEvent(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, roomID, missingEventID string, localFirst bool) (*gomatrixserverlib.HeaderedEvent, error) { if localFirst { // fetch from the roomserver queryReq := api.QueryEventsByIDRequest{ @@ -1127,6 +1106,7 @@ func (t *txnReq) lookupEvent(ctx context.Context, roomVersion gomatrixserverlib. } var event *gomatrixserverlib.Event found := false + servers := t.getServers(ctx, roomID) for _, serverName := range servers { txn, err := t.federation.GetEvent(ctx, serverName, missingEventID) if err != nil || len(txn.PDUs) == 0 { diff --git a/go.mod b/go.mod index 96aa881c6..8517ca7ea 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-20210129163316-dd4d53729ead + github.com/matrix-org/gomatrixserverlib v0.0.0-20210216163908-bab1f2be20d0 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 diff --git a/go.sum b/go.sum index a6464cc8e..473fffa78 100644 --- a/go.sum +++ b/go.sum @@ -567,8 +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-20210129163316-dd4d53729ead h1:VmGJybKUQin8+NyA9ZkrHJpE8ygXzcON9peQH9LC92c= -github.com/matrix-org/gomatrixserverlib v0.0.0-20210129163316-dd4d53729ead/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210216163908-bab1f2be20d0 h1:eP8t7DaLKkNz0IT9GcJeG6UTKjfvihIxbAXKN0I7j6g= +github.com/matrix-org/gomatrixserverlib v0.0.0-20210216163908-bab1f2be20d0/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= diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go index 2a361641a..3aa51726e 100644 --- a/roomserver/internal/query/query.go +++ b/roomserver/internal/query/query.go @@ -112,7 +112,7 @@ func (r *Queryer) QueryStateAfterEvents( return fmt.Errorf("getAuthChain: %w", err) } - stateEvents, err = state.ResolveConflictsAdhoc(info.RoomVersion, stateEvents, authEvents) + stateEvents, err = gomatrixserverlib.ResolveConflicts(info.RoomVersion, stateEvents, authEvents) if err != nil { return fmt.Errorf("state.ResolveConflictsAdhoc: %w", err) } @@ -469,7 +469,7 @@ func (r *Queryer) QueryStateAndAuthChain( } if request.ResolveState { - if stateEvents, err = state.ResolveConflictsAdhoc( + if stateEvents, err = gomatrixserverlib.ResolveConflicts( info.RoomVersion, stateEvents, authEvents, ); err != nil { return err diff --git a/roomserver/state/state.go b/roomserver/state/state.go index 7f0bc37db..2c01ca035 100644 --- a/roomserver/state/state.go +++ b/roomserver/state/state.go @@ -683,79 +683,6 @@ func (v *StateResolution) calculateStateAfterManyEvents( return } -// ResolveConflictsAdhoc is a helper function to assist the query API in -// performing state resolution when requested. This is a different code -// path to the rest of state.go because this assumes you already have -// gomatrixserverlib.Event objects and not just a bunch of NIDs like -// elsewhere in the state resolution. -// TODO: Some of this can possibly be deduplicated -func ResolveConflictsAdhoc( - version gomatrixserverlib.RoomVersion, - events []*gomatrixserverlib.Event, - authEvents []*gomatrixserverlib.Event, -) ([]*gomatrixserverlib.Event, error) { - type stateKeyTuple struct { - Type string - StateKey string - } - - // Prepare our data structures. - eventMap := make(map[stateKeyTuple][]*gomatrixserverlib.Event) - var conflicted, notConflicted, resolved []*gomatrixserverlib.Event - - // Run through all of the events that we were given and sort them - // into a map, sorted by (event_type, state_key) tuple. This means - // that we can easily spot events that are "conflicted", e.g. - // there are duplicate values for the same tuple key. - for _, event := range events { - if event.StateKey() == nil { - // Ignore events that are not state events. - continue - } - // Append the events if there is already a conflicted list for - // this tuple key, create it if not. - tuple := stateKeyTuple{event.Type(), *event.StateKey()} - eventMap[tuple] = append(eventMap[tuple], event) - } - - // Split out the events in the map into conflicted and unconflicted - // buckets. The conflicted events will be ran through state res, - // whereas unconfliced events will always going to appear in the - // final resolved state. - for _, list := range eventMap { - if len(list) > 1 { - conflicted = append(conflicted, list...) - } else { - notConflicted = append(notConflicted, list...) - } - } - - // Work out which state resolution algorithm we want to run for - // the room version. - stateResAlgo, err := version.StateResAlgorithm() - if err != nil { - return nil, err - } - switch stateResAlgo { - case gomatrixserverlib.StateResV1: - // Currently state res v1 doesn't handle unconflicted events - // for us, like state res v2 does, so we will need to add the - // unconflicted events into the state ourselves. - // TODO: Fix state res v1 so this is handled for the caller. - resolved = gomatrixserverlib.ResolveStateConflicts(conflicted, authEvents) - resolved = append(resolved, notConflicted...) - case gomatrixserverlib.StateResV2: - // TODO: auth difference here? - resolved = gomatrixserverlib.ResolveStateConflictsV2(conflicted, notConflicted, authEvents, authEvents) - default: - return nil, fmt.Errorf("unsupported state resolution algorithm %v", stateResAlgo) - } - - // Return the final resolved state events, including both the - // resolved set of conflicted events, and the unconflicted events. - return resolved, nil -} - func (v *StateResolution) resolveConflicts( ctx context.Context, version gomatrixserverlib.RoomVersion, notConflicted, conflicted []types.StateEntry,