From 7aa5f75b8489cb0580fc7dbd3fb47d1c89798e6c Mon Sep 17 00:00:00 2001 From: julio4 <30329843+julio4@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:50:56 +0800 Subject: [PATCH 1/2] feat: add decker manifest and migrate local devnet docs Introduce decker.ts (opstack recipe, host-process op-rbuilder, flashblocks, chain-monitor) as the primary local devnet flow, with a `just devnet` target and a rewritten local-testing/README.md. The builder-playground path (playground.yaml, run-playground) is kept fully intact as a deprecated fallback during the transition. --- .gitignore | 3 ++ README.md | 2 +- decker.ts | 82 +++++++++++++++++++++++++++++++++++ justfile | 6 +++ local-testing/README.md | 94 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 decker.ts diff --git a/.gitignore b/.gitignore index f01ed3a0a..414c524a1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ genesis.json /op-reth .env +# decker (local devnet) clone + generated manifests/runtime state +/.decker + # editors .code .idea diff --git a/README.md b/README.md index c658584bc..b7c51df13 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ This runs both unit tests and integration tests. ## Local Devnet -Local development environment setup and configuration files can be found in [`local-testing/README.md`](./local-testing/README.md). +Local devnet setup — via [decker](https://github.com/flashbots/decker) (`decker.ts`), with builder-playground kept as a deprecated fallback — is documented in [`local-testing/README.md`](./local-testing/README.md). ## Running GitHub actions locally diff --git a/decker.ts b/decker.ts new file mode 100644 index 000000000..b537b4b57 --- /dev/null +++ b/decker.ts @@ -0,0 +1,82 @@ +// decker (https://github.com/flashbots/decker) manifest for op-rbuilder's local +// devnet — the primary local dev flow; see local-testing/README.md for +// prerequisites/quickstart. builder-playground (local-testing/playground.yaml) +// is kept as a deprecated fallback for the duration of the migration. +// +// Requires three decker opstack options not yet on `main` as of 2026-07-20: +// - builderBinary (jules/opstack-builder-process): run op-rbuilder as a host process +// - flashblocks (jules/opstack-flashblocks): op-rbuilder's flashblocks ws stream +// - chainMonitor (jules/opstack-chain-monitor): chain-monitor sidecar +// Until they land on flashbots/decker main, point `source` below at a local +// checkout of their tip instead, e.g.: +// source: "file:///path/to/decker-chain-monitor", ref: "HEAD", + +// A post-up hook. Author each in its own module typed against decker's real +// `Script`/`Recipe` (from your clone); here the array just needs to accept them, +// so the parameter is left open. +type Script = (recipe: never) => void | Promise; + +// A Recipe value, or a Prototype value keyed by name in `prototypes`. Build +// these in their own modules typed against decker's real `Recipe`/`Prototype` +// (from your clone); here they're left open so this file stays self-contained. +type Recipe = Record; +type Prototype = Record; + +type DeckerProject = { + decker: { + source: string; + ref: string; + into?: string; + }; + recipe: string | Recipe; + options?: Record; + prototypes?: { + pods?: Record; + processes?: Record; + }; + scripts?: Script[]; + target?: { + pods?: string; + processes?: string; + }; +}; + +export const project: DeckerProject = { + decker: { + source: "https://github.com/flashbots/decker.git", + ref: "main", + into: ".decker", + }, + recipe: "opstack", + options: { + // Run a real op-rbuilder behind rollup-boost instead of the sequencer EL + // building its own blocks. + externalBuilder: "op-rbuilder", + // Host process instead of the pinned container, so this devnet always runs + // your local op-rbuilder checkout. `just devnet` builds this exact binary + // (`make op-rbuilder-profiling`) before starting decker, and `just` always + // runs recipes from this justfile's directory, so the relative path below + // resolves correctly through `just devnet`. It does NOT resolve relative to + // this file — a bare `decker start`/`up` run from elsewhere would resolve it + // against that shell's cwd instead (decker never chdirs; see the + // builderBinary option doc in the opstack recipe). cd here first if you + // invoke decker directly instead of through `just devnet`. + builderBinary: "./target/profiling/op-rbuilder", + // Karst (the default l2Fork) needs 1s+ blocks to be useful for local iteration. + l2BlockTime: 1, + // builder-playground's tuned cadence — these numbers are also flashblocks' + // own defaults; spelled out here for discoverability. ws stream on :1111. + flashblocks: { blockTimeMs: 200, endBufferMs: 75, sendOffsetMs: -30, continuousBuild: true }, + // Sidecar watching L1 + L2 for stalled/missed blocks + the builder's wallet. + chainMonitor: true, + }, + // Note on the builder's RPC port: decker's op-rbuilder container/process + // prototype hardcodes rpc=9645 (authrpc=9651, metrics=6062) with no options + // surface (recipe or prototype-override) to repin it to builder-playground's + // old :2222 without risking a mismatch between the port it actually binds and + // what other services resolve by name (see local-testing/README.md's service + // table) — so :9645 is the port to use everywhere for this devnet. + // + // Docker instead of podman (no rootless podman socket available): + // target: { pods: "docker-compose" }, +}; diff --git a/justfile b/justfile index 23c05d476..75cf3abbd 100644 --- a/justfile +++ b/justfile @@ -1,3 +1,9 @@ +# Build the profiling op-rbuilder binary and start the decker local devnet +# (see local-testing/README.md; requires the `decker` CLI on PATH) +devnet: + make op-rbuilder-profiling FEATURES=telemetry,loki + decker start + # Build and run op-rbuilder in playground mode for testing run-playground: cargo build --bin op-rbuilder -p op-rbuilder diff --git a/local-testing/README.md b/local-testing/README.md index 0e640518b..bad5ca674 100644 --- a/local-testing/README.md +++ b/local-testing/README.md @@ -1,14 +1,92 @@ # Local Dev Environment -This directory contains local development environment configuration for `op-rbuilder`. +Local devnet configuration for `op-rbuilder`, powered by [decker](https://github.com/flashbots/decker) (`decker.ts` at the repo root runs the `opstack` recipe with a host-process `op-rbuilder`). [builder-playground](https://github.com/flashbots/builder-playground) (`local-testing/playground.yaml`) is kept working as a deprecated fallback for the rest of this migration — see the [Legacy appendix](#legacy-builder-playground-deprecated). ## Prerequisites +- **podman**, with its (rootless) API socket running. Prefer Docker? Install **docker** instead and uncomment the `target: { pods: "docker-compose" }` line at the bottom of `decker.ts`. +- **decker** CLI: + ```bash + curl -sSfL https://raw.githubusercontent.com/flashbots/decker/main/install.sh | bash + ``` +- **process-compose** — `op-rbuilder` runs as a host process here (not a container), which decker drives through its `process-compose` renderer: `brew install process-compose` (macOS) or see the [process-compose install docs](https://github.com/F1bonacc1/process-compose#installation) (Linux). +- [flashbots/contender](https://github.com/flashbots/contender), for traffic generation. + +## Quickstart + +From the repository root: + +```bash +just devnet +``` + +This builds the profiling `op-rbuilder` binary (`make op-rbuilder-profiling FEATURES=telemetry,loki` — the binary `decker.ts` points `builderBinary` at) and then runs `decker start`, which clones the pinned decker source into `.decker/` on first use and brings the whole stack up in the foreground (Ctrl+C tears it down). + +Equivalent, one step at a time: + +```bash +make op-rbuilder-profiling FEATURES=telemetry,loki +decker pull # clone the pinned decker source into .decker/ (skips an existing clone) +decker start # foreground, Ctrl+C to stop; use `decker up` + `decker down` to detach instead +``` + +See `decker.ts` for the exact recipe options in use (fork, L2 block time, flashblocks tuning, chain-monitor); `decker up`/`start` print every pod/process and its port right before the run starts. + +## Generate Local Traffic + +```bash +contender spam --tps 50 -r http://localhost:9645 --optimism --min-balance 0.14eth --forever +``` + +You should start to see blocks being built and landed on-chain with `contender` transactions. + +## Send a Test Transaction + +```bash +decker test --rpc 9645 +``` + +(`--rpc` accepts a bare port as shorthand for `http://localhost:`, or a full URL.) + +> The obvious legacy-style equivalent, `builder-playground test http://localhost:2222 --timeout 30s --retries 10`, was actually silently broken: `test` ignores positional arguments and requires `--rpc`, so that invocation really exercised the command's *default* target (`:8545`), never the builder. See the [Legacy appendix](#legacy-builder-playground-deprecated) for the corrected form. + +## Services + +| Service | Port | Notes | +|---|---|---| +| op-rbuilder (builder) RPC | `9645` | decker's fixed port — **not** builder-playground's old `:2222` (see `decker.ts`) | +| op-rbuilder authrpc | `9651` | rollup-boost's engine-API target | +| op-rbuilder metrics | `6062` | | +| op-rbuilder flashblocks (ws) | `1111` | `ws://localhost:1111` | +| chain-monitor | `8087` | `http://localhost:8087/metrics` | +| L1 EL (`el-1`) RPC | `8545` | | +| L2 sequencer EL RPC | `9545` | fallback target rollup-boost uses if the builder is down | +| L1 explorer (Blockscout) | `3002` | `http://localhost:3002` | +| L2 explorer (Blockscout) | `3003` | `http://localhost:3003` | +| Pod logs (Dozzle) | `18080` | podman only; under `docker-compose` use `decker attach` for logs | + +## Builder-Only Restart Loop + +To iterate on `op-rbuilder` itself without tearing down the rest of the devnet: stop decker's own builder process (it holds `:9645`/`:9651`), rebuild, then run the binary directly from the repository root — it auto-detects the running devnet's artifacts: + +```bash +make op-rbuilder-profiling FEATURES=telemetry,loki +./target/profiling/op-rbuilder node --builder.playground +``` + +A bare `--builder.playground` (no path) resolves `./.decker/runtime/artifacts` first, falling back to the legacy `$HOME/.local/state/builder-playground/devnet` if that doesn't exist. It fixes `authrpc` at `:9651` (so rollup-boost keeps working unchanged) and trusted-peers the sequencer EL directly; its own RPC prefers `:2222` but falls back to a random free port if that's taken — check the startup logs for the actual one. This auto-detection requires the decker-aware `--builder.playground` rewrite (`feat/decker-devnet-detection`); on an older `op-rbuilder` build, pass the directory explicitly instead: `--builder.playground .decker/runtime/artifacts`. + +## Karst + +The devnet defaults to the `karst` L2 fork, which requires the `op-reth` v2.3.x dependency line — `main` is already on it, but older branches will not produce valid Karst blocks. Testing an older branch: override the fork via `decker.ts`'s `options.l2Fork` (e.g. `"jovian"` or `"isthmus"`), which also switches the sequencer EL back to `op-geth`. + +## Legacy: builder-playground (deprecated) + +Kept working during the migration to decker above; do not add anything new here. + 1. [flashbots/builder-playground](https://github.com/flashbots/builder-playground). 1. [flashbots/contender](https://github.com/flashbots/contender). -## Start Local Devnet - From the repository root: ```bash @@ -31,20 +109,16 @@ Services: Logs can be found at `$HOME/.local/state/builder-playground/sessions/latest/logs/` -## Generate Local Traffic - -Run `contender`: +Generate local traffic with `contender`: ```bash contender spam --tps 50 -r http://localhost:2222 --optimism --min-balance 0.14eth --forever ``` -You should start to see blocks being built and landed on-chain with `contender` transactions. - -Alternatively, send a single test transaction: +Send a single test transaction (note the `--rpc` flag — a bare target URL is silently ignored): ```bash -builder-playground test http://localhost:2222 --timeout 30s --retries 10 +builder-playground test --rpc http://localhost:2222 --timeout 30s --retries 10 ``` `op-rbuilder` automatically detects settings and ports from the running playground via `--builder.playground`. From c9ff0f8b209f806b49fd62ef8d3026bd1725fc32 Mon Sep 17 00:00:00 2001 From: julio4 <30329843+julio4@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:19:16 +0800 Subject: [PATCH 2/2] feat: add Prometheus + Grafana observability + decker devnet doc --- README.md | 2 +- decker.ts | 96 +++++++++++-------------------------- justfile | 4 +- local-testing/README.md | 46 +++++++++++------- local-testing/decker/obs.ts | 83 ++++++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 local-testing/decker/obs.ts diff --git a/README.md b/README.md index b7c51df13..d8714b85b 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ This runs both unit tests and integration tests. ## Local Devnet -Local devnet setup — via [decker](https://github.com/flashbots/decker) (`decker.ts`), with builder-playground kept as a deprecated fallback — is documented in [`local-testing/README.md`](./local-testing/README.md). +Local devnet setup via [decker](https://github.com/flashbots/decker) is documented in [`local-testing/README.md`](./local-testing/README.md). ## Running GitHub actions locally diff --git a/decker.ts b/decker.ts index b537b4b57..9500236e2 100644 --- a/decker.ts +++ b/decker.ts @@ -1,82 +1,42 @@ // decker (https://github.com/flashbots/decker) manifest for op-rbuilder's local -// devnet — the primary local dev flow; see local-testing/README.md for -// prerequisites/quickstart. builder-playground (local-testing/playground.yaml) -// is kept as a deprecated fallback for the duration of the migration. -// -// Requires three decker opstack options not yet on `main` as of 2026-07-20: -// - builderBinary (jules/opstack-builder-process): run op-rbuilder as a host process -// - flashblocks (jules/opstack-flashblocks): op-rbuilder's flashblocks ws stream -// - chainMonitor (jules/opstack-chain-monitor): chain-monitor sidecar -// Until they land on flashbots/decker main, point `source` below at a local -// checkout of their tip instead, e.g.: -// source: "file:///path/to/decker-chain-monitor", ref: "HEAD", +// devnet. See local-testing/README.md for quickstart. -// A post-up hook. Author each in its own module typed against decker's real -// `Script`/`Recipe` (from your clone); here the array just needs to accept them, -// so the parameter is left open. -type Script = (recipe: never) => void | Promise; +import { recipe as opstackRecipe, type OpstackOptions } from "./.decker/recipes/opstack.ts"; +import type { DeckerProject } from "./.decker/utils/manifest.ts"; +import type { Recipe } from "./.decker/utils/types.ts"; +import { obsPods } from "./local-testing/decker/obs.ts"; -// A Recipe value, or a Prototype value keyed by name in `prototypes`. Build -// these in their own modules typed against decker's real `Recipe`/`Prototype` -// (from your clone); here they're left open so this file stays self-contained. -type Recipe = Record; -type Prototype = Record; - -type DeckerProject = { - decker: { - source: string; - ref: string; - into?: string; - }; - recipe: string | Recipe; - options?: Record; - prototypes?: { - pods?: Record; - processes?: Record; - }; - scripts?: Script[]; - target?: { - pods?: string; - processes?: string; - }; +const options: OpstackOptions = { + // Run op-rbuilder behind rollup-boost + externalBuilder: "op-rbuilder", + // Host process instead of pinned op-rbuilder container + // Use `just devnet` or `make op-rbuilder-profiling` to compile binary. + builderBinary: "./target/profiling/op-rbuilder", + // Karst needs 1s+ blocks for local iteration. + l2BlockTime: 1, + flashblocks: { blockTimeMs: 200, endBufferMs: 75, sendOffsetMs: -30, continuousBuild: true }, + chainMonitor: true, }; +// Builder's RPC port: decker's op-rbuilder container/process +// prototype hardcodes rpc=9645 (authrpc=9651, metrics=6062) with no options +// surface (recipe or prototype-override) to repin it to builder-playground's +// old :2222 without risking a mismatch between the port it actually binds and +// what other services resolve by name so :9645 is the port to use everywhere +// for this devnet. +const opstack = opstackRecipe(options); + +// Prometheus + Grafana appended on top of the opstack recipe's own pods +// (see local-testing/decker/obs.ts). +const recipe: Recipe = { ...opstack, pods: [...opstack.pods, ...obsPods(opstack)] }; + export const project: DeckerProject = { decker: { source: "https://github.com/flashbots/decker.git", ref: "main", into: ".decker", }, - recipe: "opstack", - options: { - // Run a real op-rbuilder behind rollup-boost instead of the sequencer EL - // building its own blocks. - externalBuilder: "op-rbuilder", - // Host process instead of the pinned container, so this devnet always runs - // your local op-rbuilder checkout. `just devnet` builds this exact binary - // (`make op-rbuilder-profiling`) before starting decker, and `just` always - // runs recipes from this justfile's directory, so the relative path below - // resolves correctly through `just devnet`. It does NOT resolve relative to - // this file — a bare `decker start`/`up` run from elsewhere would resolve it - // against that shell's cwd instead (decker never chdirs; see the - // builderBinary option doc in the opstack recipe). cd here first if you - // invoke decker directly instead of through `just devnet`. - builderBinary: "./target/profiling/op-rbuilder", - // Karst (the default l2Fork) needs 1s+ blocks to be useful for local iteration. - l2BlockTime: 1, - // builder-playground's tuned cadence — these numbers are also flashblocks' - // own defaults; spelled out here for discoverability. ws stream on :1111. - flashblocks: { blockTimeMs: 200, endBufferMs: 75, sendOffsetMs: -30, continuousBuild: true }, - // Sidecar watching L1 + L2 for stalled/missed blocks + the builder's wallet. - chainMonitor: true, - }, - // Note on the builder's RPC port: decker's op-rbuilder container/process - // prototype hardcodes rpc=9645 (authrpc=9651, metrics=6062) with no options - // surface (recipe or prototype-override) to repin it to builder-playground's - // old :2222 without risking a mismatch between the port it actually binds and - // what other services resolve by name (see local-testing/README.md's service - // table) — so :9645 is the port to use everywhere for this devnet. - // + recipe, // Docker instead of podman (no rootless podman socket available): // target: { pods: "docker-compose" }, }; diff --git a/justfile b/justfile index 75cf3abbd..9f2affab0 100644 --- a/justfile +++ b/justfile @@ -1,5 +1,5 @@ -# Build the profiling op-rbuilder binary and start the decker local devnet -# (see local-testing/README.md; requires the `decker` CLI on PATH) +# Build profiling op-rbuilder binary and start the decker local devnet +# (see local-testing/README.md; requires `decker` CLI on PATH) devnet: make op-rbuilder-profiling FEATURES=telemetry,loki decker start diff --git a/local-testing/README.md b/local-testing/README.md index bad5ca674..9b97c37b0 100644 --- a/local-testing/README.md +++ b/local-testing/README.md @@ -1,36 +1,41 @@ # Local Dev Environment -Local devnet configuration for `op-rbuilder`, powered by [decker](https://github.com/flashbots/decker) (`decker.ts` at the repo root runs the `opstack` recipe with a host-process `op-rbuilder`). [builder-playground](https://github.com/flashbots/builder-playground) (`local-testing/playground.yaml`) is kept working as a deprecated fallback for the rest of this migration — see the [Legacy appendix](#legacy-builder-playground-deprecated). +Local devnet configuration for `op-rbuilder`, powered by [decker](https://github.com/flashbots/decker). [builder-playground](https://github.com/flashbots/builder-playground) (`local-testing/playground.yaml`) is kept working as a legacy fallback, see [Legacy appendix](#legacy-builder-playground-deprecated). ## Prerequisites -- **podman**, with its (rootless) API socket running. Prefer Docker? Install **docker** instead and uncomment the `target: { pods: "docker-compose" }` line at the bottom of `decker.ts`. +- **podman**, with its (rootless) API socket running. For docker instead, uncomment the `target: { pods: "docker-compose" }` line at the bottom of `decker.ts`. - **decker** CLI: ```bash curl -sSfL https://raw.githubusercontent.com/flashbots/decker/main/install.sh | bash ``` -- **process-compose** — `op-rbuilder` runs as a host process here (not a container), which decker drives through its `process-compose` renderer: `brew install process-compose` (macOS) or see the [process-compose install docs](https://github.com/F1bonacc1/process-compose#installation) (Linux). +- **process-compose**: `op-rbuilder` runs as a host process here, which decker drives through its `process-compose` renderer. - [flashbots/contender](https://github.com/flashbots/contender), for traffic generation. ## Quickstart +**First time only**: clone decker into `.decker/` before anything else: + +```bash +git clone https://github.com/flashbots/decker.git .decker +``` + From the repository root: ```bash just devnet ``` -This builds the profiling `op-rbuilder` binary (`make op-rbuilder-profiling FEATURES=telemetry,loki` — the binary `decker.ts` points `builderBinary` at) and then runs `decker start`, which clones the pinned decker source into `.decker/` on first use and brings the whole stack up in the foreground (Ctrl+C tears it down). +This builds the profiling `op-rbuilder` binary and runs `decker start` against the `.decker/` clone above, bringing the whole stack up. -Equivalent, one step at a time: +Or alternatively: ```bash make op-rbuilder-profiling FEATURES=telemetry,loki -decker pull # clone the pinned decker source into .decker/ (skips an existing clone) -decker start # foreground, Ctrl+C to stop; use `decker up` + `decker down` to detach instead +decker start # fg, Ctrl+C to stop; use `decker up` + `decker down` to detach ``` -See `decker.ts` for the exact recipe options in use (fork, L2 block time, flashblocks tuning, chain-monitor); `decker up`/`start` print every pod/process and its port right before the run starts. +See `decker.ts` for the exact recipe options (fork, L2 block time, flashblocks config, chain-monitor, observability). ## Generate Local Traffic @@ -48,37 +53,46 @@ decker test --rpc 9645 (`--rpc` accepts a bare port as shorthand for `http://localhost:`, or a full URL.) -> The obvious legacy-style equivalent, `builder-playground test http://localhost:2222 --timeout 30s --retries 10`, was actually silently broken: `test` ignores positional arguments and requires `--rpc`, so that invocation really exercised the command's *default* target (`:8545`), never the builder. See the [Legacy appendix](#legacy-builder-playground-deprecated) for the corrected form. - ## Services | Service | Port | Notes | |---|---|---| -| op-rbuilder (builder) RPC | `9645` | decker's fixed port — **not** builder-playground's old `:2222` (see `decker.ts`) | -| op-rbuilder authrpc | `9651` | rollup-boost's engine-API target | +| op-rbuilder RPC | `9645` | decker's fixed port | +| op-rbuilder authrpc | `9651` | rollup-boost engine-API | | op-rbuilder metrics | `6062` | | | op-rbuilder flashblocks (ws) | `1111` | `ws://localhost:1111` | | chain-monitor | `8087` | `http://localhost:8087/metrics` | +| Prometheus | `9009` | scrapes op-rbuilder + chain-monitor metrics | +| Grafana | `3000` | `http://localhost:3000`, anonymous Admin (no login) | | L1 EL (`el-1`) RPC | `8545` | | -| L2 sequencer EL RPC | `9545` | fallback target rollup-boost uses if the builder is down | +| L2 sequencer EL RPC | `9545` | fallback target rollup-boost | | L1 explorer (Blockscout) | `3002` | `http://localhost:3002` | | L2 explorer (Blockscout) | `3003` | `http://localhost:3003` | | Pod logs (Dozzle) | `18080` | podman only; under `docker-compose` use `decker attach` for logs | +## Observability + +Provisioned by `local-testing/decker/obs.ts`: + +- **Prometheus** (`:9009`) scrapes `op-rbuilder`'s metrics. +- **Grafana** (`:3000`) provisioned with op-rbuilder local dashboard. + +**Deferred**: Loki, Tempo and Alloy. decker has no container prototypes for them yet. + ## Builder-Only Restart Loop -To iterate on `op-rbuilder` itself without tearing down the rest of the devnet: stop decker's own builder process (it holds `:9645`/`:9651`), rebuild, then run the binary directly from the repository root — it auto-detects the running devnet's artifacts: +To iterate on `op-rbuilder` itself without tearing down the rest of the devnet: stop decker's own builder process (it holds `:9645`/`:9651`), rebuild, then run the binary directly from the repository root; it auto-detects the running devnet's artifacts: ```bash make op-rbuilder-profiling FEATURES=telemetry,loki ./target/profiling/op-rbuilder node --builder.playground ``` -A bare `--builder.playground` (no path) resolves `./.decker/runtime/artifacts` first, falling back to the legacy `$HOME/.local/state/builder-playground/devnet` if that doesn't exist. It fixes `authrpc` at `:9651` (so rollup-boost keeps working unchanged) and trusted-peers the sequencer EL directly; its own RPC prefers `:2222` but falls back to a random free port if that's taken — check the startup logs for the actual one. This auto-detection requires the decker-aware `--builder.playground` rewrite (`feat/decker-devnet-detection`); on an older `op-rbuilder` build, pass the directory explicitly instead: `--builder.playground .decker/runtime/artifacts`. +A bare `--builder.playground` resolves `./.decker/runtime/artifacts` first, falling back to the legacy `$HOME/.local/state/builder-playground/devnet` if that doesn't exist. ## Karst -The devnet defaults to the `karst` L2 fork, which requires the `op-reth` v2.3.x dependency line — `main` is already on it, but older branches will not produce valid Karst blocks. Testing an older branch: override the fork via `decker.ts`'s `options.l2Fork` (e.g. `"jovian"` or `"isthmus"`), which also switches the sequencer EL back to `op-geth`. +The devnet defaults to the `karst` L2 fork, which requires `op-reth` v2.3.x dependency. ## Legacy: builder-playground (deprecated) diff --git a/local-testing/decker/obs.ts b/local-testing/decker/obs.ts new file mode 100644 index 000000000..0065e34c3 --- /dev/null +++ b/local-testing/decker/obs.ts @@ -0,0 +1,83 @@ +// Observability overlay for decker.ts: Prometheus scraping op-rbuilder (+ +// chain-monitor, when enabled) and Grafana provisioning. +// +// Loki/Tempo/Alloy are deferred and will be wired as follow up. +import type { ContainerDef, ContainerResult, Ctx, Pod, Prototype, Recipe } from "../../.decker/utils/types.ts"; + +// Resolved against this module's file. +const GRAFANA_DIR = new URL("../grafana/", import.meta.url); + +function readLocal(relPath: string): string { + return Deno.readTextFileSync(new URL(relPath, GRAFANA_DIR)); +} + +// Read once at module load instead of per-render. +const DASHBOARD_PROVIDER_YML = readLocal("dashboards.yaml"); +const OP_RBUILDER_DASHBOARD_JSON = readLocal("dashboards/op-rbuilder.json"); + +const GRAFANA_HTTP_PORT = 3000; + +function buildGrafanaContainer(def: ContainerDef, ctx: Ctx): ContainerResult { + const prometheusRef = def.refs?.prometheus; + if (!prometheusRef) throw new Error(`grafana ${def.name}: missing refs.prometheus`); + + const datasourcesYml = `apiVersion: 1 +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: ${ctx.url(prometheusRef, "http")} + uid: prometheus + isDefault: true + editable: false + jsonData: + timeInterval: "5s" + httpMethod: POST +`; + + return { + container: { + image: "docker.io/grafana/grafana:13.0.1", + env: { + // Passwordless dev access + GF_AUTH_ANONYMOUS_ENABLED: "true", + GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin", + GF_AUTH_DISABLE_LOGIN_FORM: "true", + GF_AUTH_BASIC_ENABLED: "false", + GF_USERS_DEFAULT_THEME: "dark", + GF_LOG_LEVEL: "warn", + }, + ports: { http: GRAFANA_HTTP_PORT }, + }, + configs: [ + { filename: "datasources.yml", content: datasourcesYml, mountPath: "/etc/grafana/provisioning/datasources/datasources.yml" }, + { filename: "dashboards.yml", content: DASHBOARD_PROVIDER_YML, mountPath: "/etc/grafana/provisioning/dashboards/dashboards.yml" }, + { filename: "op-rbuilder.json", content: OP_RBUILDER_DASHBOARD_JSON, mountPath: "/var/lib/grafana/dashboards/op-rbuilder.json" }, + ], + }; +} + +const grafanaPrototype: Prototype = { + ports: { http: GRAFANA_HTTP_PORT }, + buildContainer: buildGrafanaContainer, + webui: { label: "Grafana (op-rbuilder dashboard)" }, +}; + +// Appended to the opstack recipe `pods`. +export function obsPods(base: Recipe): Pod[] { + const hasChainMonitor = base.pods.some((p) => p.name === "chain-monitor"); + const scrape = [ + { job: "op-rbuilder", ref: "op-rbuilder", port: "metrics" }, + ...(hasChainMonitor ? [{ job: "chain-monitor", ref: "chain-monitor", port: "http", path: "/metrics" }] : []), + ]; + return [ + { + name: "prometheus", + containers: [{ name: "prometheus", prototype: "prometheus", config: { scrape } }], + }, + { + name: "grafana", + containers: [{ name: "grafana", prototype: grafanaPrototype, refs: { prometheus: "prometheus" } }], + }, + ]; +}