mirror of
https://github.com/matrix-org/dendrite
synced 2024-11-14 00:47:19 +00:00
1414922026
* sytest: Make 'Inbound federation can backfill events' pass This breaks 'Outbound federation can backfill events' because now we are returning the right number of events, which the previous test was relying on. Previously, /messages was backfilling the membership event, causing the test to pass. Now we are no longer backfilling the membership event due to the change in this commit, causing the test to fail. The test should instead be returning the membership event locally from synacpis database, but it doesn't do it fast enough, resulting in a no-op /sync response with a next_batch=s0_0 which will never pick up the local membership event when it rolls in. The test does attempt to retry, but doesn't take the new next_batch=s1_0 resulting in it missing from the /messages response. * Linting
305 lines
11 KiB
Go
305 lines
11 KiB
Go
package internal
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/matrix-org/dendrite/roomserver/auth"
|
|
"github.com/matrix-org/dendrite/roomserver/storage"
|
|
"github.com/matrix-org/dendrite/roomserver/types"
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
"github.com/matrix-org/util"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// backfillRequester implements gomatrixserverlib.BackfillRequester
|
|
type backfillRequester struct {
|
|
db storage.Database
|
|
fedClient *gomatrixserverlib.FederationClient
|
|
thisServer gomatrixserverlib.ServerName
|
|
bwExtrems map[string][]string
|
|
|
|
// per-request state
|
|
servers []gomatrixserverlib.ServerName
|
|
eventIDToBeforeStateIDs map[string][]string
|
|
eventIDMap map[string]gomatrixserverlib.Event
|
|
}
|
|
|
|
func newBackfillRequester(db storage.Database, fedClient *gomatrixserverlib.FederationClient, thisServer gomatrixserverlib.ServerName, bwExtrems map[string][]string) *backfillRequester {
|
|
return &backfillRequester{
|
|
db: db,
|
|
fedClient: fedClient,
|
|
thisServer: thisServer,
|
|
eventIDToBeforeStateIDs: make(map[string][]string),
|
|
eventIDMap: make(map[string]gomatrixserverlib.Event),
|
|
bwExtrems: bwExtrems,
|
|
}
|
|
}
|
|
|
|
func (b *backfillRequester) StateIDsBeforeEvent(ctx context.Context, targetEvent gomatrixserverlib.HeaderedEvent) ([]string, error) {
|
|
b.eventIDMap[targetEvent.EventID()] = targetEvent.Unwrap()
|
|
if ids, ok := b.eventIDToBeforeStateIDs[targetEvent.EventID()]; ok {
|
|
return ids, nil
|
|
}
|
|
if len(targetEvent.PrevEventIDs()) == 0 && targetEvent.Type() == "m.room.create" && targetEvent.StateKeyEquals("") {
|
|
util.GetLogger(ctx).WithField("room_id", targetEvent.RoomID()).Info("Backfilled to the beginning of the room")
|
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = []string{}
|
|
return nil, nil
|
|
}
|
|
// if we have exactly 1 prev event and we know the state of the room at that prev event, then just roll forward the prev event.
|
|
// Else, we have to hit /state_ids because either we don't know the state at all at this event (new backwards extremity) or
|
|
// we don't know the result of state res to merge forks (2 or more prev_events)
|
|
if len(targetEvent.PrevEventIDs()) == 1 {
|
|
prevEventID := targetEvent.PrevEventIDs()[0]
|
|
prevEvent, ok := b.eventIDMap[prevEventID]
|
|
if !ok {
|
|
goto FederationHit
|
|
}
|
|
prevEventStateIDs, ok := b.eventIDToBeforeStateIDs[prevEventID]
|
|
if !ok {
|
|
goto FederationHit
|
|
}
|
|
newStateIDs := b.calculateNewStateIDs(targetEvent.Unwrap(), prevEvent, prevEventStateIDs)
|
|
if newStateIDs != nil {
|
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = newStateIDs
|
|
return newStateIDs, nil
|
|
}
|
|
// else we failed to calculate the new state, so fallthrough
|
|
}
|
|
|
|
FederationHit:
|
|
var lastErr error
|
|
logrus.WithField("event_id", targetEvent.EventID()).Info("Requesting /state_ids at event")
|
|
for _, srv := range b.servers { // hit any valid server
|
|
c := gomatrixserverlib.FederatedStateProvider{
|
|
FedClient: b.fedClient,
|
|
RememberAuthEvents: false,
|
|
Server: srv,
|
|
}
|
|
res, err := c.StateIDsBeforeEvent(ctx, targetEvent)
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = res
|
|
return res, nil
|
|
}
|
|
return nil, lastErr
|
|
}
|
|
|
|
func (b *backfillRequester) calculateNewStateIDs(targetEvent, prevEvent gomatrixserverlib.Event, prevEventStateIDs []string) []string {
|
|
newStateIDs := prevEventStateIDs[:]
|
|
if prevEvent.StateKey() == nil {
|
|
// state is the same as the previous event
|
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = newStateIDs
|
|
return newStateIDs
|
|
}
|
|
|
|
missingState := false // true if we are missing the info for a state event ID
|
|
foundEvent := false // true if we found a (type, state_key) match
|
|
// find which state ID to replace, if any
|
|
for i, id := range newStateIDs {
|
|
ev, ok := b.eventIDMap[id]
|
|
if !ok {
|
|
missingState = true
|
|
continue
|
|
}
|
|
// The state IDs BEFORE the target event are the state IDs BEFORE the prev_event PLUS the prev_event itself
|
|
if ev.Type() == prevEvent.Type() && ev.StateKey() != nil && *ev.StateKey() == *prevEvent.StateKey() {
|
|
newStateIDs[i] = prevEvent.EventID()
|
|
foundEvent = true
|
|
break
|
|
}
|
|
}
|
|
if !foundEvent && !missingState {
|
|
// we can be certain that this is new state
|
|
newStateIDs = append(newStateIDs, prevEvent.EventID())
|
|
foundEvent = true
|
|
}
|
|
|
|
if foundEvent {
|
|
b.eventIDToBeforeStateIDs[targetEvent.EventID()] = newStateIDs
|
|
return newStateIDs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *backfillRequester) StateBeforeEvent(ctx context.Context, roomVer gomatrixserverlib.RoomVersion,
|
|
event gomatrixserverlib.HeaderedEvent, eventIDs []string) (map[string]*gomatrixserverlib.Event, error) {
|
|
|
|
// try to fetch the events from the database first
|
|
events, err := b.ProvideEvents(roomVer, eventIDs)
|
|
if err != nil {
|
|
// non-fatal, fallthrough
|
|
logrus.WithError(err).Info("Failed to fetch events")
|
|
} else {
|
|
logrus.Infof("Fetched %d/%d events from the database", len(events), len(eventIDs))
|
|
if len(events) == len(eventIDs) {
|
|
result := make(map[string]*gomatrixserverlib.Event)
|
|
for i := range events {
|
|
result[events[i].EventID()] = &events[i]
|
|
b.eventIDMap[events[i].EventID()] = events[i]
|
|
}
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
c := gomatrixserverlib.FederatedStateProvider{
|
|
FedClient: b.fedClient,
|
|
RememberAuthEvents: false,
|
|
Server: b.servers[0],
|
|
}
|
|
result, err := c.StateBeforeEvent(ctx, roomVer, event, eventIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for eventID, ev := range result {
|
|
b.eventIDMap[eventID] = *ev
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ServersAtEvent is called when trying to determine which server to request from.
|
|
// It returns a list of servers which can be queried for backfill requests. These servers
|
|
// will be servers that are in the room already. The entries at the beginning are preferred servers
|
|
// and will be tried first. An empty list will fail the request.
|
|
func (b *backfillRequester) ServersAtEvent(ctx context.Context, roomID, eventID string) []gomatrixserverlib.ServerName {
|
|
// eventID will be a prev_event ID of a backwards extremity, meaning we will not have a database entry for it. Instead, use
|
|
// its successor, so look it up.
|
|
successor := ""
|
|
FindSuccessor:
|
|
for sucID, prevEventIDs := range b.bwExtrems {
|
|
for _, pe := range prevEventIDs {
|
|
if pe == eventID {
|
|
successor = sucID
|
|
break FindSuccessor
|
|
}
|
|
}
|
|
}
|
|
if successor == "" {
|
|
logrus.WithField("event_id", eventID).Error("ServersAtEvent: failed to find successor of this event to determine room state")
|
|
return nil
|
|
}
|
|
eventID = successor
|
|
|
|
// getMembershipsBeforeEventNID requires a NID, so retrieving the NID for
|
|
// the event is necessary.
|
|
NIDs, err := b.db.EventNIDs(ctx, []string{eventID})
|
|
if err != nil {
|
|
logrus.WithField("event_id", eventID).WithError(err).Error("ServersAtEvent: failed to get event NID for event")
|
|
return nil
|
|
}
|
|
|
|
stateEntries, err := stateBeforeEvent(ctx, b.db, NIDs[eventID])
|
|
if err != nil {
|
|
logrus.WithField("event_id", eventID).WithError(err).Error("ServersAtEvent: failed to load state before event")
|
|
return nil
|
|
}
|
|
|
|
// possibly return all joined servers depending on history visiblity
|
|
memberEventsFromVis, err := joinEventsFromHistoryVisibility(ctx, b.db, roomID, stateEntries)
|
|
if err != nil {
|
|
logrus.WithError(err).Error("ServersAtEvent: failed calculate servers from history visibility rules")
|
|
return nil
|
|
}
|
|
logrus.Infof("ServersAtEvent including %d current events from history visibility", len(memberEventsFromVis))
|
|
|
|
// Retrieve all "m.room.member" state events of "join" membership, which
|
|
// contains the list of users in the room before the event, therefore all
|
|
// the servers in it at that moment.
|
|
memberEvents, err := getMembershipsAtState(ctx, b.db, stateEntries, true)
|
|
if err != nil {
|
|
logrus.WithField("event_id", eventID).WithError(err).Error("ServersAtEvent: failed to get memberships before event")
|
|
return nil
|
|
}
|
|
memberEvents = append(memberEvents, memberEventsFromVis...)
|
|
|
|
// Store the server names in a temporary map to avoid duplicates.
|
|
serverSet := make(map[gomatrixserverlib.ServerName]bool)
|
|
for _, event := range memberEvents {
|
|
serverSet[event.Origin()] = true
|
|
}
|
|
var servers []gomatrixserverlib.ServerName
|
|
for server := range serverSet {
|
|
if server == b.thisServer {
|
|
continue
|
|
}
|
|
servers = append(servers, server)
|
|
}
|
|
b.servers = servers
|
|
return servers
|
|
}
|
|
|
|
// Backfill performs a backfill request to the given server.
|
|
// https://matrix.org/docs/spec/server_server/latest#get-matrix-federation-v1-backfill-roomid
|
|
func (b *backfillRequester) Backfill(ctx context.Context, server gomatrixserverlib.ServerName, roomID string,
|
|
fromEventIDs []string, limit int) (*gomatrixserverlib.Transaction, error) {
|
|
|
|
tx, err := b.fedClient.Backfill(ctx, server, roomID, limit, fromEventIDs)
|
|
return &tx, err
|
|
}
|
|
|
|
func (b *backfillRequester) ProvideEvents(roomVer gomatrixserverlib.RoomVersion, eventIDs []string) ([]gomatrixserverlib.Event, error) {
|
|
ctx := context.Background()
|
|
nidMap, err := b.db.EventNIDs(ctx, eventIDs)
|
|
if err != nil {
|
|
logrus.WithError(err).WithField("event_ids", eventIDs).Error("Failed to find events")
|
|
return nil, err
|
|
}
|
|
eventNIDs := make([]types.EventNID, len(nidMap))
|
|
i := 0
|
|
for _, nid := range nidMap {
|
|
eventNIDs[i] = nid
|
|
i++
|
|
}
|
|
eventsWithNids, err := b.db.Events(ctx, eventNIDs)
|
|
if err != nil {
|
|
logrus.WithError(err).WithField("event_nids", eventNIDs).Error("Failed to load events")
|
|
return nil, err
|
|
}
|
|
events := make([]gomatrixserverlib.Event, len(eventsWithNids))
|
|
for i := range eventsWithNids {
|
|
events[i] = eventsWithNids[i].Event
|
|
}
|
|
return events, nil
|
|
}
|
|
|
|
// joinEventsFromHistoryVisibility returns all CURRENTLY joined members if the provided state indicated a 'shared' history visibility.
|
|
// TODO: Long term we probably want a history_visibility table which stores eventNID | visibility_enum so we can just
|
|
// pull all events and then filter by that table.
|
|
func joinEventsFromHistoryVisibility(
|
|
ctx context.Context, db storage.Database, roomID string, stateEntries []types.StateEntry) ([]types.Event, error) {
|
|
|
|
var eventNIDs []types.EventNID
|
|
for _, entry := range stateEntries {
|
|
// Filter the events to retrieve to only keep the membership events
|
|
if entry.EventTypeNID == types.MRoomHistoryVisibilityNID && entry.EventStateKeyNID == types.EmptyStateKeyNID {
|
|
eventNIDs = append(eventNIDs, entry.EventNID)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Get all of the events in this state
|
|
stateEvents, err := db.Events(ctx, eventNIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
events := make([]gomatrixserverlib.Event, len(stateEvents))
|
|
for i := range stateEvents {
|
|
events[i] = stateEvents[i].Event
|
|
}
|
|
visibility := auth.HistoryVisibilityForRoom(events)
|
|
if visibility != "shared" {
|
|
logrus.Infof("ServersAtEvent history visibility not shared: %s", visibility)
|
|
return nil, nil
|
|
}
|
|
// get joined members
|
|
roomNID, err := db.RoomNID(ctx, roomID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
joinEventNIDs, err := db.GetMembershipEventNIDsForRoom(ctx, roomNID, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return db.Events(ctx, joinEventNIDs)
|
|
}
|