From c1f2f04a173858227f9d5220a2436b2879fc8c46 Mon Sep 17 00:00:00 2001 From: julio4 <30329843+julio4@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:30:22 +0800 Subject: [PATCH] feat(opstack): add flashblocks streaming support to op-rbuilder --- containers/op-rbuilder.ts | 38 +++++++++++++++++++++++++++++++++++++- decker.example.ts | 3 +++ recipes/opstack.ts | 20 +++++++++++++++++++- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/containers/op-rbuilder.ts b/containers/op-rbuilder.ts index b811a70..ed1cb7d 100644 --- a/containers/op-rbuilder.ts +++ b/containers/op-rbuilder.ts @@ -29,8 +29,40 @@ function resolvedPorts(def: ContainerDef | ProcessDef): Ports { return { ...ports, ...((def.config?.ports as Ports | undefined) ?? {}) }; } +type FlashblocksConfig = { + blockTimeMs?: number; // --flashblocks.block-time + endBufferMs?: number; // --flashblocks.end-buffer-ms + sendOffsetMs?: number; // --flashblocks.send-offset-ms (negative sends early) + continuousBuild?: boolean; // --flashblocks.continuous-build +}; + +const FLASHBLOCKS_DEFAULTS: Required = { + blockTimeMs: 200, + endBufferMs: 75, + sendOffsetMs: -30, + continuousBuild: true, +}; + +// op-rbuilder default for --flashblocks.port +const FLASHBLOCKS_WS_PORT = 1111; + +function flashblocksArgs(opt: boolean | FlashblocksConfig | undefined, wsPort: number): string[] { + if (!opt) return []; + const cfg = { ...FLASHBLOCKS_DEFAULTS, ...(typeof opt === "object" ? opt : {}) }; + return [ + "--flashblocks.addr", "0.0.0.0", + "--flashblocks.port", String(wsPort), + "--flashblocks.block-time", String(cfg.blockTimeMs), + "--flashblocks.end-buffer-ms", String(cfg.endBufferMs), + "--flashblocks.send-offset-ms", String(cfg.sendOffsetMs), + ...(cfg.continuousBuild ? ["--flashblocks.continuous-build"] : []), + ]; +} + export function buildContainer(def: ContainerDef, _ctx: Ctx): ContainerResult { const ps = resolvedPorts(def); + const flashblocks = def.config?.flashblocks as boolean | FlashblocksConfig | undefined; + const fbPort = portNum(ps.flashblocks ?? FLASHBLOCKS_WS_PORT); return { container: { image: "ghcr.io/flashbots/op-rbuilder:v0.4.9", @@ -55,8 +87,9 @@ export function buildContainer(def: ContainerDef, _ctx: Ctx): ContainerResult { "--bootnodes", `enode://${BOOTNODE_ID}@bootnode:30303`, "--nat", "none", "--rollup.discovery.v4", + ...flashblocksArgs(flashblocks, fbPort), ], - ports: ps, + ports: flashblocks ? { ...ps, flashblocks: ps.flashblocks ?? FLASHBLOCKS_WS_PORT } : ps, volumeMounts: [ { name: "artifacts", mountPath: "/artifacts", readOnly: true }, { name: "data", mountPath: "/data_op_rbuilder" }, @@ -94,6 +127,8 @@ export function buildProcess(def: ProcessDef, ctx: HostCtx): ProcessResult { const dataDir = ctx.dataPath(def.name, "data"); // The sequencer EL only publishes its p2p port to the host in this mode. const elP2pPort = new URL(ctx.url(l2, "p2p")).port; + const flashblocks = def.config?.flashblocks as boolean | FlashblocksConfig | undefined; + const fbPort = portNum(ps.flashblocks ?? FLASHBLOCKS_WS_PORT); return { process: { @@ -119,6 +154,7 @@ export function buildProcess(def: ProcessDef, ctx: HostCtx): ProcessResult { // don't rely on discovery at all: dial EL directly by its fixed enode "--trusted-peers", `enode://${EL_P2P_ID}@127.0.0.1:${elP2pPort}`, "--disable-discovery", + ...flashblocksArgs(flashblocks, fbPort), ], }, binaryBuild: def.binary ? undefined : BUILD, diff --git a/decker.example.ts b/decker.example.ts index 96b53ef..d3fb215 100644 --- a/decker.example.ts +++ b/decker.example.ts @@ -53,6 +53,9 @@ export const project: DeckerProject = { // externalBuilder: "op-rbuilder", // // Run op-rbuilder as a host process instead of the pinned container // builderBinary: "../op-rbuilder/target/profiling/op-rbuilder", + // // Enable op-rbuilder's Flashblocks websocket stream (ws://:1111); + // // pass an object instead of `true` to override individual fields. + // flashblocks: true, // }, // // Add or override the prototypes the recipe resolves by name — tweak a diff --git a/recipes/opstack.ts b/recipes/opstack.ts index 1b790f5..b86579f 100644 --- a/recipes/opstack.ts +++ b/recipes/opstack.ts @@ -25,6 +25,13 @@ export type OpstackOptions = { // op-rbuilder can trusted-peer-dial it directly instead of going through the // container-mode bootnode, which the host can't reach. (default off) builderBinary?: string | boolean; + // Enable or tune op-rbuilder flashblocks websocket streaming + flashblocks?: boolean | string | { + blockTimeMs?: number; + endBufferMs?: number; + sendOffsetMs?: number; + continuousBuild?: boolean; + }; }; export function recipe(o: OpstackOptions = {}): Recipe { @@ -44,6 +51,16 @@ export function recipe(o: OpstackOptions = {}): Recipe { throw new Error(`opstack: builderBinary requires externalBuilder="op-rbuilder" (got ${externalBuilder || "false"})`); } + // Same true/"true" vs. false/"false"/undefined as builderBinary + const flashblocksOpt = o.flashblocks; + const flashblocksEnabled = flashblocksOpt !== undefined && flashblocksOpt !== false && flashblocksOpt !== "false"; + const flashblocks = flashblocksEnabled + ? (flashblocksOpt === true || flashblocksOpt === "true" ? true : flashblocksOpt) + : undefined; + // Same value forwarded to both possible op-rbuilder placements below, so + // container mode and host-process mode can't tune Flashblocks differently. + const builderConfig = flashblocks !== undefined ? { flashblocks } : undefined; + // L2 execution client selection. Karst (OP Upgrade 19) ends op-geth support, so // from Karst on the L2 EL is op-reth and op-node needs a newer release. Forks // up to jovian keep the op-geth client set they were verified with. @@ -70,7 +87,7 @@ export function recipe(o: OpstackOptions = {}): Recipe { // peering; a host-process op-rbuilder dials the EL directly (see above). ...(builderHostProcess ? [] : [ { name: "bootnode", containers: [{ name: "bootnode", prototype: "bootnode" }] }, - { name: "op-rbuilder", containers: [{ name: "op-rbuilder", prototype: "op-rbuilder" }] }, + { name: "op-rbuilder", containers: [{ name: "op-rbuilder", prototype: "op-rbuilder", config: builderConfig }] }, ]), { name: "rollup-boost", @@ -86,6 +103,7 @@ export function recipe(o: OpstackOptions = {}): Recipe { name: "op-rbuilder", prototype: "op-rbuilder", refs: { l2: l2El }, + config: builderConfig, ...(builderBinaryPath ? { binary: builderBinaryPath } : {}), }, ]