mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix(multiplayer): harden live registration verification
This commit is contained in:
@@ -72,10 +72,10 @@ const AdvanceServerRegistrationSQL = `WITH matched AS (
|
||||
WHERE match_id = $1 AND server_id = $2 AND state = $3 AND protocol_version = $7
|
||||
AND EXISTS (SELECT 1 FROM allocations WHERE match_id = $1 AND server_id = $2 AND allocation_id = $5 AND protocol_version = $7 AND state = 'ALLOCATED')
|
||||
AND ($4 <> 'ASSIGNMENT_READY' OR (SELECT count(*) FROM assignments WHERE match_id = $1 AND expires_at > $6) = (SELECT count(*) FROM match_participants WHERE match_id = $1))
|
||||
RETURNING match_id
|
||||
RETURNING match_id, revision
|
||||
), advanced AS (
|
||||
UPDATE queue_tickets q
|
||||
SET state = $4, revision = revision + 1
|
||||
SET state = $4, revision = q.revision + 1
|
||||
FROM match_participants mp JOIN matched m ON m.match_id = mp.match_id
|
||||
WHERE q.ticket_id = mp.ticket_id AND q.player_id = mp.player_id AND q.state = $3
|
||||
RETURNING q.ticket_id
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestAllocationMatchClaimSQLFencesConcurrentWorkers(t *testing.T) {
|
||||
AllocatingMatchBuildSQL: {"match_participants", "queue_tickets", "ORDER BY q.client_build"},
|
||||
BindAllocatedMatchParticipantsSQL: {"allocation_id = $2", "server_id IS NULL", "SET server_id = $3", "FROM allocations", "state = 'ALLOCATING'", "revision = revision + 1", "SELECT revision FROM bound"},
|
||||
ReleaseAllocatedMatchClaimSQL: {"allocation_id = $2", "allocation_id = NULL", "allocation_claimed_at = NULL"},
|
||||
AdvanceServerRegistrationSQL: {"state = $4", "initial_connect_ready_at", "$6", "protocol_version = $7", "ASSIGNMENT_READY", "revision = revision + 1"},
|
||||
AdvanceServerRegistrationSQL: {"state = $4", "initial_connect_ready_at", "$6", "protocol_version = $7", "ASSIGNMENT_READY", "RETURNING match_id, revision", "revision = q.revision + 1", "SELECT revision FROM matched"},
|
||||
ServerRegistrationIdempotencyInsertSQL: {"idempotency_keys", "ON CONFLICT (scope, idempotency_key) DO NOTHING", "payload_digest"},
|
||||
ServerRegistrationIdempotencySelectSQL: {"scope = $1", "idempotency_key = $2", "FOR UPDATE"},
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ import (
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
type recordingRegistrar struct {
|
||||
delegate api.ServerRegistrar
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *recordingRegistrar) RegisterServer(ctx context.Context, binding domain.WorkloadBinding, protocol int, assignmentReady bool, idempotencyKey string, now time.Time) error {
|
||||
r.err = r.delegate.RegisterServer(ctx, binding, protocol, assignmentReady, idempotencyKey, now)
|
||||
return r.err
|
||||
}
|
||||
|
||||
func TestRealSupervisorRegistersAllocatedServerThroughControlPlane(t *testing.T) {
|
||||
dsn := os.Getenv("COSMIC_CLASH_POSTGRES_DSN")
|
||||
if dsn == "" {
|
||||
@@ -102,7 +112,8 @@ func TestRealSupervisorRegistersAllocatedServerThroughControlPlane(t *testing.T)
|
||||
}))
|
||||
defer sdk.Close()
|
||||
rosterPath := filepath.Join(t.TempDir(), "join-roster.json")
|
||||
service := &api.Service{ServerRegistrar: api.ServerRegistrarFromStore(db), WorkloadVerify: api.WorkloadVerifierFromSignedToken(secret, db), Roster: func(ctx context.Context, binding domain.WorkloadBinding, at time.Time) ([][]byte, error) {
|
||||
registrar := &recordingRegistrar{delegate: api.ServerRegistrarFromStore(db)}
|
||||
service := &api.Service{ServerRegistrar: registrar, WorkloadVerify: api.WorkloadVerifierFromSignedToken(secret, db), Roster: func(ctx context.Context, binding domain.WorkloadBinding, at time.Time) ([][]byte, error) {
|
||||
return store.GetAssignmentRoster(ctx, db, binding.MatchID, binding.ServerID, at)
|
||||
}, Now: func() time.Time { return now }}
|
||||
control := httptest.NewServer(service.Handler())
|
||||
@@ -116,7 +127,10 @@ func TestRealSupervisorRegistersAllocatedServerThroughControlPlane(t *testing.T)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := supervisor.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
var matchState, matchServerID, allocationID, ticketState string
|
||||
_ = db.QueryRowContext(ctx, `SELECT state, server_id, allocation_id FROM matches WHERE match_id = 'supervisor-live-match'`).Scan(&matchState, &matchServerID, &allocationID)
|
||||
_ = db.QueryRowContext(ctx, `SELECT state FROM queue_tickets WHERE ticket_id = 'supervisor-live-ticket-0'`).Scan(&ticketState)
|
||||
t.Fatalf("start supervisor: %v (registration error=%v; match state=%q server=%q allocation=%q ticket=%q)", err, registrar.err, matchState, matchServerID, allocationID, ticketState)
|
||||
}
|
||||
if err := supervisor.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user