Skip to content

Commit 378561c

Browse files
tuanngocptnclaude
andcommitted
fix(node): a test about arithmetic was failing on a timeout
"never exceeds the configured ceiling" made 400 sequential Durable Object round-trips to drive proof-of-work difficulty past its cap. Reaching the cap takes 24 — one bit per CREATES_PER_EXTRA_BIT creates, over six bits of headroom — so the loop was sixteen times longer than the property needed, and slow enough that a loaded runner blew vitest's 5s default. Same signature as defect 45: green on CI, red on Deploy staging, same commit. Two workflows running one suite on differently-loaded runners is the only thing that separates a test whose runtime decides the result from one whose logic does. The number was 400 because CREATES_PER_EXTRA_BIT was module-private, so the test could not compute the real bound and reached for one comfortably past it. Exporting it makes the loop derived: (ceiling - floor) * perBit * 2. That is 48 hops instead of 400, 375ms instead of a timeout, and it stays correct if either bit setting changes — which the magic number did not. Raising the timeout would have been the wrong fix: it keeps a test that takes seconds to assert min(a, b), and moves the threshold rather than the cause. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ND8DWQLFchUayY5xeP6vb
1 parent 909d67b commit 378561c

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

apps/node/src/do/source-quota.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ const CREATE_WINDOW_MS = 3_600_000
4848
* in mind — the escalation should still be climbing when the hard cap arrives, or it would be
4949
* decoration.
5050
*/
51-
const CREATES_PER_EXTRA_BIT = 4
51+
/**
52+
* Creates per extra bit of difficulty.
53+
*
54+
* Exported so `test/abuse-controls.test.ts` can work out how many reservations reach the ceiling
55+
* instead of guessing a number far past it — the guess was 400 where 24 suffice, and 400 sequential
56+
* Durable Object hops is slow enough that the test timed out on a loaded runner (defect 55).
57+
*/
58+
export const CREATES_PER_EXTRA_BIT = 4
5259

5360
export interface QuotaLimits {
5461
readonly maxConcurrent: number

apps/node/test/abuse-controls.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import { reset, SELF, env as testEnv } from "cloudflare:test"
1212
import { solveChallenge } from "@nport/worker-kit"
1313
import { afterEach, beforeEach, describe, expect, it } from "vitest"
14+
import { CREATES_PER_EXTRA_BIT } from "../src/do/source-quota"
1415
import type { Env } from "../src/types"
1516
import { FakeCloudflare } from "./fake-cloudflare"
1617

@@ -250,11 +251,18 @@ describe("proof-of-work difficulty rises per source", () => {
250251
// Unbounded escalation would eventually price out a legitimate heavy user permanently, and there
251252
// is no way for them to appeal to anyone — there are no accounts.
252253
const quota = env.SOURCE_QUOTA.get(env.SOURCE_QUOTA.idFromName("synthetic"))
254+
const floor = Number(env.POW_DIFFICULTY_BITS)
253255
const ceiling = Number(env.POW_MAX_DIFFICULTY_BITS)
254-
for (let index = 0; index < 400; index += 1) {
256+
257+
// **Derived, not guessed.** Reaching the ceiling takes one bit per `CREATES_PER_EXTRA_BIT`
258+
// creates; twice that is comfortably past it, which is the property under test. The previous
259+
// 400 was sixteen times more than needed, and 400 sequential Durable Object hops took long
260+
// enough that a loaded runner blew the 5 s default and failed a test about *arithmetic*.
261+
const pastTheCeiling = (ceiling - floor) * CREATES_PER_EXTRA_BIT * 2
262+
for (let index = 0; index < pastTheCeiling; index += 1) {
255263
await quota.reserve(`bulk${index}`, { maxConcurrent: 10_000, maxPerHour: 10_000 })
256264
}
257-
expect(await quota.difficulty(Number(env.POW_DIFFICULTY_BITS), ceiling)).toBe(ceiling)
265+
expect(await quota.difficulty(floor, ceiling)).toBe(ceiling)
258266
})
259267
})
260268

docs/ROADMAP.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,25 @@ into nothing, and swallowed the failure — by design, silently. That was the ga
925925
a listed-then-idle node slipping out of a directory nobody is reading harms nobody. What it
926926
blocks is trusting `/v1/nodes` as a health display for a quiet node
927927

928+
**55. A test about arithmetic failed on a timeout.** `abuse-controls.test.ts`'s "never exceeds the
929+
configured ceiling" made **400 sequential Durable Object round-trips** to drive the proof-of-work
930+
difficulty past its cap. Reaching the cap takes 24 — one bit per `CREATES_PER_EXTRA_BIT` creates,
931+
across six bits of headroom — so the loop was sixteen times longer than the property needed, and slow
932+
enough that a loaded runner blew vitest's 5 s default.
933+
934+
Same signature as defect 45: **green on `CI`, red on `Deploy staging`, same commit.** Two workflows
935+
running the same suite on differently-loaded runners is the only thing that distinguishes a test
936+
whose *runtime* decides the result from one whose logic does.
937+
938+
The number was 400 because `CREATES_PER_EXTRA_BIT` was module-private, so the test could not compute
939+
the real bound and reached for one comfortably past it. Exporting the constant makes the loop
940+
derived: `(ceiling - floor) * CREATES_PER_EXTRA_BIT * 2`. That is 48 hops rather than 400, the test
941+
runs in 375 ms rather than timing out, **and it stays correct if either bit setting changes** — which
942+
the magic number did not.
943+
944+
**Raising the timeout would have been the wrong fix.** It would have kept a test that takes seconds
945+
to assert `min(a, b)`, and moved the threshold rather than the cause. The guess was the defect.
946+
928947
**54. Four screens had been built without anyone looking at them, and four of them were wrong.**
929948
`tauri dev` does launch on this machine — the process runs — but the window cannot be captured
930949
without screen-recording permission, which is why "no browser tier" had been treated as "no way to

0 commit comments

Comments
 (0)