mirror of
https://github.com/1f349/dendrite.git
synced 2024-11-25 13:11:37 +00:00
79072c3dcd
Based on #3340 This adds a `/_synapse/admin/v1/event_reports` endpoint, the same Synapse has. This way existing tools also work with Dendrite. Given this is already getting huge (even though many test lines), splitting this into two PRs. (The next adds "getting one report" and "deleting reports") [skip ci]
2125 lines
70 KiB
Go
2125 lines
70 KiB
Go
package shared
|
||
|
||
import (
|
||
"context"
|
||
"crypto/ed25519"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"sort"
|
||
|
||
"github.com/matrix-org/dendrite/internal/eventutil"
|
||
"github.com/matrix-org/dendrite/roomserver/api"
|
||
"github.com/matrix-org/gomatrixserverlib"
|
||
"github.com/matrix-org/gomatrixserverlib/spec"
|
||
"github.com/matrix-org/util"
|
||
"github.com/sirupsen/logrus"
|
||
"github.com/tidwall/gjson"
|
||
|
||
"github.com/matrix-org/dendrite/internal/caching"
|
||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||
"github.com/matrix-org/dendrite/roomserver/state"
|
||
"github.com/matrix-org/dendrite/roomserver/storage/tables"
|
||
"github.com/matrix-org/dendrite/roomserver/types"
|
||
)
|
||
|
||
// Ideally, when we have both events we should redact the event JSON and forget about the redaction, but we currently
|
||
// don't because the redaction code is brand new. When we are more certain that redactions don't misbehave or are
|
||
// vulnerable to attacks from remote servers (e.g a server bypassing event auth rules shouldn't redact our data)
|
||
// then we should flip this to true. This will mean redactions /actually delete information irretrievably/ which
|
||
// will be necessary for compliance with the law. Note that downstream components (syncapi) WILL delete information
|
||
// in their database on receipt of a redaction. Also note that we still modify the event JSON to set the field
|
||
// unsigned.redacted_because - we just don't clear out the content fields yet.
|
||
const redactionsArePermanent = true
|
||
|
||
type Database struct {
|
||
DB *sql.DB
|
||
EventDatabase
|
||
Cache caching.RoomServerCaches
|
||
Writer sqlutil.Writer
|
||
RoomsTable tables.Rooms
|
||
StateSnapshotTable tables.StateSnapshot
|
||
StateBlockTable tables.StateBlock
|
||
RoomAliasesTable tables.RoomAliases
|
||
InvitesTable tables.Invites
|
||
MembershipTable tables.Membership
|
||
PublishedTable tables.Published
|
||
Purge tables.Purge
|
||
UserRoomKeyTable tables.UserRoomKeys
|
||
GetRoomUpdaterFn func(ctx context.Context, roomInfo *types.RoomInfo) (*RoomUpdater, error)
|
||
}
|
||
|
||
// EventDatabase contains all tables needed to work with events
|
||
type EventDatabase struct {
|
||
DB *sql.DB
|
||
Cache caching.RoomServerCaches
|
||
Writer sqlutil.Writer
|
||
EventsTable tables.Events
|
||
EventJSONTable tables.EventJSON
|
||
EventTypesTable tables.EventTypes
|
||
EventStateKeysTable tables.EventStateKeys
|
||
PrevEventsTable tables.PreviousEvents
|
||
RedactionsTable tables.Redactions
|
||
ReportedEventsTable tables.ReportedEvents
|
||
}
|
||
|
||
func (d *Database) SupportsConcurrentRoomInputs() bool {
|
||
return true
|
||
}
|
||
|
||
func (d *Database) GetMembershipForHistoryVisibility(
|
||
ctx context.Context, userNID types.EventStateKeyNID, roomInfo *types.RoomInfo, eventIDs ...string,
|
||
) (map[string]*types.HeaderedEvent, error) {
|
||
return d.StateSnapshotTable.BulkSelectMembershipForHistoryVisibility(ctx, nil, userNID, roomInfo, eventIDs...)
|
||
}
|
||
|
||
func (d *EventDatabase) EventTypeNIDs(
|
||
ctx context.Context, eventTypes []string,
|
||
) (map[string]types.EventTypeNID, error) {
|
||
return d.eventTypeNIDs(ctx, nil, eventTypes)
|
||
}
|
||
|
||
func (d *EventDatabase) eventTypeNIDs(
|
||
ctx context.Context, txn *sql.Tx, eventTypes []string,
|
||
) (map[string]types.EventTypeNID, error) {
|
||
result := make(map[string]types.EventTypeNID)
|
||
// first try the cache
|
||
fetchEventTypes := make([]string, 0, len(eventTypes))
|
||
for _, eventType := range eventTypes {
|
||
eventTypeNID, ok := d.Cache.GetEventTypeKey(eventType)
|
||
if ok {
|
||
result[eventType] = eventTypeNID
|
||
continue
|
||
}
|
||
fetchEventTypes = append(fetchEventTypes, eventType)
|
||
}
|
||
if len(fetchEventTypes) > 0 {
|
||
nids, err := d.EventTypesTable.BulkSelectEventTypeNID(ctx, txn, fetchEventTypes)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for eventType, nid := range nids {
|
||
result[eventType] = nid
|
||
d.Cache.StoreEventTypeKey(nid, eventType)
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (d *EventDatabase) EventStateKeys(
|
||
ctx context.Context, eventStateKeyNIDs []types.EventStateKeyNID,
|
||
) (map[types.EventStateKeyNID]string, error) {
|
||
result := make(map[types.EventStateKeyNID]string, len(eventStateKeyNIDs))
|
||
fetch := make([]types.EventStateKeyNID, 0, len(eventStateKeyNIDs))
|
||
for _, nid := range eventStateKeyNIDs {
|
||
if key, ok := d.Cache.GetEventStateKey(nid); ok {
|
||
result[nid] = key
|
||
} else {
|
||
fetch = append(fetch, nid)
|
||
}
|
||
}
|
||
if len(fetch) > 0 {
|
||
fromDB, err := d.EventStateKeysTable.BulkSelectEventStateKey(ctx, nil, fetch)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for nid, key := range fromDB {
|
||
result[nid] = key
|
||
d.Cache.StoreEventStateKey(nid, key)
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (d *EventDatabase) EventStateKeyNIDs(
|
||
ctx context.Context, eventStateKeys []string,
|
||
) (map[string]types.EventStateKeyNID, error) {
|
||
return d.eventStateKeyNIDs(ctx, nil, eventStateKeys)
|
||
}
|
||
|
||
func (d *EventDatabase) eventStateKeyNIDs(
|
||
ctx context.Context, txn *sql.Tx, eventStateKeys []string,
|
||
) (map[string]types.EventStateKeyNID, error) {
|
||
result := make(map[string]types.EventStateKeyNID)
|
||
eventStateKeys = util.UniqueStrings(eventStateKeys)
|
||
// first ask the cache about these keys
|
||
fetchEventStateKeys := make([]string, 0, len(eventStateKeys))
|
||
for _, eventStateKey := range eventStateKeys {
|
||
eventStateKeyNID, ok := d.Cache.GetEventStateKeyNID(eventStateKey)
|
||
if ok {
|
||
result[eventStateKey] = eventStateKeyNID
|
||
continue
|
||
}
|
||
fetchEventStateKeys = append(fetchEventStateKeys, eventStateKey)
|
||
}
|
||
|
||
if len(fetchEventStateKeys) > 0 {
|
||
nids, err := d.EventStateKeysTable.BulkSelectEventStateKeyNID(ctx, txn, fetchEventStateKeys)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for eventStateKey, nid := range nids {
|
||
result[eventStateKey] = nid
|
||
d.Cache.StoreEventStateKey(nid, eventStateKey)
|
||
}
|
||
}
|
||
|
||
// We received some nids, but are still missing some, work out which and create them
|
||
if len(eventStateKeys) > len(result) {
|
||
var nid types.EventStateKeyNID
|
||
var err error
|
||
err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error {
|
||
for _, eventStateKey := range eventStateKeys {
|
||
if _, ok := result[eventStateKey]; ok {
|
||
continue
|
||
}
|
||
|
||
nid, err = d.assignStateKeyNID(ctx, txn, eventStateKey)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
result[eventStateKey] = nid
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (d *EventDatabase) StateEntriesForEventIDs(
|
||
ctx context.Context, eventIDs []string, excludeRejected bool,
|
||
) ([]types.StateEntry, error) {
|
||
return d.EventsTable.BulkSelectStateEventByID(ctx, nil, eventIDs, excludeRejected)
|
||
}
|
||
|
||
func (d *Database) StateEntriesForTuples(
|
||
ctx context.Context,
|
||
stateBlockNIDs []types.StateBlockNID,
|
||
stateKeyTuples []types.StateKeyTuple,
|
||
) ([]types.StateEntryList, error) {
|
||
return d.stateEntriesForTuples(ctx, nil, stateBlockNIDs, stateKeyTuples)
|
||
}
|
||
|
||
func (d *Database) stateEntriesForTuples(
|
||
ctx context.Context, txn *sql.Tx,
|
||
stateBlockNIDs []types.StateBlockNID,
|
||
stateKeyTuples []types.StateKeyTuple,
|
||
) ([]types.StateEntryList, error) {
|
||
entries, err := d.StateBlockTable.BulkSelectStateBlockEntries(
|
||
ctx, txn, stateBlockNIDs,
|
||
)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("d.StateBlockTable.BulkSelectStateBlockEntries: %w", err)
|
||
}
|
||
lists := []types.StateEntryList{}
|
||
for i, entry := range entries {
|
||
entries, err := d.EventsTable.BulkSelectStateEventByNID(ctx, txn, entry, stateKeyTuples)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("d.EventsTable.BulkSelectStateEventByNID: %w", err)
|
||
}
|
||
lists = append(lists, types.StateEntryList{
|
||
StateBlockNID: stateBlockNIDs[i],
|
||
StateEntries: entries,
|
||
})
|
||
}
|
||
return lists, nil
|
||
}
|
||
|
||
func (d *Database) RoomInfoByNID(ctx context.Context, roomNID types.RoomNID) (*types.RoomInfo, error) {
|
||
roomIDs, err := d.RoomsTable.BulkSelectRoomIDs(ctx, nil, []types.RoomNID{roomNID})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(roomIDs) == 0 {
|
||
return nil, fmt.Errorf("room does not exist")
|
||
}
|
||
return d.roomInfo(ctx, nil, roomIDs[0])
|
||
}
|
||
|
||
func (d *Database) RoomInfo(ctx context.Context, roomID string) (*types.RoomInfo, error) {
|
||
return d.roomInfo(ctx, nil, roomID)
|
||
}
|
||
|
||
func (d *Database) roomInfo(ctx context.Context, txn *sql.Tx, roomID string) (*types.RoomInfo, error) {
|
||
roomInfo, err := d.RoomsTable.SelectRoomInfo(ctx, txn, roomID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if roomInfo != nil {
|
||
d.Cache.StoreRoomServerRoomID(roomInfo.RoomNID, roomID)
|
||
d.Cache.StoreRoomVersion(roomID, roomInfo.RoomVersion)
|
||
}
|
||
return roomInfo, err
|
||
}
|
||
|
||
func (d *Database) AddState(
|
||
ctx context.Context,
|
||
roomNID types.RoomNID,
|
||
stateBlockNIDs []types.StateBlockNID,
|
||
state []types.StateEntry,
|
||
) (stateNID types.StateSnapshotNID, err error) {
|
||
return d.addState(ctx, nil, roomNID, stateBlockNIDs, state)
|
||
}
|
||
|
||
func (d *Database) addState(
|
||
ctx context.Context, txn *sql.Tx,
|
||
roomNID types.RoomNID,
|
||
stateBlockNIDs []types.StateBlockNID,
|
||
state []types.StateEntry,
|
||
) (stateNID types.StateSnapshotNID, err error) {
|
||
if len(stateBlockNIDs) > 0 && len(state) > 0 {
|
||
// Check to see if the event already appears in any of the existing state
|
||
// blocks. If it does then we should not add it again, as this will just
|
||
// result in excess state blocks and snapshots.
|
||
// TODO: Investigate why this is happening - probably input_events.go!
|
||
blocks, berr := d.StateBlockTable.BulkSelectStateBlockEntries(ctx, txn, stateBlockNIDs)
|
||
if berr != nil {
|
||
return 0, fmt.Errorf("d.StateBlockTable.BulkSelectStateBlockEntries: %w", berr)
|
||
}
|
||
var found bool
|
||
for i := len(state) - 1; i >= 0; i-- {
|
||
found = false
|
||
blocksLoop:
|
||
for _, events := range blocks {
|
||
for _, event := range events {
|
||
if state[i].EventNID == event {
|
||
found = true
|
||
break blocksLoop
|
||
}
|
||
}
|
||
}
|
||
if found {
|
||
state = append(state[:i], state[i+1:]...)
|
||
}
|
||
}
|
||
}
|
||
err = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error {
|
||
if len(state) > 0 {
|
||
// If there's any state left to add then let's add new blocks.
|
||
var stateBlockNID types.StateBlockNID
|
||
stateBlockNID, err = d.StateBlockTable.BulkInsertStateData(ctx, txn, state)
|
||
if err != nil {
|
||
return fmt.Errorf("d.StateBlockTable.BulkInsertStateData: %w", err)
|
||
}
|
||
stateBlockNIDs = append(stateBlockNIDs[:len(stateBlockNIDs):len(stateBlockNIDs)], stateBlockNID)
|
||
}
|
||
stateNID, err = d.StateSnapshotTable.InsertState(ctx, txn, roomNID, stateBlockNIDs)
|
||
if err != nil {
|
||
return fmt.Errorf("d.StateSnapshotTable.InsertState: %w", err)
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return 0, fmt.Errorf("d.Writer.Do: %w", err)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *EventDatabase) EventNIDs(
|
||
ctx context.Context, eventIDs []string,
|
||
) (map[string]types.EventMetadata, error) {
|
||
return d.eventNIDs(ctx, nil, eventIDs, NoFilter)
|
||
}
|
||
|
||
type UnsentFilter bool
|
||
|
||
const (
|
||
NoFilter UnsentFilter = false
|
||
FilterUnsentOnly UnsentFilter = true
|
||
)
|
||
|
||
func (d *EventDatabase) eventNIDs(
|
||
ctx context.Context, txn *sql.Tx, eventIDs []string, filter UnsentFilter,
|
||
) (map[string]types.EventMetadata, error) {
|
||
switch filter {
|
||
case FilterUnsentOnly:
|
||
return d.EventsTable.BulkSelectUnsentEventNID(ctx, txn, eventIDs)
|
||
case NoFilter:
|
||
return d.EventsTable.BulkSelectEventNID(ctx, txn, eventIDs)
|
||
default:
|
||
panic("impossible case")
|
||
}
|
||
}
|
||
|
||
func (d *EventDatabase) SetState(
|
||
ctx context.Context, eventNID types.EventNID, stateNID types.StateSnapshotNID,
|
||
) error {
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
return d.EventsTable.UpdateEventState(ctx, txn, eventNID, stateNID)
|
||
})
|
||
}
|
||
|
||
func (d *EventDatabase) StateAtEventIDs(
|
||
ctx context.Context, eventIDs []string,
|
||
) ([]types.StateAtEvent, error) {
|
||
return d.EventsTable.BulkSelectStateAtEventByID(ctx, nil, eventIDs)
|
||
}
|
||
|
||
func (d *EventDatabase) SnapshotNIDFromEventID(
|
||
ctx context.Context, eventID string,
|
||
) (types.StateSnapshotNID, error) {
|
||
return d.snapshotNIDFromEventID(ctx, nil, eventID)
|
||
}
|
||
|
||
func (d *EventDatabase) snapshotNIDFromEventID(
|
||
ctx context.Context, txn *sql.Tx, eventID string,
|
||
) (types.StateSnapshotNID, error) {
|
||
_, stateNID, err := d.EventsTable.SelectEvent(ctx, txn, eventID)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if stateNID == 0 {
|
||
return 0, sql.ErrNoRows // effectively there's no state entry
|
||
}
|
||
return stateNID, err
|
||
}
|
||
|
||
func (d *EventDatabase) EventIDs(
|
||
ctx context.Context, eventNIDs []types.EventNID,
|
||
) (map[types.EventNID]string, error) {
|
||
return d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
}
|
||
|
||
func (d *EventDatabase) EventsFromIDs(ctx context.Context, roomInfo *types.RoomInfo, eventIDs []string) ([]types.Event, error) {
|
||
return d.eventsFromIDs(ctx, nil, roomInfo, eventIDs, NoFilter)
|
||
}
|
||
|
||
func (d *EventDatabase) eventsFromIDs(ctx context.Context, txn *sql.Tx, roomInfo *types.RoomInfo, eventIDs []string, filter UnsentFilter) ([]types.Event, error) {
|
||
nidMap, err := d.eventNIDs(ctx, txn, eventIDs, filter)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var nids []types.EventNID
|
||
for _, nid := range nidMap {
|
||
nids = append(nids, nid.EventNID)
|
||
}
|
||
|
||
if roomInfo == nil {
|
||
return nil, types.ErrorInvalidRoomInfo
|
||
}
|
||
return d.events(ctx, txn, roomInfo.RoomVersion, nids)
|
||
}
|
||
|
||
func (d *Database) LatestEventIDs(ctx context.Context, roomNID types.RoomNID) (references []string, currentStateSnapshotNID types.StateSnapshotNID, depth int64, err error) {
|
||
var eventNIDs []types.EventNID
|
||
eventNIDs, currentStateSnapshotNID, err = d.RoomsTable.SelectLatestEventNIDs(ctx, nil, roomNID)
|
||
if err != nil {
|
||
return
|
||
}
|
||
eventNIDMap, err := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
return
|
||
}
|
||
depth, err = d.EventsTable.SelectMaxEventDepth(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
return
|
||
}
|
||
for _, eventID := range eventNIDMap {
|
||
references = append(references, eventID)
|
||
}
|
||
return
|
||
}
|
||
|
||
func (d *Database) StateBlockNIDs(
|
||
ctx context.Context, stateNIDs []types.StateSnapshotNID,
|
||
) ([]types.StateBlockNIDList, error) {
|
||
return d.stateBlockNIDs(ctx, nil, stateNIDs)
|
||
}
|
||
|
||
func (d *Database) stateBlockNIDs(
|
||
ctx context.Context, txn *sql.Tx, stateNIDs []types.StateSnapshotNID,
|
||
) ([]types.StateBlockNIDList, error) {
|
||
return d.StateSnapshotTable.BulkSelectStateBlockNIDs(ctx, txn, stateNIDs)
|
||
}
|
||
|
||
func (d *Database) StateEntries(
|
||
ctx context.Context, stateBlockNIDs []types.StateBlockNID,
|
||
) ([]types.StateEntryList, error) {
|
||
return d.stateEntries(ctx, nil, stateBlockNIDs)
|
||
}
|
||
|
||
func (d *Database) stateEntries(
|
||
ctx context.Context, txn *sql.Tx, stateBlockNIDs []types.StateBlockNID,
|
||
) ([]types.StateEntryList, error) {
|
||
entries, err := d.StateBlockTable.BulkSelectStateBlockEntries(
|
||
ctx, txn, stateBlockNIDs,
|
||
)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("d.StateBlockTable.BulkSelectStateBlockEntries: %w", err)
|
||
}
|
||
lists := make([]types.StateEntryList, 0, len(entries))
|
||
for i, entry := range entries {
|
||
eventNIDs, err := d.EventsTable.BulkSelectStateEventByNID(ctx, txn, entry, nil)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("d.EventsTable.BulkSelectStateEventByNID: %w", err)
|
||
}
|
||
lists = append(lists, types.StateEntryList{
|
||
StateBlockNID: stateBlockNIDs[i],
|
||
StateEntries: eventNIDs,
|
||
})
|
||
}
|
||
return lists, nil
|
||
}
|
||
|
||
func (d *Database) SetRoomAlias(ctx context.Context, alias string, roomID string, creatorUserID string) error {
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
return d.RoomAliasesTable.InsertRoomAlias(ctx, txn, alias, roomID, creatorUserID)
|
||
})
|
||
}
|
||
|
||
func (d *Database) GetRoomIDForAlias(ctx context.Context, alias string) (string, error) {
|
||
return d.RoomAliasesTable.SelectRoomIDFromAlias(ctx, nil, alias)
|
||
}
|
||
|
||
func (d *Database) GetAliasesForRoomID(ctx context.Context, roomID string) ([]string, error) {
|
||
return d.RoomAliasesTable.SelectAliasesFromRoomID(ctx, nil, roomID)
|
||
}
|
||
|
||
func (d *Database) GetCreatorIDForAlias(
|
||
ctx context.Context, alias string,
|
||
) (string, error) {
|
||
return d.RoomAliasesTable.SelectCreatorIDFromAlias(ctx, nil, alias)
|
||
}
|
||
|
||
func (d *Database) RemoveRoomAlias(ctx context.Context, alias string) error {
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
return d.RoomAliasesTable.DeleteRoomAlias(ctx, txn, alias)
|
||
})
|
||
}
|
||
|
||
func (d *Database) GetMembership(ctx context.Context, roomNID types.RoomNID, requestSenderID spec.SenderID) (membershipEventNID types.EventNID, stillInRoom, isRoomforgotten bool, err error) {
|
||
var requestSenderUserNID types.EventStateKeyNID
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
requestSenderUserNID, err = d.assignStateKeyNID(ctx, txn, string(requestSenderID))
|
||
return err
|
||
})
|
||
if err != nil {
|
||
return 0, false, false, fmt.Errorf("d.assignStateKeyNID: %w", err)
|
||
}
|
||
|
||
senderMembershipEventNID, senderMembership, isRoomforgotten, err :=
|
||
d.MembershipTable.SelectMembershipFromRoomAndTarget(
|
||
ctx, nil, roomNID, requestSenderUserNID,
|
||
)
|
||
if err == sql.ErrNoRows {
|
||
// The user has never been a member of that room
|
||
return 0, false, false, nil
|
||
} else if err != nil {
|
||
return
|
||
}
|
||
|
||
return senderMembershipEventNID, senderMembership == tables.MembershipStateJoin, isRoomforgotten, nil
|
||
}
|
||
|
||
func (d *Database) GetMembershipEventNIDsForRoom(
|
||
ctx context.Context, roomNID types.RoomNID, joinOnly bool, localOnly bool,
|
||
) ([]types.EventNID, error) {
|
||
return d.getMembershipEventNIDsForRoom(ctx, nil, roomNID, joinOnly, localOnly)
|
||
}
|
||
|
||
func (d *Database) getMembershipEventNIDsForRoom(
|
||
ctx context.Context, txn *sql.Tx, roomNID types.RoomNID, joinOnly bool, localOnly bool,
|
||
) ([]types.EventNID, error) {
|
||
if joinOnly {
|
||
return d.MembershipTable.SelectMembershipsFromRoomAndMembership(
|
||
ctx, txn, roomNID, tables.MembershipStateJoin, localOnly,
|
||
)
|
||
}
|
||
|
||
return d.MembershipTable.SelectMembershipsFromRoom(ctx, txn, roomNID, localOnly)
|
||
}
|
||
|
||
func (d *Database) GetInvitesForUser(
|
||
ctx context.Context,
|
||
roomNID types.RoomNID,
|
||
targetUserNID types.EventStateKeyNID,
|
||
) (senderUserIDs []types.EventStateKeyNID, eventIDs []string, inviteEventJSON []byte, err error) {
|
||
return d.InvitesTable.SelectInviteActiveForUserInRoom(ctx, nil, targetUserNID, roomNID)
|
||
}
|
||
|
||
func (d *EventDatabase) Events(ctx context.Context, roomVersion gomatrixserverlib.RoomVersion, eventNIDs []types.EventNID) ([]types.Event, error) {
|
||
return d.events(ctx, nil, roomVersion, eventNIDs)
|
||
}
|
||
|
||
func (d *EventDatabase) events(
|
||
ctx context.Context, txn *sql.Tx, roomVersion gomatrixserverlib.RoomVersion, inputEventNIDs types.EventNIDs,
|
||
) ([]types.Event, error) {
|
||
sort.Sort(inputEventNIDs)
|
||
events := make(map[types.EventNID]gomatrixserverlib.PDU, len(inputEventNIDs))
|
||
eventNIDs := make([]types.EventNID, 0, len(inputEventNIDs))
|
||
for _, nid := range inputEventNIDs {
|
||
if event, ok := d.Cache.GetRoomServerEvent(nid); ok && event != nil {
|
||
events[nid] = event
|
||
} else {
|
||
eventNIDs = append(eventNIDs, nid)
|
||
}
|
||
}
|
||
// If we don't need to get any events from the database, short circuit now
|
||
if len(eventNIDs) == 0 {
|
||
results := make([]types.Event, 0, len(inputEventNIDs))
|
||
for _, nid := range inputEventNIDs {
|
||
event, ok := events[nid]
|
||
if !ok || event == nil {
|
||
return nil, fmt.Errorf("event %d missing", nid)
|
||
}
|
||
results = append(results, types.Event{
|
||
EventNID: nid,
|
||
PDU: event,
|
||
})
|
||
}
|
||
if !redactionsArePermanent {
|
||
d.applyRedactions(results)
|
||
}
|
||
return results, nil
|
||
}
|
||
eventJSONs, err := d.EventJSONTable.BulkSelectEventJSON(ctx, txn, eventNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
eventIDs, err := d.EventsTable.BulkSelectEventID(ctx, txn, eventNIDs)
|
||
if err != nil {
|
||
eventIDs = map[types.EventNID]string{}
|
||
}
|
||
|
||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVersion)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, eventJSON := range eventJSONs {
|
||
redacted := gjson.GetBytes(eventJSON.EventJSON, "unsigned.redacted_because").Exists()
|
||
events[eventJSON.EventNID], err = verImpl.NewEventFromTrustedJSONWithEventID(
|
||
eventIDs[eventJSON.EventNID], eventJSON.EventJSON, redacted,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if event := events[eventJSON.EventNID]; event != nil {
|
||
d.Cache.StoreRoomServerEvent(eventJSON.EventNID, &types.HeaderedEvent{PDU: event})
|
||
}
|
||
}
|
||
results := make([]types.Event, 0, len(inputEventNIDs))
|
||
for _, nid := range inputEventNIDs {
|
||
event, ok := events[nid]
|
||
if !ok || event == nil {
|
||
return nil, fmt.Errorf("event %d missing", nid)
|
||
}
|
||
results = append(results, types.Event{
|
||
EventNID: nid,
|
||
PDU: event,
|
||
})
|
||
}
|
||
if !redactionsArePermanent {
|
||
d.applyRedactions(results)
|
||
}
|
||
return results, nil
|
||
}
|
||
|
||
func (d *Database) BulkSelectSnapshotsFromEventIDs(
|
||
ctx context.Context, eventIDs []string,
|
||
) (map[types.StateSnapshotNID][]string, error) {
|
||
return d.EventsTable.BulkSelectSnapshotsFromEventIDs(ctx, nil, eventIDs)
|
||
}
|
||
|
||
func (d *Database) MembershipUpdater(
|
||
ctx context.Context, roomID, targetUserID string,
|
||
targetLocal bool, roomVersion gomatrixserverlib.RoomVersion,
|
||
) (*MembershipUpdater, error) {
|
||
txn, err := d.DB.Begin()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var updater *MembershipUpdater
|
||
_ = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error {
|
||
updater, err = NewMembershipUpdater(ctx, d, txn, roomID, targetUserID, targetLocal, roomVersion)
|
||
return err
|
||
})
|
||
return updater, err
|
||
}
|
||
|
||
func (d *Database) GetRoomUpdater(
|
||
ctx context.Context, roomInfo *types.RoomInfo,
|
||
) (*RoomUpdater, error) {
|
||
if d.GetRoomUpdaterFn != nil {
|
||
return d.GetRoomUpdaterFn(ctx, roomInfo)
|
||
}
|
||
txn, err := d.DB.Begin()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var updater *RoomUpdater
|
||
_ = d.Writer.Do(d.DB, txn, func(txn *sql.Tx) error {
|
||
updater, err = NewRoomUpdater(ctx, d, txn, roomInfo)
|
||
return err
|
||
})
|
||
return updater, err
|
||
}
|
||
|
||
func (d *Database) IsEventRejected(ctx context.Context, roomNID types.RoomNID, eventID string) (bool, error) {
|
||
return d.EventsTable.SelectEventRejected(ctx, nil, roomNID, eventID)
|
||
}
|
||
|
||
func (d *Database) AssignRoomNID(ctx context.Context, roomID spec.RoomID, roomVersion gomatrixserverlib.RoomVersion) (roomNID types.RoomNID, err error) {
|
||
// This should already be checked, let's check it anyway.
|
||
_, err = gomatrixserverlib.GetRoomVersion(roomVersion)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomNID, err = d.assignRoomNID(ctx, txn, roomID.String(), roomVersion)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
// Not setting caches, as assignRoomNID already does this
|
||
return roomNID, err
|
||
}
|
||
|
||
// GetOrCreateRoomInfo gets or creates a new RoomInfo, which is only safe to use with functions only needing a roomVersion or roomNID.
|
||
func (d *Database) GetOrCreateRoomInfo(ctx context.Context, event gomatrixserverlib.PDU) (roomInfo *types.RoomInfo, err error) {
|
||
// Get the default room version. If the client doesn't supply a room_version
|
||
// then we will use our configured default to create the room.
|
||
// https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-createroom
|
||
// Note that the below logic depends on the m.room.create event being the
|
||
// first event that is persisted to the database when creating or joining a
|
||
// room.
|
||
var roomVersion gomatrixserverlib.RoomVersion
|
||
if roomVersion, err = extractRoomVersionFromCreateEvent(event); err != nil {
|
||
return nil, fmt.Errorf("extractRoomVersionFromCreateEvent: %w", err)
|
||
}
|
||
|
||
roomNID, nidOK := d.Cache.GetRoomServerRoomNID(event.RoomID().String())
|
||
cachedRoomVersion, versionOK := d.Cache.GetRoomVersion(event.RoomID().String())
|
||
// if we found both, the roomNID and version in our cache, no need to query the database
|
||
if nidOK && versionOK {
|
||
return &types.RoomInfo{
|
||
RoomNID: roomNID,
|
||
RoomVersion: cachedRoomVersion,
|
||
}, nil
|
||
}
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomNID, err = d.assignRoomNID(ctx, txn, event.RoomID().String(), roomVersion)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
})
|
||
if roomVersion != "" {
|
||
d.Cache.StoreRoomVersion(event.RoomID().String(), roomVersion)
|
||
}
|
||
return &types.RoomInfo{
|
||
RoomVersion: roomVersion,
|
||
RoomNID: roomNID,
|
||
}, err
|
||
}
|
||
|
||
func (d *Database) GetRoomVersion(ctx context.Context, roomID string) (gomatrixserverlib.RoomVersion, error) {
|
||
cachedRoomVersion, versionOK := d.Cache.GetRoomVersion(roomID)
|
||
if versionOK {
|
||
return cachedRoomVersion, nil
|
||
}
|
||
|
||
roomInfo, err := d.RoomInfo(ctx, roomID)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if roomInfo == nil {
|
||
return "", nil
|
||
}
|
||
return roomInfo.RoomVersion, nil
|
||
}
|
||
|
||
func (d *Database) GetOrCreateEventTypeNID(ctx context.Context, eventType string) (eventTypeNID types.EventTypeNID, err error) {
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
if eventTypeNID, err = d.assignEventTypeNID(ctx, txn, eventType); err != nil {
|
||
return fmt.Errorf("d.assignEventTypeNID: %w", err)
|
||
}
|
||
return nil
|
||
})
|
||
return eventTypeNID, err
|
||
}
|
||
|
||
func (d *Database) GetOrCreateEventStateKeyNID(ctx context.Context, eventStateKey *string) (eventStateKeyNID types.EventStateKeyNID, err error) {
|
||
if eventStateKey == nil {
|
||
return 0, nil
|
||
}
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
if eventStateKeyNID, err = d.assignStateKeyNID(ctx, txn, *eventStateKey); err != nil {
|
||
return fmt.Errorf("d.assignStateKeyNID: %w", err)
|
||
}
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
return eventStateKeyNID, nil
|
||
}
|
||
|
||
func (d *EventDatabase) StoreEvent(
|
||
ctx context.Context, event gomatrixserverlib.PDU,
|
||
roomInfo *types.RoomInfo, eventTypeNID types.EventTypeNID, eventStateKeyNID types.EventStateKeyNID,
|
||
authEventNIDs []types.EventNID, isRejected bool,
|
||
) (types.EventNID, types.StateAtEvent, error) {
|
||
var (
|
||
eventNID types.EventNID
|
||
stateNID types.StateSnapshotNID
|
||
err error
|
||
)
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
if eventNID, stateNID, err = d.EventsTable.InsertEvent(
|
||
ctx,
|
||
txn,
|
||
roomInfo.RoomNID,
|
||
eventTypeNID,
|
||
eventStateKeyNID,
|
||
event.EventID(),
|
||
authEventNIDs,
|
||
event.Depth(),
|
||
isRejected,
|
||
); err != nil {
|
||
if err == sql.ErrNoRows {
|
||
// We've already inserted the event so select the numeric event ID
|
||
eventNID, stateNID, err = d.EventsTable.SelectEvent(ctx, txn, event.EventID())
|
||
} else if err != nil {
|
||
return fmt.Errorf("d.EventsTable.InsertEvent: %w", err)
|
||
}
|
||
if err != nil {
|
||
return fmt.Errorf("d.EventsTable.SelectEvent: %w", err)
|
||
}
|
||
}
|
||
|
||
if err = d.EventJSONTable.InsertEventJSON(ctx, txn, eventNID, event.JSON()); err != nil {
|
||
return fmt.Errorf("d.EventJSONTable.InsertEventJSON: %w", err)
|
||
}
|
||
|
||
if prevEvents := event.PrevEventIDs(); len(prevEvents) > 0 {
|
||
// Create an updater - NB: on sqlite this WILL create a txn as we are directly calling the shared DB form of
|
||
// GetLatestEventsForUpdate - not via the SQLiteDatabase form which has `nil` txns. This
|
||
// function only does SELECTs though so the created txn (at this point) is just a read txn like
|
||
// any other so this is fine. If we ever update GetLatestEventsForUpdate or NewLatestEventsUpdater
|
||
// to do writes however then this will need to go inside `Writer.Do`.
|
||
|
||
// The following is a copy of RoomUpdater.StorePreviousEvents
|
||
for _, eventID := range prevEvents {
|
||
if err = d.PrevEventsTable.InsertPreviousEvent(ctx, txn, eventID, eventNID); err != nil {
|
||
return fmt.Errorf("u.d.PrevEventsTable.InsertPreviousEvent: %w", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
return 0, types.StateAtEvent{}, fmt.Errorf("d.Writer.Do: %w", err)
|
||
}
|
||
|
||
// We should attempt to update the previous events table with any
|
||
// references that this new event makes. We do this using a latest
|
||
// events updater because it somewhat works as a mutex, ensuring
|
||
// that there's a row-level lock on the latest room events (well,
|
||
// on Postgres at least).
|
||
|
||
return eventNID, types.StateAtEvent{
|
||
BeforeStateSnapshotNID: stateNID,
|
||
StateEntry: types.StateEntry{
|
||
StateKeyTuple: types.StateKeyTuple{
|
||
EventTypeNID: eventTypeNID,
|
||
EventStateKeyNID: eventStateKeyNID,
|
||
},
|
||
EventNID: eventNID,
|
||
},
|
||
}, err
|
||
}
|
||
|
||
func (d *Database) PublishRoom(ctx context.Context, roomID, appserviceID, networkID string, publish bool) error {
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
return d.PublishedTable.UpsertRoomPublished(ctx, txn, roomID, appserviceID, networkID, publish)
|
||
})
|
||
}
|
||
|
||
func (d *Database) GetPublishedRoom(ctx context.Context, roomID string) (bool, error) {
|
||
return d.PublishedTable.SelectPublishedFromRoomID(ctx, nil, roomID)
|
||
}
|
||
|
||
func (d *Database) GetPublishedRooms(ctx context.Context, networkID string, includeAllNetworks bool) ([]string, error) {
|
||
return d.PublishedTable.SelectAllPublishedRooms(ctx, nil, networkID, true, includeAllNetworks)
|
||
}
|
||
|
||
func (d *Database) MissingAuthPrevEvents(
|
||
ctx context.Context, e gomatrixserverlib.PDU,
|
||
) (missingAuth, missingPrev []string, err error) {
|
||
authEventNIDs, err := d.EventNIDs(ctx, e.AuthEventIDs())
|
||
if err != nil {
|
||
return nil, nil, fmt.Errorf("d.EventNIDs: %w", err)
|
||
}
|
||
for _, authEventID := range e.AuthEventIDs() {
|
||
if _, ok := authEventNIDs[authEventID]; !ok {
|
||
missingAuth = append(missingAuth, authEventID)
|
||
}
|
||
}
|
||
|
||
for _, prevEventID := range e.PrevEventIDs() {
|
||
state, err := d.StateAtEventIDs(ctx, []string{prevEventID})
|
||
if err != nil || len(state) == 0 || (!state[0].IsCreate() && state[0].BeforeStateSnapshotNID == 0) {
|
||
missingPrev = append(missingPrev, prevEventID)
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
func (d *Database) assignRoomNID(
|
||
ctx context.Context, txn *sql.Tx, roomID string, roomVersion gomatrixserverlib.RoomVersion,
|
||
) (types.RoomNID, error) {
|
||
roomNID, ok := d.Cache.GetRoomServerRoomNID(roomID)
|
||
if ok {
|
||
return roomNID, nil
|
||
}
|
||
// Check if we already have a numeric ID in the database.
|
||
roomNID, err := d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We don't have a numeric ID so insert one into the database.
|
||
roomNID, err = d.RoomsTable.InsertRoomNID(ctx, txn, roomID, roomVersion)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We raced with another insert so run the select again.
|
||
roomNID, err = d.RoomsTable.SelectRoomNID(ctx, txn, roomID)
|
||
}
|
||
}
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
d.Cache.StoreRoomServerRoomID(roomNID, roomID)
|
||
d.Cache.StoreRoomVersion(roomID, roomVersion)
|
||
return roomNID, nil
|
||
}
|
||
|
||
func (d *Database) assignEventTypeNID(
|
||
ctx context.Context, txn *sql.Tx, eventType string,
|
||
) (types.EventTypeNID, error) {
|
||
eventTypeNID, ok := d.Cache.GetEventTypeKey(eventType)
|
||
if ok {
|
||
return eventTypeNID, nil
|
||
}
|
||
// Check if we already have a numeric ID in the database.
|
||
eventTypeNID, err := d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We don't have a numeric ID so insert one into the database.
|
||
eventTypeNID, err = d.EventTypesTable.InsertEventTypeNID(ctx, txn, eventType)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We raced with another insert so run the select again.
|
||
eventTypeNID, err = d.EventTypesTable.SelectEventTypeNID(ctx, txn, eventType)
|
||
}
|
||
}
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
d.Cache.StoreEventTypeKey(eventTypeNID, eventType)
|
||
return eventTypeNID, nil
|
||
}
|
||
|
||
func (d *EventDatabase) assignStateKeyNID(
|
||
ctx context.Context, txn *sql.Tx, eventStateKey string,
|
||
) (types.EventStateKeyNID, error) {
|
||
eventStateKeyNID, ok := d.Cache.GetEventStateKeyNID(eventStateKey)
|
||
if ok {
|
||
return eventStateKeyNID, nil
|
||
}
|
||
// Check if we already have a numeric ID in the database.
|
||
eventStateKeyNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We don't have a numeric ID so insert one into the database.
|
||
eventStateKeyNID, err = d.EventStateKeysTable.InsertEventStateKeyNID(ctx, txn, eventStateKey)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
// We raced with another insert so run the select again.
|
||
eventStateKeyNID, err = d.EventStateKeysTable.SelectEventStateKeyNID(ctx, txn, eventStateKey)
|
||
}
|
||
}
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
d.Cache.StoreEventStateKey(eventStateKeyNID, eventStateKey)
|
||
return eventStateKeyNID, nil
|
||
}
|
||
|
||
func extractRoomVersionFromCreateEvent(event gomatrixserverlib.PDU) (
|
||
gomatrixserverlib.RoomVersion, error,
|
||
) {
|
||
var err error
|
||
var roomVersion gomatrixserverlib.RoomVersion
|
||
// Look for m.room.create events.
|
||
if event.Type() != spec.MRoomCreate {
|
||
return gomatrixserverlib.RoomVersion(""), nil
|
||
}
|
||
roomVersion = gomatrixserverlib.RoomVersionV1
|
||
var createContent gomatrixserverlib.CreateContent
|
||
// The m.room.create event contains an optional "room_version" key in
|
||
// the event content, so we need to unmarshal that first.
|
||
if err = json.Unmarshal(event.Content(), &createContent); err != nil {
|
||
return gomatrixserverlib.RoomVersion(""), err
|
||
}
|
||
// A room version was specified in the event content?
|
||
if createContent.RoomVersion != nil {
|
||
roomVersion = gomatrixserverlib.RoomVersion(*createContent.RoomVersion)
|
||
}
|
||
return roomVersion, err
|
||
}
|
||
|
||
// nolint:gocyclo
|
||
// MaybeRedactEvent manages the redacted status of events. There's two cases to consider in order to comply with the spec:
|
||
// "servers should not apply or send redactions to clients until both the redaction event and original event have been seen, and are valid."
|
||
// https://matrix.org/docs/spec/rooms/v3#authorization-rules-for-events
|
||
// These cases are:
|
||
// - This is a redaction event, redact the event it references if we know about it.
|
||
// - This is a normal event which may have been previously redacted.
|
||
//
|
||
// In the first case, check if we have the referenced event then apply the redaction, else store it
|
||
// in the redactions table with validated=FALSE. In the second case, check if there is a redaction for it:
|
||
// if there is then apply the redactions and set validated=TRUE.
|
||
//
|
||
// When an event is redacted, the redacted event JSON is modified to add an `unsigned.redacted_because` field. We use this field
|
||
// when loading events to determine whether to apply redactions. This keeps the hot-path of reading events quick as we don't need
|
||
// to cross-reference with other tables when loading.
|
||
//
|
||
// Returns the redaction event and the redacted event if this call resulted in a redaction.
|
||
func (d *EventDatabase) MaybeRedactEvent(
|
||
ctx context.Context, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU, plResolver state.PowerLevelResolver,
|
||
querier api.QuerySenderIDAPI,
|
||
) (gomatrixserverlib.PDU, gomatrixserverlib.PDU, error) {
|
||
var (
|
||
redactionEvent, redactedEvent *types.Event
|
||
err error
|
||
validated bool
|
||
ignoreRedaction bool
|
||
)
|
||
|
||
wErr := d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
isRedactionEvent := event.Type() == spec.MRoomRedaction && event.StateKey() == nil
|
||
if isRedactionEvent {
|
||
// an event which redacts itself should be ignored
|
||
if event.EventID() == event.Redacts() {
|
||
return nil
|
||
}
|
||
|
||
err = d.RedactionsTable.InsertRedaction(ctx, txn, tables.RedactionInfo{
|
||
Validated: false,
|
||
RedactionEventID: event.EventID(),
|
||
RedactsEventID: event.Redacts(),
|
||
})
|
||
if err != nil {
|
||
return fmt.Errorf("d.RedactionsTable.InsertRedaction: %w", err)
|
||
}
|
||
}
|
||
|
||
redactionEvent, redactedEvent, validated, err = d.loadRedactionPair(ctx, txn, roomInfo, eventNID, event)
|
||
switch {
|
||
case err != nil:
|
||
return fmt.Errorf("d.loadRedactionPair: %w", err)
|
||
case validated || redactedEvent == nil || redactionEvent == nil:
|
||
// we've seen this redaction before or there is nothing to redact
|
||
return nil
|
||
case redactedEvent.RoomID().String() != redactionEvent.RoomID().String():
|
||
// redactions across rooms aren't allowed
|
||
ignoreRedaction = true
|
||
return nil
|
||
}
|
||
|
||
sender1Domain := ""
|
||
sender1, err1 := querier.QueryUserIDForSender(ctx, redactedEvent.RoomID(), redactedEvent.SenderID())
|
||
if err1 == nil {
|
||
sender1Domain = string(sender1.Domain())
|
||
}
|
||
sender2Domain := ""
|
||
sender2, err2 := querier.QueryUserIDForSender(ctx, redactedEvent.RoomID(), redactionEvent.SenderID())
|
||
if err2 == nil {
|
||
sender2Domain = string(sender2.Domain())
|
||
}
|
||
var powerlevels *gomatrixserverlib.PowerLevelContent
|
||
powerlevels, err = plResolver.Resolve(ctx, redactionEvent.EventID())
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
switch {
|
||
case powerlevels.UserLevel(redactionEvent.SenderID()) >= powerlevels.Redact:
|
||
// 1. The power level of the redaction event’s sender is greater than or equal to the redact level.
|
||
case sender1Domain != "" && sender2Domain != "" && sender1Domain == sender2Domain:
|
||
// 2. The domain of the redaction event’s sender matches that of the original event’s sender.
|
||
default:
|
||
ignoreRedaction = true
|
||
return nil
|
||
}
|
||
|
||
// mark the event as redacted
|
||
if redactionsArePermanent {
|
||
redactedEvent.Redact()
|
||
}
|
||
|
||
err = redactedEvent.SetUnsignedField("redacted_because", redactionEvent)
|
||
if err != nil {
|
||
return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
|
||
}
|
||
// NOTSPEC: sytest relies on this unspecced field existing :(
|
||
err = redactedEvent.SetUnsignedField("redacted_by", redactionEvent.EventID())
|
||
if err != nil {
|
||
return fmt.Errorf("redactedEvent.SetUnsignedField: %w", err)
|
||
}
|
||
// overwrite the eventJSON table
|
||
err = d.EventJSONTable.InsertEventJSON(ctx, txn, redactedEvent.EventNID, redactedEvent.JSON())
|
||
if err != nil {
|
||
return fmt.Errorf("d.EventJSONTable.InsertEventJSON: %w", err)
|
||
}
|
||
|
||
err = d.RedactionsTable.MarkRedactionValidated(ctx, txn, redactionEvent.EventID(), true)
|
||
if err != nil {
|
||
return fmt.Errorf("d.RedactionsTable.MarkRedactionValidated: %w", err)
|
||
}
|
||
|
||
// We remove the entry from the cache, as if we just "StoreRoomServerEvent", we can't be
|
||
// certain that the cached entry actually is updated, since ristretto is eventual-persistent.
|
||
d.Cache.InvalidateRoomServerEvent(redactedEvent.EventNID)
|
||
|
||
return nil
|
||
})
|
||
if wErr != nil {
|
||
return nil, nil, err
|
||
}
|
||
if ignoreRedaction || redactionEvent == nil || redactedEvent == nil {
|
||
return nil, nil, nil
|
||
}
|
||
return redactionEvent.PDU, redactedEvent.PDU, nil
|
||
}
|
||
|
||
// loadRedactionPair returns both the redaction event and the redacted event, else nil.
|
||
func (d *EventDatabase) loadRedactionPair(
|
||
ctx context.Context, txn *sql.Tx, roomInfo *types.RoomInfo, eventNID types.EventNID, event gomatrixserverlib.PDU,
|
||
) (*types.Event, *types.Event, bool, error) {
|
||
var redactionEvent, redactedEvent *types.Event
|
||
var info *tables.RedactionInfo
|
||
var err error
|
||
isRedactionEvent := event.Type() == spec.MRoomRedaction && event.StateKey() == nil
|
||
|
||
var eventBeingRedacted string
|
||
if isRedactionEvent {
|
||
eventBeingRedacted = event.Redacts()
|
||
redactionEvent = &types.Event{
|
||
EventNID: eventNID,
|
||
PDU: event,
|
||
}
|
||
} else {
|
||
eventBeingRedacted = event.EventID() // maybe, we'll see if we have info
|
||
redactedEvent = &types.Event{
|
||
EventNID: eventNID,
|
||
PDU: event,
|
||
}
|
||
}
|
||
|
||
info, err = d.RedactionsTable.SelectRedactionInfoByEventBeingRedacted(ctx, txn, eventBeingRedacted)
|
||
if err != nil {
|
||
return nil, nil, false, err
|
||
}
|
||
if info == nil {
|
||
// this event hasn't been redacted or we don't have the redaction for it yet
|
||
return nil, nil, false, nil
|
||
}
|
||
|
||
if isRedactionEvent {
|
||
redactedEvent = d.loadEvent(ctx, roomInfo, info.RedactsEventID)
|
||
} else {
|
||
redactionEvent = d.loadEvent(ctx, roomInfo, info.RedactionEventID)
|
||
}
|
||
|
||
return redactionEvent, redactedEvent, info.Validated, nil
|
||
}
|
||
|
||
// applyRedactions will redact events that have an `unsigned.redacted_because` field.
|
||
func (d *EventDatabase) applyRedactions(events []types.Event) {
|
||
for i := range events {
|
||
if result := gjson.GetBytes(events[i].Unsigned(), "redacted_because"); result.Exists() {
|
||
events[i].Redact()
|
||
}
|
||
}
|
||
}
|
||
|
||
// loadEvent loads a single event or returns nil on any problems/missing event
|
||
func (d *EventDatabase) loadEvent(ctx context.Context, roomInfo *types.RoomInfo, eventID string) *types.Event {
|
||
nids, err := d.EventNIDs(ctx, []string{eventID})
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
if len(nids) == 0 {
|
||
return nil
|
||
}
|
||
if roomInfo == nil {
|
||
return nil
|
||
}
|
||
evs, err := d.Events(ctx, roomInfo.RoomVersion, []types.EventNID{nids[eventID].EventNID})
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
if len(evs) != 1 {
|
||
return nil
|
||
}
|
||
return &evs[0]
|
||
}
|
||
|
||
func (d *Database) GetHistoryVisibilityState(ctx context.Context, roomInfo *types.RoomInfo, eventID string, domain string) ([]gomatrixserverlib.PDU, error) {
|
||
eventStates, err := d.EventsTable.BulkSelectStateAtEventByID(ctx, nil, []string{eventID})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
stateSnapshotNID := eventStates[0].BeforeStateSnapshotNID
|
||
if stateSnapshotNID == 0 {
|
||
return nil, nil
|
||
}
|
||
eventNIDs, err := d.StateSnapshotTable.BulkSelectStateForHistoryVisibility(ctx, nil, stateSnapshotNID, domain)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
eventIDs, _ := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
eventIDs = map[types.EventNID]string{}
|
||
}
|
||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomInfo.RoomVersion)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
events := make([]gomatrixserverlib.PDU, 0, len(eventNIDs))
|
||
for _, eventNID := range eventNIDs {
|
||
data, err := d.EventJSONTable.BulkSelectEventJSON(ctx, nil, []types.EventNID{eventNID})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
ev, err := verImpl.NewEventFromTrustedJSONWithEventID(eventIDs[eventNID], data[0].EventJSON, false)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
events = append(events, ev)
|
||
}
|
||
return events, nil
|
||
}
|
||
|
||
// GetStateEvent returns the current state event of a given type for a given room with a given state key
|
||
// If no event could be found, returns nil
|
||
// If there was an issue during the retrieval, returns an error
|
||
func (d *Database) GetStateEvent(ctx context.Context, roomID, evType, stateKey string) (*types.HeaderedEvent, error) {
|
||
roomInfo, err := d.roomInfo(ctx, nil, roomID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if roomInfo == nil {
|
||
return nil, fmt.Errorf("room %s doesn't exist", roomID)
|
||
}
|
||
// e.g invited rooms
|
||
if roomInfo.IsStub() {
|
||
return nil, nil
|
||
}
|
||
eventTypeNID, err := d.GetOrCreateEventTypeNID(ctx, evType)
|
||
if err == sql.ErrNoRows {
|
||
// No rooms have an event of this type, otherwise we'd have an event type NID
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
stateKeyNID, err := d.GetOrCreateEventStateKeyNID(ctx, &stateKey)
|
||
if err == sql.ErrNoRows {
|
||
// No rooms have a state event with this state key, otherwise we'd have an state key NID
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
entries, err := d.loadStateAtSnapshot(ctx, roomInfo.StateSnapshotNID())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var eventNIDs []types.EventNID
|
||
for _, e := range entries {
|
||
if e.EventTypeNID == eventTypeNID && e.EventStateKeyNID == stateKeyNID {
|
||
eventNIDs = append(eventNIDs, e.EventNID)
|
||
}
|
||
}
|
||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomInfo.RoomVersion)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
eventIDs, _ := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
eventIDs = map[types.EventNID]string{}
|
||
}
|
||
// return the event requested
|
||
for _, e := range entries {
|
||
if e.EventTypeNID == eventTypeNID && e.EventStateKeyNID == stateKeyNID {
|
||
cachedEvent, ok := d.Cache.GetRoomServerEvent(e.EventNID)
|
||
if ok {
|
||
return &types.HeaderedEvent{PDU: cachedEvent}, nil
|
||
}
|
||
data, err := d.EventJSONTable.BulkSelectEventJSON(ctx, nil, []types.EventNID{e.EventNID})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(data) == 0 {
|
||
return nil, fmt.Errorf("GetStateEvent: no json for event nid %d", e.EventNID)
|
||
}
|
||
ev, err := verImpl.NewEventFromTrustedJSONWithEventID(eventIDs[e.EventNID], data[0].EventJSON, false)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &types.HeaderedEvent{PDU: ev}, nil
|
||
}
|
||
}
|
||
|
||
return nil, nil
|
||
}
|
||
|
||
// Same as GetStateEvent but returns all matching state events with this event type. Returns no error
|
||
// if there are no events with this event type.
|
||
func (d *Database) GetStateEventsWithEventType(ctx context.Context, roomID, evType string) ([]*types.HeaderedEvent, error) {
|
||
roomInfo, err := d.roomInfo(ctx, nil, roomID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if roomInfo == nil {
|
||
return nil, fmt.Errorf("room %s doesn't exist", roomID)
|
||
}
|
||
// e.g invited rooms
|
||
if roomInfo.IsStub() {
|
||
return nil, nil
|
||
}
|
||
eventTypeNID, err := d.EventTypesTable.SelectEventTypeNID(ctx, nil, evType)
|
||
if err == sql.ErrNoRows {
|
||
// No rooms have an event of this type, otherwise we'd have an event type NID
|
||
return nil, nil
|
||
}
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
entries, err := d.loadStateAtSnapshot(ctx, roomInfo.StateSnapshotNID())
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var eventNIDs []types.EventNID
|
||
for _, e := range entries {
|
||
if e.EventTypeNID == eventTypeNID {
|
||
eventNIDs = append(eventNIDs, e.EventNID)
|
||
}
|
||
}
|
||
eventIDs, _ := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
eventIDs = map[types.EventNID]string{}
|
||
}
|
||
// return the events requested
|
||
eventPairs, err := d.EventJSONTable.BulkSelectEventJSON(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(eventPairs) == 0 {
|
||
return nil, nil
|
||
}
|
||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomInfo.RoomVersion)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var result []*types.HeaderedEvent
|
||
for _, pair := range eventPairs {
|
||
ev, err := verImpl.NewEventFromTrustedJSONWithEventID(eventIDs[pair.EventNID], pair.EventJSON, false)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result = append(result, &types.HeaderedEvent{PDU: ev})
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// GetRoomsByMembership returns a list of room IDs matching the provided membership and user ID (as state_key).
|
||
func (d *Database) GetRoomsByMembership(ctx context.Context, userID spec.UserID, membership string) ([]string, error) {
|
||
var membershipState tables.MembershipState
|
||
switch membership {
|
||
case "join":
|
||
membershipState = tables.MembershipStateJoin
|
||
case "invite":
|
||
membershipState = tables.MembershipStateInvite
|
||
case "leave":
|
||
membershipState = tables.MembershipStateLeaveOrBan
|
||
case "ban":
|
||
membershipState = tables.MembershipStateLeaveOrBan
|
||
default:
|
||
return nil, fmt.Errorf("GetRoomsByMembership: invalid membership %s", membership)
|
||
}
|
||
|
||
// Convert provided user ID to NID
|
||
userNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, nil, userID.String())
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return nil, nil
|
||
} else {
|
||
return nil, fmt.Errorf("SelectEventStateKeyNID: cannot map user ID to state key NIDs: %w", err)
|
||
}
|
||
}
|
||
|
||
// Use this NID to fetch all associated room keys (for pseudo ID rooms)
|
||
roomKeyMap, err := d.UserRoomKeyTable.SelectAllPublicKeysForUser(ctx, nil, userNID)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
roomKeyMap = map[types.RoomNID]ed25519.PublicKey{}
|
||
} else {
|
||
return nil, fmt.Errorf("SelectAllPublicKeysForUser: could not select user room public keys for user: %w", err)
|
||
}
|
||
}
|
||
|
||
var eventStateKeyNIDs []types.EventStateKeyNID
|
||
|
||
// If there are room keys (i.e. this user is in pseudo ID rooms), then gather the appropriate NIDs
|
||
if len(roomKeyMap) != 0 {
|
||
// Convert keys to string representation
|
||
userRoomKeys := make([]string, len(roomKeyMap))
|
||
i := 0
|
||
for _, key := range roomKeyMap {
|
||
userRoomKeys[i] = spec.Base64Bytes(key).Encode()
|
||
i += 1
|
||
}
|
||
|
||
// Convert the string representation to its NID
|
||
pseudoIDStateKeys, sqlErr := d.EventStateKeysTable.BulkSelectEventStateKeyNID(ctx, nil, userRoomKeys)
|
||
if sqlErr != nil {
|
||
if sqlErr == sql.ErrNoRows {
|
||
pseudoIDStateKeys = map[string]types.EventStateKeyNID{}
|
||
} else {
|
||
return nil, fmt.Errorf("BulkSelectEventStateKeyNID: could not select state keys for public room keys: %w", err)
|
||
}
|
||
}
|
||
|
||
// Collect all NIDs together
|
||
eventStateKeyNIDs = make([]types.EventStateKeyNID, len(pseudoIDStateKeys)+1)
|
||
eventStateKeyNIDs[0] = userNID
|
||
i = 1
|
||
for _, nid := range pseudoIDStateKeys {
|
||
eventStateKeyNIDs[i] = nid
|
||
i += 1
|
||
}
|
||
} else {
|
||
// If there are no room keys (so no pseudo ID rooms), we only need to care about the user ID NID.
|
||
eventStateKeyNIDs = []types.EventStateKeyNID{userNID}
|
||
}
|
||
|
||
// Fetch rooms that match membership for each NID
|
||
roomNIDs := []types.RoomNID{}
|
||
for _, nid := range eventStateKeyNIDs {
|
||
var roomNIDsChunk []types.RoomNID
|
||
roomNIDsChunk, err = d.MembershipTable.SelectRoomsWithMembership(ctx, nil, nid, membershipState)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetRoomsByMembership: failed to SelectRoomsWithMembership: %w", err)
|
||
}
|
||
roomNIDs = append(roomNIDs, roomNIDsChunk...)
|
||
}
|
||
|
||
roomIDs, err := d.RoomsTable.BulkSelectRoomIDs(ctx, nil, roomNIDs)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetRoomsByMembership: failed to lookup room nids: %w", err)
|
||
}
|
||
if len(roomIDs) != len(roomNIDs) {
|
||
return nil, fmt.Errorf("GetRoomsByMembership: missing room IDs, got %d want %d", len(roomIDs), len(roomNIDs))
|
||
}
|
||
return roomIDs, nil
|
||
}
|
||
|
||
// GetBulkStateContent returns all state events which match a given room ID and a given state key tuple. Both must be satisfied for a match.
|
||
// If a tuple has the StateKey of '*' and allowWildcards=true then all state events with the EventType should be returned.
|
||
func (d *Database) GetBulkStateContent(ctx context.Context, roomIDs []string, tuples []gomatrixserverlib.StateKeyTuple, allowWildcards bool) ([]tables.StrippedEvent, error) {
|
||
eventTypes := make([]string, 0, len(tuples))
|
||
for _, tuple := range tuples {
|
||
eventTypes = append(eventTypes, tuple.EventType)
|
||
}
|
||
// we don't bother failing the request if we get asked for event types we don't know about, as all that would result in is no matches which
|
||
// isn't a failure.
|
||
eventTypeNIDMap, err := d.eventTypeNIDs(ctx, nil, eventTypes)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to map event type nids: %w", err)
|
||
}
|
||
typeNIDSet := make(map[types.EventTypeNID]bool)
|
||
for _, nid := range eventTypeNIDMap {
|
||
typeNIDSet[nid] = true
|
||
}
|
||
|
||
allowWildcard := make(map[types.EventTypeNID]bool)
|
||
eventStateKeys := make([]string, 0, len(tuples))
|
||
for _, tuple := range tuples {
|
||
if allowWildcards && tuple.StateKey == "*" {
|
||
allowWildcard[eventTypeNIDMap[tuple.EventType]] = true
|
||
continue
|
||
}
|
||
eventStateKeys = append(eventStateKeys, tuple.StateKey)
|
||
|
||
}
|
||
|
||
eventStateKeyNIDMap, err := d.eventStateKeyNIDs(ctx, nil, eventStateKeys)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to map state key nids: %w", err)
|
||
}
|
||
stateKeyNIDSet := make(map[types.EventStateKeyNID]bool)
|
||
for _, nid := range eventStateKeyNIDMap {
|
||
stateKeyNIDSet[nid] = true
|
||
}
|
||
|
||
var eventNIDs []types.EventNID
|
||
eventNIDToVer := make(map[types.EventNID]gomatrixserverlib.RoomVersion)
|
||
// TODO: This feels like this is going to be really slow...
|
||
for _, roomID := range roomIDs {
|
||
roomInfo, err2 := d.roomInfo(ctx, nil, roomID)
|
||
if err2 != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to load room info for room %s : %w", roomID, err2)
|
||
}
|
||
// for unknown rooms or rooms which we don't have the current state, skip them.
|
||
if roomInfo == nil || roomInfo.IsStub() {
|
||
continue
|
||
}
|
||
entries, err2 := d.loadStateAtSnapshot(ctx, roomInfo.StateSnapshotNID())
|
||
if err2 != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to load state for room %s : %w", roomID, err2)
|
||
}
|
||
for _, entry := range entries {
|
||
if typeNIDSet[entry.EventTypeNID] {
|
||
if allowWildcard[entry.EventTypeNID] || stateKeyNIDSet[entry.EventStateKeyNID] {
|
||
eventNIDs = append(eventNIDs, entry.EventNID)
|
||
eventNIDToVer[entry.EventNID] = roomInfo.RoomVersion
|
||
}
|
||
}
|
||
}
|
||
}
|
||
eventIDs, _ := d.EventsTable.BulkSelectEventID(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
eventIDs = map[types.EventNID]string{}
|
||
}
|
||
events, err := d.EventJSONTable.BulkSelectEventJSON(ctx, nil, eventNIDs)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to load event JSON for event nids: %w", err)
|
||
}
|
||
result := make([]tables.StrippedEvent, len(events))
|
||
for i := range events {
|
||
roomVer := eventNIDToVer[events[i].EventNID]
|
||
verImpl, err := gomatrixserverlib.GetRoomVersion(roomVer)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
ev, err := verImpl.NewEventFromTrustedJSONWithEventID(eventIDs[events[i].EventNID], events[i].EventJSON, false)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GetBulkStateContent: failed to load event JSON for event NID %v : %w", events[i].EventNID, err)
|
||
}
|
||
result[i] = tables.StrippedEvent{
|
||
EventType: ev.Type(),
|
||
RoomID: ev.RoomID().String(),
|
||
StateKey: *ev.StateKey(),
|
||
ContentValue: tables.ExtractContentValue(&types.HeaderedEvent{PDU: ev}),
|
||
}
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// JoinedUsersSetInRooms returns a map of how many times the given users appear in the specified rooms.
|
||
func (d *Database) JoinedUsersSetInRooms(ctx context.Context, roomIDs, userIDs []string, localOnly bool) (map[string]int, error) {
|
||
roomNIDs, err := d.RoomsTable.BulkSelectRoomNIDs(ctx, nil, roomIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
userNIDsMap, err := d.eventStateKeyNIDs(ctx, nil, userIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
userNIDs := make([]types.EventStateKeyNID, 0, len(userNIDsMap))
|
||
nidToUserID := make(map[types.EventStateKeyNID]string, len(userNIDsMap))
|
||
for id, nid := range userNIDsMap {
|
||
userNIDs = append(userNIDs, nid)
|
||
nidToUserID[nid] = id
|
||
}
|
||
userNIDToCount, err := d.MembershipTable.SelectJoinedUsersSetForRooms(ctx, nil, roomNIDs, userNIDs, localOnly)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
stateKeyNIDs := make([]types.EventStateKeyNID, len(userNIDToCount))
|
||
i := 0
|
||
for nid := range userNIDToCount {
|
||
stateKeyNIDs[i] = nid
|
||
i++
|
||
}
|
||
// If we didn't have any userIDs to look up, get the UserIDs for the returned userNIDToCount now
|
||
if len(userIDs) == 0 {
|
||
nidToUserID, err = d.EventStateKeys(ctx, stateKeyNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
result := make(map[string]int, len(userNIDToCount))
|
||
for nid, count := range userNIDToCount {
|
||
result[nidToUserID[nid]] = count
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// GetLeftUsers calculates users we (the server) don't share a room with anymore.
|
||
func (d *Database) GetLeftUsers(ctx context.Context, userIDs []string) ([]string, error) {
|
||
// Get the userNID for all users with a stale device list
|
||
stateKeyNIDMap, err := d.EventStateKeyNIDs(ctx, userIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
userNIDs := make([]types.EventStateKeyNID, 0, len(stateKeyNIDMap))
|
||
userNIDtoUserID := make(map[types.EventStateKeyNID]string, len(stateKeyNIDMap))
|
||
// Create a map from userNID -> userID
|
||
for userID, nid := range stateKeyNIDMap {
|
||
userNIDs = append(userNIDs, nid)
|
||
userNIDtoUserID[nid] = userID
|
||
}
|
||
|
||
// Get all users whose membership is still join, knock or invite.
|
||
stillJoinedUsersNIDs, err := d.MembershipTable.SelectJoinedUsers(ctx, nil, userNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Remove joined users from the "user with stale devices" list, which contains left AND joined users
|
||
for _, joinedUser := range stillJoinedUsersNIDs {
|
||
delete(userNIDtoUserID, joinedUser)
|
||
}
|
||
|
||
// The users still in our userNIDtoUserID map are the users we don't share a room with anymore,
|
||
// and the return value we are looking for.
|
||
leftUsers := make([]string, 0, len(userNIDtoUserID))
|
||
for _, userID := range userNIDtoUserID {
|
||
leftUsers = append(leftUsers, userID)
|
||
}
|
||
|
||
return leftUsers, nil
|
||
}
|
||
|
||
// GetLocalServerInRoom returns true if we think we're in a given room or false otherwise.
|
||
func (d *Database) GetLocalServerInRoom(ctx context.Context, roomNID types.RoomNID) (bool, error) {
|
||
return d.MembershipTable.SelectLocalServerInRoom(ctx, nil, roomNID)
|
||
}
|
||
|
||
// GetServerInRoom returns true if we think a server is in a given room or false otherwise.
|
||
func (d *Database) GetServerInRoom(ctx context.Context, roomNID types.RoomNID, serverName spec.ServerName) (bool, error) {
|
||
return d.MembershipTable.SelectServerInRoom(ctx, nil, roomNID, serverName)
|
||
}
|
||
|
||
// GetKnownUsers searches all users that userID knows about.
|
||
func (d *Database) GetKnownUsers(ctx context.Context, userID, searchString string, limit int) ([]string, error) {
|
||
stateKeyNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, nil, userID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return d.MembershipTable.SelectKnownUsers(ctx, nil, stateKeyNID, searchString, limit)
|
||
}
|
||
|
||
func (d *Database) RoomsWithACLs(ctx context.Context) ([]string, error) {
|
||
|
||
eventTypeNID, err := d.GetOrCreateEventTypeNID(ctx, "m.room.server_acl")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
roomNIDs, err := d.EventsTable.SelectRoomsWithEventTypeNID(ctx, nil, eventTypeNID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
roomIDs, err := d.RoomsTable.BulkSelectRoomIDs(ctx, nil, roomNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return roomIDs, nil
|
||
}
|
||
|
||
// ForgetRoom sets a users room to forgotten
|
||
func (d *Database) ForgetRoom(ctx context.Context, userID, roomID string, forget bool) error {
|
||
roomNIDs, err := d.RoomsTable.BulkSelectRoomNIDs(ctx, nil, []string{roomID})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(roomNIDs) > 1 {
|
||
return fmt.Errorf("expected one room, got %d", len(roomNIDs))
|
||
}
|
||
stateKeyNID, err := d.EventStateKeysTable.SelectEventStateKeyNID(ctx, nil, userID)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
return d.MembershipTable.UpdateForgetMembership(ctx, nil, roomNIDs[0], stateKeyNID, forget)
|
||
})
|
||
}
|
||
|
||
// PurgeRoom removes all information about a given room from the roomserver.
|
||
// For large rooms this operation may take a considerable amount of time.
|
||
func (d *Database) PurgeRoom(ctx context.Context, roomID string) error {
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomNID, err := d.RoomsTable.SelectRoomNIDForUpdate(ctx, txn, roomID)
|
||
if err != nil {
|
||
if err == sql.ErrNoRows {
|
||
return fmt.Errorf("room %s does not exist", roomID)
|
||
}
|
||
return fmt.Errorf("failed to lock the room: %w", err)
|
||
}
|
||
return d.Purge.PurgeRoom(ctx, txn, roomNID, roomID)
|
||
})
|
||
}
|
||
|
||
func (d *Database) UpgradeRoom(ctx context.Context, oldRoomID, newRoomID, eventSender string) error {
|
||
|
||
return d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
published, err := d.PublishedTable.SelectPublishedFromRoomID(ctx, txn, oldRoomID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get published room: %w", err)
|
||
}
|
||
if published {
|
||
// un-publish old room
|
||
if err = d.PublishedTable.UpsertRoomPublished(ctx, txn, oldRoomID, "", "", false); err != nil {
|
||
return fmt.Errorf("failed to unpublish room: %w", err)
|
||
}
|
||
// publish new room
|
||
if err = d.PublishedTable.UpsertRoomPublished(ctx, txn, newRoomID, "", "", true); err != nil {
|
||
return fmt.Errorf("failed to publish room: %w", err)
|
||
}
|
||
}
|
||
|
||
// Migrate any existing room aliases
|
||
aliases, err := d.RoomAliasesTable.SelectAliasesFromRoomID(ctx, txn, oldRoomID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get room aliases: %w", err)
|
||
}
|
||
|
||
for _, alias := range aliases {
|
||
if err = d.RoomAliasesTable.DeleteRoomAlias(ctx, txn, alias); err != nil {
|
||
return fmt.Errorf("failed to remove room alias: %w", err)
|
||
}
|
||
if err = d.RoomAliasesTable.InsertRoomAlias(ctx, txn, alias, newRoomID, eventSender); err != nil {
|
||
return fmt.Errorf("failed to set room alias: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
// InsertUserRoomPrivatePublicKey inserts a new user room key for the given user and room.
|
||
// Returns the newly inserted private key or an existing private key. If there is
|
||
// an error talking to the database, returns that error.
|
||
func (d *Database) InsertUserRoomPrivatePublicKey(ctx context.Context, userID spec.UserID, roomID spec.RoomID, key ed25519.PrivateKey) (result ed25519.PrivateKey, err error) {
|
||
uID := userID.String()
|
||
stateKeyNIDMap, sErr := d.eventStateKeyNIDs(ctx, nil, []string{uID})
|
||
if sErr != nil {
|
||
return nil, sErr
|
||
}
|
||
stateKeyNID := stateKeyNIDMap[uID]
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomInfo, rErr := d.roomInfo(ctx, txn, roomID.String())
|
||
if rErr != nil {
|
||
return rErr
|
||
}
|
||
if roomInfo == nil {
|
||
return eventutil.ErrRoomNoExists{}
|
||
}
|
||
|
||
var iErr error
|
||
result, iErr = d.UserRoomKeyTable.InsertUserRoomPrivatePublicKey(ctx, txn, stateKeyNID, roomInfo.RoomNID, key)
|
||
return iErr
|
||
})
|
||
return result, err
|
||
}
|
||
|
||
// InsertUserRoomPublicKey inserts a new user room key for the given user and room.
|
||
// Returns the newly inserted public key or an existing public key. If there is
|
||
// an error talking to the database, returns that error.
|
||
func (d *Database) InsertUserRoomPublicKey(ctx context.Context, userID spec.UserID, roomID spec.RoomID, key ed25519.PublicKey) (result ed25519.PublicKey, err error) {
|
||
uID := userID.String()
|
||
stateKeyNIDMap, sErr := d.eventStateKeyNIDs(ctx, nil, []string{uID})
|
||
if sErr != nil {
|
||
return nil, sErr
|
||
}
|
||
stateKeyNID := stateKeyNIDMap[uID]
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomInfo, rErr := d.roomInfo(ctx, txn, roomID.String())
|
||
if rErr != nil {
|
||
return rErr
|
||
}
|
||
if roomInfo == nil {
|
||
return eventutil.ErrRoomNoExists{}
|
||
}
|
||
|
||
var iErr error
|
||
result, iErr = d.UserRoomKeyTable.InsertUserRoomPublicKey(ctx, txn, stateKeyNID, roomInfo.RoomNID, key)
|
||
return iErr
|
||
})
|
||
return result, err
|
||
}
|
||
|
||
// SelectUserRoomPrivateKey queries the users room private key.
|
||
// If no key exists, returns no key and no error. Otherwise returns
|
||
// the key and a database error, if any.
|
||
// TODO: Cache this?
|
||
func (d *Database) SelectUserRoomPrivateKey(ctx context.Context, userID spec.UserID, roomID spec.RoomID) (key ed25519.PrivateKey, err error) {
|
||
uID := userID.String()
|
||
stateKeyNIDMap, sErr := d.eventStateKeyNIDs(ctx, nil, []string{uID})
|
||
if sErr != nil {
|
||
return nil, sErr
|
||
}
|
||
stateKeyNID := stateKeyNIDMap[uID]
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomInfo, rErr := d.roomInfo(ctx, txn, roomID.String())
|
||
if rErr != nil {
|
||
return rErr
|
||
}
|
||
if roomInfo == nil {
|
||
return eventutil.ErrRoomNoExists{}
|
||
}
|
||
|
||
key, sErr = d.UserRoomKeyTable.SelectUserRoomPrivateKey(ctx, txn, stateKeyNID, roomInfo.RoomNID)
|
||
if !errors.Is(sErr, sql.ErrNoRows) {
|
||
return sErr
|
||
}
|
||
return nil
|
||
})
|
||
return
|
||
}
|
||
|
||
// SelectUserRoomPublicKey queries the users room public key.
|
||
// If no key exists, returns no key and no error. Otherwise returns
|
||
// the key and a database error, if any.
|
||
func (d *Database) SelectUserRoomPublicKey(ctx context.Context, userID spec.UserID, roomID spec.RoomID) (key ed25519.PublicKey, err error) {
|
||
uID := userID.String()
|
||
stateKeyNIDMap, sErr := d.eventStateKeyNIDs(ctx, nil, []string{uID})
|
||
if sErr != nil {
|
||
return nil, sErr
|
||
}
|
||
stateKeyNID := stateKeyNIDMap[uID]
|
||
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
roomInfo, rErr := d.roomInfo(ctx, txn, roomID.String())
|
||
if rErr != nil {
|
||
return rErr
|
||
}
|
||
if roomInfo == nil {
|
||
return nil
|
||
}
|
||
|
||
key, sErr = d.UserRoomKeyTable.SelectUserRoomPublicKey(ctx, txn, stateKeyNID, roomInfo.RoomNID)
|
||
if !errors.Is(sErr, sql.ErrNoRows) {
|
||
return sErr
|
||
}
|
||
return nil
|
||
})
|
||
return
|
||
}
|
||
|
||
// SelectUserIDsForPublicKeys returns a map from roomID -> map from senderKey -> userID
|
||
func (d *Database) SelectUserIDsForPublicKeys(ctx context.Context, publicKeys map[spec.RoomID][]ed25519.PublicKey) (result map[spec.RoomID]map[string]string, err error) {
|
||
result = make(map[spec.RoomID]map[string]string, len(publicKeys))
|
||
|
||
// map all roomIDs to roomNIDs
|
||
query := make(map[types.RoomNID][]ed25519.PublicKey)
|
||
rooms := make(map[types.RoomNID]spec.RoomID)
|
||
for roomID, keys := range publicKeys {
|
||
roomNID, ok := d.Cache.GetRoomServerRoomNID(roomID.String())
|
||
if !ok {
|
||
roomInfo, rErr := d.roomInfo(ctx, nil, roomID.String())
|
||
if rErr != nil {
|
||
return nil, rErr
|
||
}
|
||
if roomInfo == nil {
|
||
logrus.Warnf("missing room info for %s, there will be missing users in the response", roomID.String())
|
||
continue
|
||
}
|
||
roomNID = roomInfo.RoomNID
|
||
}
|
||
|
||
query[roomNID] = keys
|
||
rooms[roomNID] = roomID
|
||
}
|
||
|
||
// get the user room key pars
|
||
userRoomKeyPairMap, sErr := d.UserRoomKeyTable.BulkSelectUserNIDs(ctx, nil, query)
|
||
if sErr != nil {
|
||
return nil, sErr
|
||
}
|
||
nids := make([]types.EventStateKeyNID, 0, len(userRoomKeyPairMap))
|
||
for _, nid := range userRoomKeyPairMap {
|
||
nids = append(nids, nid.EventStateKeyNID)
|
||
}
|
||
// get the userIDs
|
||
nidMap, seErr := d.EventStateKeys(ctx, nids)
|
||
if seErr != nil {
|
||
return nil, seErr
|
||
}
|
||
|
||
// build the result map (roomID -> map publicKey -> userID)
|
||
for publicKey, userRoomKeyPair := range userRoomKeyPairMap {
|
||
userID := nidMap[userRoomKeyPair.EventStateKeyNID]
|
||
roomID := rooms[userRoomKeyPair.RoomNID]
|
||
resMap, exists := result[roomID]
|
||
if !exists {
|
||
resMap = map[string]string{}
|
||
}
|
||
resMap[publicKey] = userID
|
||
result[roomID] = resMap
|
||
}
|
||
return result, err
|
||
}
|
||
|
||
// InsertReportedEvent stores a reported event.
|
||
func (d *Database) InsertReportedEvent(
|
||
ctx context.Context,
|
||
roomID, eventID, reportingUserID, reason string,
|
||
score int64,
|
||
) (int64, error) {
|
||
roomInfo, err := d.roomInfo(ctx, nil, roomID)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if roomInfo == nil {
|
||
return 0, fmt.Errorf("room does not exist")
|
||
}
|
||
|
||
events, err := d.eventsFromIDs(ctx, nil, roomInfo, []string{eventID}, NoFilter)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
if len(events) == 0 {
|
||
return 0, fmt.Errorf("unable to find requested event")
|
||
}
|
||
|
||
stateKeyNIDs, err := d.EventStateKeyNIDs(ctx, []string{reportingUserID, events[0].SenderID().ToUserID().String()})
|
||
if err != nil {
|
||
return 0, fmt.Errorf("failed to query eventStateKeyNIDs: %w", err)
|
||
}
|
||
|
||
// We expect exactly 2 stateKeyNIDs
|
||
if len(stateKeyNIDs) != 2 {
|
||
return 0, fmt.Errorf("expected 2 stateKeyNIDs, received %d", len(stateKeyNIDs))
|
||
}
|
||
|
||
var reportID int64
|
||
err = d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error {
|
||
reportID, err = d.ReportedEventsTable.InsertReportedEvent(
|
||
ctx,
|
||
txn,
|
||
roomInfo.RoomNID,
|
||
events[0].EventNID,
|
||
stateKeyNIDs[reportingUserID],
|
||
stateKeyNIDs[events[0].SenderID().ToUserID().String()],
|
||
reason,
|
||
score,
|
||
)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
})
|
||
|
||
return reportID, err
|
||
}
|
||
|
||
// QueryAdminEventReports returns event reports given a filter.
|
||
func (d *Database) QueryAdminEventReports(ctx context.Context, from uint64, limit uint64, backwards bool, userID string, roomID string) ([]api.QueryAdminEventReportsResponse, int64, error) {
|
||
// Filter on roomID, if requested
|
||
var roomNID types.RoomNID
|
||
if roomID != "" {
|
||
roomInfo, err := d.RoomInfo(ctx, roomID)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
roomNID = roomInfo.RoomNID
|
||
}
|
||
|
||
// Same as above, but for userID
|
||
var userNID types.EventStateKeyNID
|
||
if userID != "" {
|
||
stateKeysMap, err := d.EventStateKeyNIDs(ctx, []string{userID})
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
if len(stateKeysMap) != 1 {
|
||
return nil, 0, fmt.Errorf("failed to get eventStateKeyNID for %s", userID)
|
||
}
|
||
userNID = stateKeysMap[userID]
|
||
}
|
||
|
||
// Query all reported events matching the filters
|
||
reports, count, err := d.ReportedEventsTable.SelectReportedEvents(ctx, nil, from, limit, backwards, userNID, roomNID)
|
||
if err != nil {
|
||
return nil, 0, fmt.Errorf("failed to SelectReportedEvents: %w", err)
|
||
}
|
||
|
||
// TODO: The below code may be inefficient due to many DB round trips and needs to be revisited.
|
||
// For the time being, this is "good enough".
|
||
qryRoomNIDs := make([]types.RoomNID, 0, len(reports))
|
||
qryEventNIDs := make([]types.EventNID, 0, len(reports))
|
||
qryStateKeyNIDs := make([]types.EventStateKeyNID, 0, len(reports))
|
||
for _, report := range reports {
|
||
qryRoomNIDs = append(qryRoomNIDs, report.RoomNID)
|
||
qryEventNIDs = append(qryEventNIDs, report.EventNID)
|
||
qryStateKeyNIDs = append(qryStateKeyNIDs, report.ReportingUserNID, report.SenderNID)
|
||
}
|
||
|
||
// This also de-dupes the roomIDs, otherwise we would query the same
|
||
// roomIDs in GetBulkStateContent multiple times
|
||
roomIDs, err := d.RoomsTable.BulkSelectRoomIDs(ctx, nil, qryRoomNIDs)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
// TODO: replace this with something more efficient, as it loads the entire state snapshot.
|
||
stateContent, err := d.GetBulkStateContent(ctx, roomIDs, []gomatrixserverlib.StateKeyTuple{
|
||
{EventType: spec.MRoomName, StateKey: ""},
|
||
{EventType: spec.MRoomCanonicalAlias, StateKey: ""},
|
||
}, false)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
eventIDMap, err := d.EventIDs(ctx, qryEventNIDs)
|
||
if err != nil {
|
||
logrus.WithError(err).Error("unable to map eventNIDs to eventIDs")
|
||
return nil, 0, err
|
||
}
|
||
if len(eventIDMap) != len(qryEventNIDs) {
|
||
return nil, 0, fmt.Errorf("expected %d eventIDs, got %d", len(qryEventNIDs), len(eventIDMap))
|
||
}
|
||
|
||
// Get a map from EventStateKeyNID to userID
|
||
userNIDMap, err := d.EventStateKeys(ctx, qryStateKeyNIDs)
|
||
if err != nil {
|
||
logrus.WithError(err).Error("unable to map userNIDs to userIDs")
|
||
return nil, 0, err
|
||
}
|
||
|
||
// Create a cache from roomNID to roomID to avoid hitting the DB again
|
||
roomNIDIDCache := make(map[types.RoomNID]string, len(roomIDs))
|
||
for i := 0; i < len(reports); i++ {
|
||
cachedRoomID := roomNIDIDCache[reports[i].RoomNID]
|
||
if cachedRoomID == "" {
|
||
// We need to query this again, as we otherwise don't have a way to match roomNID -> roomID
|
||
roomIDs, err = d.RoomsTable.BulkSelectRoomIDs(ctx, nil, []types.RoomNID{reports[i].RoomNID})
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
if len(roomIDs) == 0 || len(roomIDs) > 1 {
|
||
logrus.Warnf("unable to map roomNID %d to a roomID, was this room deleted?", roomNID)
|
||
continue
|
||
}
|
||
roomNIDIDCache[reports[i].RoomNID] = roomIDs[0]
|
||
cachedRoomID = roomIDs[0]
|
||
}
|
||
|
||
reports[i].EventID = eventIDMap[reports[i].EventNID]
|
||
reports[i].RoomID = cachedRoomID
|
||
roomName, canonicalAlias := findRoomNameAndCanonicalAlias(stateContent, cachedRoomID)
|
||
reports[i].RoomName = roomName
|
||
reports[i].CanonicalAlias = canonicalAlias
|
||
reports[i].Sender = userNIDMap[reports[i].SenderNID]
|
||
reports[i].UserID = userNIDMap[reports[i].ReportingUserNID]
|
||
}
|
||
|
||
return reports, count, nil
|
||
}
|
||
|
||
// findRoomNameAndCanonicalAlias loops over events to find the corresponding room name and canonicalAlias
|
||
// for a given roomID.
|
||
func findRoomNameAndCanonicalAlias(events []tables.StrippedEvent, roomID string) (name, canonicalAlias string) {
|
||
for _, ev := range events {
|
||
if ev.RoomID != roomID {
|
||
continue
|
||
}
|
||
if ev.EventType == spec.MRoomName {
|
||
name = ev.ContentValue
|
||
}
|
||
if ev.EventType == spec.MRoomCanonicalAlias {
|
||
canonicalAlias = ev.ContentValue
|
||
}
|
||
// We found both wanted values, break the loop
|
||
if name != "" && canonicalAlias != "" {
|
||
break
|
||
}
|
||
}
|
||
return name, canonicalAlias
|
||
}
|
||
|
||
// FIXME TODO: Remove all this - horrible dupe with roomserver/state. Can't use the original impl because of circular loops
|
||
// it should live in this package!
|
||
|
||
func (d *Database) loadStateAtSnapshot(
|
||
ctx context.Context, stateNID types.StateSnapshotNID,
|
||
) ([]types.StateEntry, error) {
|
||
stateBlockNIDLists, err := d.StateBlockNIDs(ctx, []types.StateSnapshotNID{stateNID})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// We've asked for exactly one snapshot from the db so we should have exactly one entry in the result.
|
||
stateBlockNIDList := stateBlockNIDLists[0]
|
||
|
||
stateEntryLists, err := d.StateEntries(ctx, stateBlockNIDList.StateBlockNIDs)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
stateEntriesMap := stateEntryListMap(stateEntryLists)
|
||
|
||
// Combine all the state entries for this snapshot.
|
||
// The order of state block NIDs in the list tells us the order to combine them in.
|
||
var fullState []types.StateEntry
|
||
for _, stateBlockNID := range stateBlockNIDList.StateBlockNIDs {
|
||
entries, ok := stateEntriesMap.lookup(stateBlockNID)
|
||
if !ok {
|
||
// This should only get hit if the database is corrupt.
|
||
// It should be impossible for an event to reference a NID that doesn't exist
|
||
panic(fmt.Errorf("corrupt DB: Missing state block numeric ID %d", stateBlockNID))
|
||
}
|
||
fullState = append(fullState, entries...)
|
||
}
|
||
|
||
// Stable sort so that the most recent entry for each state key stays
|
||
// remains later in the list than the older entries for the same state key.
|
||
sort.Stable(stateEntryByStateKeySorter(fullState))
|
||
// Unique returns the last entry and hence the most recent entry for each state key.
|
||
fullState = fullState[:util.Unique(stateEntryByStateKeySorter(fullState))]
|
||
return fullState, nil
|
||
}
|
||
|
||
type stateEntryListMap []types.StateEntryList
|
||
|
||
func (m stateEntryListMap) lookup(stateBlockNID types.StateBlockNID) (stateEntries []types.StateEntry, ok bool) {
|
||
list := []types.StateEntryList(m)
|
||
i := sort.Search(len(list), func(i int) bool {
|
||
return list[i].StateBlockNID >= stateBlockNID
|
||
})
|
||
if i < len(list) && list[i].StateBlockNID == stateBlockNID {
|
||
ok = true
|
||
stateEntries = list[i].StateEntries
|
||
}
|
||
return
|
||
}
|
||
|
||
type stateEntryByStateKeySorter []types.StateEntry
|
||
|
||
func (s stateEntryByStateKeySorter) Len() int { return len(s) }
|
||
func (s stateEntryByStateKeySorter) Less(i, j int) bool {
|
||
return s[i].StateKeyTuple.LessThan(s[j].StateKeyTuple)
|
||
}
|
||
func (s stateEntryByStateKeySorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|