mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 11:14:14 +00:00
`go fix` carries the modernize analyzers now, and the tree had drifted behind
them. This is the mechanical result, reviewed rather than trusted: the tool is
capable of rewriting code into something that no longer tests or does what it
did, so every non-test change was read individually and the concurrency-bearing
packages were re-run under -race.
Production code, four changes, all semantics-preserving:
- leases/manager.go: wg.Add(1) + go + defer wg.Done() becomes wg.Go. The
comment above that function turns on Add happening before the goroutine
starts, so that a Wait cannot return before the worker has run. wg.Go does
the Add synchronously on the calling goroutine, so the invariant it
describes still holds.
- auth/token/handler.go: strings.Fields -> strings.FieldsSeq, same splitting,
iterated rather than allocated.
- hold/gc/gc.go: a hand-written map copy -> maps.Copy.
- hold/pds/scan_broadcaster.go: three-clause loop -> range over int.
The rest are tests. The one worth naming is carstore_contention_test.go, where a
careless rewrite could have quietly stopped exercising contention: go fix
converted the reader and side-table goroutines to loopWG.Go but correctly
declined to touch the writer loop, which passes its index as a parameter. The
writer/reader/side-table shape and the stop channel are unchanged, so the test
still contends over the same carstore transactions.
Verified: go build for hold and appview, `make lint` 0 issues, the deploy and
credential-helper modules 0 issues, `make test` green across all 43 packages,
and -race green on leases, hold/pds, hold/gc and auth/token. The scanner module's
two lint findings are unchanged from HEAD and are in files go fix never touched.
Kept separate from the HTTP/2 commit so that one stays readable, and so this can
be reverted on its own if a modernization turns out to matter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TA9D4DjaLZTvzQ7dJbu4eg
119 lines
4.4 KiB
Go
119 lines
4.4 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// scanGuardCost makes a single bcrypt comparison expensive enough to be visible
|
|
// in wall-clock time. The defect being guarded is O(n) bcrypt over the devices
|
|
// table, so the only way to observe it is to make each comparison cost
|
|
// something; at the production cost of 10 the difference between "scanned six
|
|
// rows" and "scanned none" is lost in test noise.
|
|
const scanGuardCost = 13
|
|
|
|
// seedIndexedDeviceRows inserts n devices that are already indexed
|
|
// (secret_lookup populated) and whose bcrypt hashes are deliberately expensive.
|
|
//
|
|
// Only one hash is generated: the rest are copies with a mutated final
|
|
// character. bcrypt reads its cost from the hash prefix and runs the full key
|
|
// derivation before comparing, so a mutated tail costs exactly as much as a
|
|
// genuine hash and then fails to match — which is what a scan over non-matching
|
|
// rows does anyway.
|
|
func seedIndexedDeviceRows(t *testing.T, store *DeviceStore, did, handle string, n int) {
|
|
t.Helper()
|
|
|
|
base, err := bcrypt.GenerateFromPassword([]byte("filler-secret"), scanGuardCost)
|
|
if err != nil {
|
|
t.Fatalf("GenerateFromPassword: %v", err)
|
|
}
|
|
|
|
for i := range n {
|
|
hash := append([]byte(nil), base...)
|
|
// Vary the tail so secret_hash stays UNIQUE, keeping the base64 alphabet.
|
|
hash[len(hash)-1] = byte('a' + (i % 26))
|
|
if _, err := store.db.Exec(`
|
|
INSERT INTO devices (id, did, handle, name, secret_hash, secret_lookup,
|
|
ip_address, user_agent, created_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
`,
|
|
fmt.Sprintf("filler-device-%d", i), did, handle, fmt.Sprintf("Filler %d", i),
|
|
string(hash), deviceSecretLookup(fmt.Sprintf("atcr_device_filler_%d", i)),
|
|
"192.168.1.1", "Test Agent",
|
|
); err != nil {
|
|
t.Fatalf("seed device %d: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDeviceStore_IndexedRowsAreNeverBcryptCompared is the real regression guard
|
|
// for the O(n) bcrypt scan.
|
|
//
|
|
// Its sibling TestDeviceStore_ValidateDoesNotScanIndexedRows does not catch the
|
|
// defect: it asserts every row is indexed and that an unknown secret errors,
|
|
// and both of those hold whether or not the code scans. Removing the
|
|
// `WHERE secret_lookup IS NULL` filter from the fallback query leaves it green.
|
|
//
|
|
// What actually has to be true is that indexed rows are never handed to bcrypt.
|
|
// That is only observable in time, so the assertion is a deadline: six expensive
|
|
// rows cost ~1.8s to scan and ~0ms to skip.
|
|
func TestDeviceStore_IndexedRowsAreNeverBcryptCompared(t *testing.T) {
|
|
const fillerRows = 6
|
|
|
|
store := setupTestDB(t)
|
|
createTestUser(t, store, "did:plc:alice123", "alice.bsky.social")
|
|
seedIndexedDeviceRows(t, store, "did:plc:alice123", "alice.bsky.social", fillerRows)
|
|
|
|
// A real device, indexed at creation like every post-0028 row.
|
|
secret := newDeviceForTest(t, store, "did:plc:alice123", "alice.bsky.social", "My Device")
|
|
|
|
var unindexed int
|
|
if err := store.db.QueryRow(
|
|
`SELECT COUNT(*) FROM devices WHERE secret_lookup IS NULL OR secret_lookup = ''`,
|
|
).Scan(&unindexed); err != nil {
|
|
t.Fatalf("count unindexed: %v", err)
|
|
}
|
|
if unindexed != 0 {
|
|
t.Fatalf("fixture is wrong: %d rows unindexed, so the scan has legitimate work to do", unindexed)
|
|
}
|
|
|
|
// Anything above one expensive comparison means rows were scanned. The
|
|
// budget is generous against a ~1.8s full scan so this does not flake on a
|
|
// loaded machine.
|
|
const budget = 400 * time.Millisecond
|
|
|
|
t.Run("unknown secret", func(t *testing.T) {
|
|
start := time.Now()
|
|
_, err := store.ValidateDeviceSecret("atcr_device_no_such_secret")
|
|
elapsed := time.Since(start)
|
|
|
|
if err == nil {
|
|
t.Error("expected an error for an unknown secret")
|
|
}
|
|
// The worst case for the old code: no row ever matches, so it paid for
|
|
// every bcrypt comparison in the table before giving up.
|
|
if elapsed > budget {
|
|
t.Errorf("rejecting an unknown secret took %v (budget %v) — indexed rows are being bcrypt-compared, which is the O(n) scan this fix removed", elapsed, budget)
|
|
}
|
|
})
|
|
|
|
t.Run("known secret", func(t *testing.T) {
|
|
start := time.Now()
|
|
device, err := store.ValidateDeviceSecret(secret)
|
|
elapsed := time.Since(start)
|
|
|
|
if err != nil {
|
|
t.Fatalf("ValidateDeviceSecret() error = %v", err)
|
|
}
|
|
if device.Name != "My Device" {
|
|
t.Errorf("Name = %v, want My Device", device.Name)
|
|
}
|
|
if elapsed > budget {
|
|
t.Errorf("resolving an indexed secret took %v (budget %v) — it is not using the index", elapsed, budget)
|
|
}
|
|
})
|
|
}
|