-
Notifications
You must be signed in to change notification settings - Fork 16
feat(ensrainbow): implement background database bootstrapping and new readiness endpoint #1968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
djstrong
merged 23 commits into
main
from
1610-start-ensrainbow-server-immediately-and-download-database-in-background
Apr 29, 2026
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
8dcebcb
feat(ensrainbow): implement background database bootstrapping and new…
djstrong 3c23870
Merge branch 'main' into 1610-start-ensrainbow-server-immediately-and…
djstrong 5ec830f
feat(ensrainbow): enhance readiness checks and API response handling
djstrong 243667f
feat(ensrainbow): implement graceful shutdown during bootstrap
djstrong 1f9d822
refactor(ensrainbow): implement closeHttpServer utility for graceful …
djstrong d3f9c0d
fix(ensrainbow): improve database handling during bootstrap failure
djstrong 74cf754
fix(ensrainbow): improve database closure handling in the close method
djstrong 89ac55f
fix(ensrainbow): enhance shutdown handling and error management
djstrong a438a6a
fix(ensrainbow): enhance signal handling and database extraction cleanup
djstrong ac844ce
fix(ensrainbow): improve logging and error handling during database o…
djstrong 2b498f9
fix(ensrainbow): refine readiness checks and documentation updates
djstrong cac82ed
fix(ensrainbow): enhance public config readiness handling
djstrong 83a4453
Merge branch 'main' into 1610-start-ensrainbow-server-immediately-and…
djstrong 66be6f7
Merge branch 'main' into 1610-start-ensrainbow-server-immediately-and…
djstrong a9c46de
refactor: enhance entrypoint command to include DB config in bootstra…
djstrong 762036a
feat: introduce new /ready endpoint and enhance Docker entrypoint for…
djstrong c181652
refactor: remove unused comments and streamline code in entrypoint co…
djstrong f05bfe7
refactor: update test setup to use temporary directories for entrypoi…
djstrong 1453be2
feat: implement structured error handling in EnsRainbowApiClient and …
djstrong 6410edd
Merge branch 'main' into 1610-start-ensrainbow-server-immediately-and…
djstrong 73848f8
refactor: simplify ENSRainbow readiness check by removing retry logic…
djstrong 816bba5
refactor: replace ErrorCode.ServiceUnavailable with HTTP status code …
djstrong 8dbe6df
Merge branch 'main' into 1610-start-ensrainbow-server-immediately-and…
djstrong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| --- | ||
| "ensrainbow": minor | ||
| "@ensnode/ensrainbow-sdk": minor | ||
| "ensindexer": patch | ||
| --- | ||
|
|
||
| ENSRainbow now starts its HTTP server immediately and downloads/validates its database in the background, instead of blocking container startup behind a netcat placeholder. | ||
|
|
||
| - **New `GET /ready` endpoint**: returns `200 { status: "ok" }` once the database is attached, or `503 Service Unavailable` while ENSRainbow is still bootstrapping. `/health` is now a pure liveness probe that succeeds as soon as the HTTP server is listening. | ||
| - **503 responses for API routes during bootstrap**: `/v1/heal`, `/v1/labels/count`, and `/v1/config` return a structured `ServiceUnavailableError` (`errorCode: 503`) until the database is ready. | ||
| - **New Docker entrypoint**: the container now runs `pnpm run entrypoint` from the `apps/ensrainbow` working directory (implemented in Node via `tsx src/cli.ts entrypoint`), which replaces `scripts/entrypoint.sh` and the `netcat` workaround. | ||
| - **Graceful shutdown during bootstrap**: SIGTERM/SIGINT now abort an in-flight bootstrap. Spawned `download`/`tar` child processes are terminated (SIGTERM → SIGKILL after a 5s grace period) and any partially-opened LevelDB handle is closed before the HTTP server and DB-backed server shut down, so the container exits promptly without leaking child processes or LevelDB locks. | ||
| - **SDK client**: added `EnsRainbowApiClient.ready()`, plus `EnsRainbow.ReadyResponse` / `EnsRainbow.ServiceUnavailableError` types and `ErrorCode.ServiceUnavailable`. The client now throws a typed `EnsRainbowHttpError` (with structured `status` / `statusText` properties) from `ready()`, `health()`, and `config()` whenever the service responds with a non-2xx HTTP status, so callers can branch their retry/abort logic on the status without parsing message strings. | ||
| - **ENSIndexer**: `waitForEnsRainbowToBeReady` now polls `/ready` (via `ensRainbowClient.ready()`) instead of `/health`, so it correctly waits for the database to finish bootstrapping. It also aborts retries immediately on non-503 HTTP responses (e.g. `404` from a misconfigured `ENSRAINBOW_URL`, `500` from a broken instance) instead of blocking startup for ~1h, while still retrying on `503 Service Unavailable` and on transient network errors. | ||
|
|
||
| **Migration**: if you previously polled `GET /health` to gate traffic on database readiness, switch to `GET /ready` (or `client.ready()`). `/health` is still available and still returns `200`, but it now indicates liveness only. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
|
|
||
| import "@/lib/__test__/mockLogger"; | ||
|
|
||
| import { setupConfigMock } from "@/lib/__test__/mockConfig"; | ||
|
|
||
| setupConfigMock(); | ||
|
|
||
| import { EnsRainbowHttpError } from "@ensnode/ensrainbow-sdk"; | ||
|
|
||
| import { shouldRetryReadinessCheck } from "./singleton"; | ||
|
|
||
| /** | ||
| * `shouldRetryReadinessCheck` is the heart of the readiness-check retry policy used by | ||
| * `waitForEnsRainbowToBeReady`. The integration with `p-retry` is a thin wiring (passing this | ||
| * predicate into `pRetry({ shouldRetry })`), so we exhaustively unit-test the predicate here | ||
| * rather than running the full retry loop with fake timers (which is fragile against `p-retry` | ||
| * internals and module-cache resets). | ||
| */ | ||
| describe("shouldRetryReadinessCheck", () => { | ||
| it("retries on EnsRainbowHttpError with status 503 (still bootstrapping)", () => { | ||
| const error = new EnsRainbowHttpError("not ready", 503, "Service Unavailable"); | ||
| expect(shouldRetryReadinessCheck(error)).toBe(true); | ||
| }); | ||
|
|
||
| it("aborts on EnsRainbowHttpError with status 404 (likely misconfigured base URL)", () => { | ||
| const error = new EnsRainbowHttpError("not found", 404, "Not Found"); | ||
| expect(shouldRetryReadinessCheck(error)).toBe(false); | ||
| }); | ||
|
|
||
| it("aborts on EnsRainbowHttpError with status 500 (server error)", () => { | ||
| const error = new EnsRainbowHttpError("boom", 500, "Internal Server Error"); | ||
| expect(shouldRetryReadinessCheck(error)).toBe(false); | ||
| }); | ||
|
|
||
| it("aborts on EnsRainbowHttpError with status 502 (bad gateway)", () => { | ||
| const error = new EnsRainbowHttpError("bad gateway", 502, "Bad Gateway"); | ||
| expect(shouldRetryReadinessCheck(error)).toBe(false); | ||
| }); | ||
|
|
||
| it("aborts on EnsRainbowHttpError with status 401 (auth misconfiguration)", () => { | ||
| const error = new EnsRainbowHttpError("unauthorized", 401, "Unauthorized"); | ||
| expect(shouldRetryReadinessCheck(error)).toBe(false); | ||
| }); | ||
|
|
||
| it("retries on plain Error (network/DNS/ECONNREFUSED), since these are transient during cold start", () => { | ||
| expect(shouldRetryReadinessCheck(new TypeError("fetch failed"))).toBe(true); | ||
| expect(shouldRetryReadinessCheck(new Error("connect ECONNREFUSED 127.0.0.1:3223"))).toBe(true); | ||
| }); | ||
|
|
||
| it("retries on non-Error rejection values (defensive fallback)", () => { | ||
| expect(shouldRetryReadinessCheck("string error")).toBe(true); | ||
| expect(shouldRetryReadinessCheck(undefined)).toBe(true); | ||
| expect(shouldRetryReadinessCheck(null)).toBe(true); | ||
| expect(shouldRetryReadinessCheck({ message: "weird" })).toBe(true); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| import { ensRainbowClient } from "@/lib/ensrainbow/singleton"; | ||
| import { ensRainbowClient, waitForEnsRainbowToBeReady } from "@/lib/ensrainbow/singleton"; | ||
| import { PublicConfigBuilder } from "@/lib/public-config-builder/public-config-builder"; | ||
|
|
||
| export const publicConfigBuilder = new PublicConfigBuilder(ensRainbowClient); | ||
| export const publicConfigBuilder = new PublicConfigBuilder( | ||
| ensRainbowClient, | ||
| waitForEnsRainbowToBeReady, | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.