Files
Warp/server/internal/storage/storage.go
T
tajniak81andClaude Opus 5.5 6aaf554336
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Phase 5: federation
Implements PLAN.md Phase 5: the Server-Server API. Warp joins rooms on
other servers and lets their users join its rooms, invites across servers
with the invitee's server countersigning, rejects remote invites (locally
if the inviter is unreachable), and exchanges events, typing, receipts and
to-device messages with other homeservers — all behind the runtime switch,
which now starts and stops real outbound traffic.

The protocol lives in internal/federation with no knowledge of rooms:
X-Matrix signing (fuzzed parser), the spec's discovery algorithm keeping
the dial address and the TLS name apart, a signed client that refuses
every request while the switch is off or the policy denies the
destination, and a key ring that caches other servers' keys for at most a
week, with optional notaries.

The roomserver takes events from elsewhere through the spec's intake:
signatures and hashes (a hash mismatch keeps the redacted form), the rules
against the event's own auth events, against the state before it — with
/get_missing_events for small gaps and /state_ids for wide ones — and
against the current state, where a failure is a soft failure. Events held
without their state are outliers: a column on roomserver_events, because
the current state of a room joined over federation is almost all
outliers, and excluded from every timeline query. Forks are resolved at
the room's head. Membership across servers is make_join/send_join,
make_leave/send_leave and countersigned invites, in both directions;
m.room.server_acl is enforced on everything another server sends.
History before a remote join is backfilled when /messages pages past it,
stored at negative stream positions.

internal/federationapi is the running component: the inbound endpoints,
and an outbound queue that persists what it owes each destination, sends
it in transactions of 50 PDUs and 100 EDUs, backs off from ten seconds to
six hours, survives restarts, and drops events persisted while federation
is off rather than delivering a backlog in one burst. Its Module is what
the switch starts and stops.

Also: a TLS listener for port 8448, trusted key servers, key validity and
an outbound CA bundle in the config; per-destination health and backoff
reset in the admin API; remote profiles, aliases and directories in the
Client-Server API; a Complement federation profile. See
docs/federation.md, including its known gaps (no partial-state joins, no
key rotation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 17:14:20 +02:00

142 lines
4.2 KiB
Go

// Package storage defines the interfaces every Warp storage driver
// implements, plus the registry that turns a config into a Database.
//
// Drivers live in sub-packages (sqlite, postgres, pocketbase) and register
// themselves from an init function, the database/sql way. Nothing in this
// package imports a driver, so components can depend on the interfaces
// without pulling in every database engine.
package storage
import (
"context"
"errors"
"fmt"
"log/slog"
"maps"
"slices"
"sync"
"time"
)
// Driver names. These are the values accepted by database.driver in the
// config file.
const (
DriverSQLite = "sqlite"
DriverPostgres = "postgres"
DriverPocketBase = "pocketbase"
)
// ErrNotImplemented is returned by a driver that is compiled in but not
// finished — currently PocketBase.
var ErrNotImplemented = errors.New("not implemented by this storage driver")
// ErrNoSuchSetting is returned by SettingsStore.Get for an unknown key.
var ErrNoSuchSetting = errors.New("no such setting")
// Config is the subset of the file configuration a driver needs.
type Config struct {
Driver string
ConnectionString string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
}
// Database is the handle every component gets. Component-specific stores
// (userapi, roomserver, syncapi...) hang off it as they are implemented.
type Database interface {
// Driver reports which driver this is, for logs and admin output.
Driver() string
// Ping checks the connection is alive; used by /healthz.
Ping(ctx context.Context) error
// Migrate applies pending migrations.
Migrate(ctx context.Context) error
// PendingMigrations lists migrations that Migrate would apply.
PendingMigrations(ctx context.Context) ([]string, error)
// Settings is the server_settings key/value store backing the runtime
// settings service (federation and registration switches).
Settings() SettingsStore
// Users is the userapi store: accounts, devices, tokens, account data.
Users() UserStore
// Rooms is the roomserver store: rooms, events, room state, aliases.
Rooms() RoomStore
// Sync is the syncapi store: receipts, to-device messages, presence and
// uploaded filters.
Sync() SyncStore
// Federation is the federation store: other servers' keys, the
// outbound queue and its backoff, and the inbound transaction log.
Federation() FederationStore
// Close releases the connection pool.
Close() error
}
// SettingsStore persists the handful of settings that are mutable at
// runtime through the admin API.
type SettingsStore interface {
// Get returns the raw value for key. The boolean reports presence.
Get(ctx context.Context, key string) (string, bool, error)
// GetAll returns every setting.
GetAll(ctx context.Context) (map[string]string, error)
// Set writes key, overwriting any previous value.
Set(ctx context.Context, key, value string) error
// Delete removes key, reverting it to its configured default. Deleting
// a key that is not there is not an error.
Delete(ctx context.Context, key string) error
}
// OpenFunc opens a database for a driver.
type OpenFunc func(ctx context.Context, cfg Config, log *slog.Logger) (Database, error)
var (
registryMu sync.RWMutex
registry = map[string]OpenFunc{}
)
// Register makes a driver available to Open. It panics on a duplicate
// registration, which can only be a programming error.
func Register(name string, open OpenFunc) {
registryMu.Lock()
defer registryMu.Unlock()
if _, dup := registry[name]; dup {
panic("storage: driver " + name + " registered twice")
}
registry[name] = open
}
// Drivers lists the registered driver names, sorted.
func Drivers() []string {
registryMu.RLock()
defer registryMu.RUnlock()
return slices.Sorted(maps.Keys(registry))
}
// Open connects using the driver named in cfg.
func Open(ctx context.Context, cfg Config, log *slog.Logger) (Database, error) {
registryMu.RLock()
open, ok := registry[cfg.Driver]
registryMu.RUnlock()
if !ok {
return nil, fmt.Errorf("storage: unknown driver %q (compiled in: %v)", cfg.Driver, Drivers())
}
if log == nil {
log = slog.Default()
}
db, err := open(ctx, cfg, log.With("driver", cfg.Driver))
if err != nil {
return nil, fmt.Errorf("storage: open %s: %w", cfg.Driver, err)
}
return db, nil
}