Repository navigation
Conversation
c78ed9f to
319b3ab
Compare
319b3ab to
3e7dcd9
Compare
6cf6a1f to
eb6ff32
Compare
eb6ff32 to
ab13906
Compare
| // Sign represents the interface for signing things via eth_sign. | ||
| func (s *SignerClient) Sign(ctx context.Context, address common.Address, data []byte) ([]byte, error) { | ||
| var result hexutil.Bytes | ||
| if err := s.client.CallContext(ctx, &result, "eth_sign", address, data); err != nil { |
There was a problem hiding this comment.
This eth_sign call can't work against op-signer, and I don't think op-signer should be extended to make it work either.
op-signer doesn't serve eth_sign. Its server registers only two namespaces — eth (eth_signTransaction) and opsigner (signBlockPayload, signBlockPayloadV2): https://github.com/ethereum-optimism/infra/blob/main/op-signer/service/service.go#L73-L82. There is no arbitrary-data signing method. And this SignerClient can only talk to op-signer in the first place: NewSignerClient dials with op-signer's mutual-TLS and then handshakes with a health_status ping before returning, so pointing it at a plain geth or another HSM front-end fails at construction. So the call here errors method-not-found at runtime against a real op-signer.
The reason op-signer has no such method is deliberate, and it's why I'd argue against adding one. An HSM-backed signer must never sign raw bytes the caller hands it. If it did, a compromised batcher could pass a 32-byte value that is really the sighash of an L1 transaction spending the funded key, or a block payload for equivocation, and the HSM would sign it. That's why every op-signer method reconstructs the thing being signed server-side from typed arguments and binds a domain tag and chain id into the hash — see BlockPayloadArgs (domain, chainId, payloadBytes) and Message().ToSigningHash(). The client never sends a bare hash. Adding an eth_sign that signs any digest would remove exactly that protection for a key that also signs L1 transactions.
There's a second, backend-independent problem: eth_sign applies the EIP-191 prefix ("\x19Ethereum Signed Message:\n32" || hash), but the verify side recovers over the raw digest (crypto.SigToPub(batchHash, sig) in op-node/rollup/derive/espresso_batch.go):
optimism/op-node/rollup/derive/espresso_batch.go
Lines 105 to 107 in 014f88d
If remote HSM signing of Espresso batches is a requirement, the right shape is a purpose-built op-signer method modeled on signBlockPayload: the client sends typed args (a fixed domain tag, the L2 chain id / namespace, and the batch commitment), op-signer reconstructs the domain-separated digest and signs it with the HSM key, and op-node verifies the same digest. That also resolves the separate domain-separation gap (the batch digest is currently a bare keccak256(rlp(batch)) with no namespace binding). It is a change in the op-signer repo, so it can't land from this PR alone — until it exists, only the local private-key ChainSigner actually works. I'd suggest dropping this eth_sign helper and the clientSigner branch here rather than shipping a path that can't sign or verify.
There was a problem hiding this comment.
Jean is going to look and respond here as he did this work.
There was a problem hiding this comment.
Thanks for the thorough writeup. The analysis is right about op-signer, and the fact that it reads as targeting op-signer at all is a documentation gap on our side, so let me fill in the missing context first.
This Sign call doesn't talk to op-signer. The --signer.endpoint it's deployed against is espresso-kms-signer (https://github.com/EspressoSystems/espresso-kms-signer), a small AWS KMS signing sidecar we built specifically to speak the signer protocol this batcher uses: health_status, eth_signTransaction, and eth_sign (https://github.com/EspressoSystems/espresso-kms-signer/blob/758e4d0/src/rpc.rs#L26-L46). It runs as an ECS sidecar next to op-batcher-tee and has done full batch-posting cycles on our kms test devnets (batches accepted on L1 and on HotShot). On the client side, NewSignerClient isn't actually op-signer-specific: mTLS only kicks in when tlsConfig.Enabled is set (https://github.com/celo-org/optimism/blob/c828d61cd9/op-service/signer/client.go#L31-L65) (plain HTTP otherwise), and the handshake is just a health_status call into a Go string, which the sidecar answers. That genericness is what let us add KMS signing with no batcher code changes.
On EIP-191: agreed that a standard eth_sign would break recovery, but the sidecar's eth_sign is deliberately non-standard; it signs the raw 32-byte digest with no message prefix and returns r||s||v with v ∈ {0,1}, (i.e. go-ethereum's crypto). Sign convention, which makes it semantically identical to the privateKeySigner path in this PR (crypto.Sign(hash, privKey) (https://github.com/celo-org/optimism/blob/c828d61cd9/op-service/crypto/espresso.go#L161)). Both verify the same way under SigToPub. And it's pinned rather than hoped-for: the sidecar's fixture generator (https://github.com/EspressoSystems/espresso-kms-signer/blob/758e4d0/tests/fixtures/gen/main.go#L158-L178) is a Go program that imports op-service/signer itself and records the exact JSON-RPC params bytes geth's RPC client marshals (including the base64 []byte encoding), which CI replays against the production handler; there's also a localstack test that runs the real KMS path end-to-end and asserts the recovered address. That said, you're completely right that calling a method eth_sign while breaking eth_sign semantics is asking for exactly this confusion. I'd be happy to rename it in a follow-up, and we'll add a doc comment on SignerClient.Sign pointing at the sidecar and its semantics either way. (Small note: the espresso_batch.go verify code you linked has since moved into espresso-streamers digest recovery (https://github.com/EspressoSystems/espresso-streamers/blob/1884a718fbf7/op/derivation/espresso_batch.go#L102-L104).)
On "an HSM signer should never sign raw caller-supplied bytes", no pushback on the principle, and I'd rather be precise about what it costs us here. A raw-digest endpoint does mean the sidecar's eth_signTransaction guards (chainId, from, to-allowlist) only protect against a buggy caller, not a malicious one; anyone who can reach the endpoint can get a signature over an arbitrary digest, including an L1 tx sighash. What bounds the damage is that this key was never a batch-eligibility authority: batch acceptance in TEE mode requires an EIP-712 commitment signature from the ephemeral key generated inside the Nitro enclave and verified on-chain via the TEE verifier; the sidecar never touches that key. So a compromised sidecar (or its host) can spend the batcher address's gas funds and inject noise into our own HotShot namespace, but it can't make derivation accept a batch the enclave didn't produce. That's our documented trust model: the sidecar is trusted for availability, not integrity.
You're also right about the missing domain separation, and that one stands on its own: the namespace lives in the transaction envelope outside the signed bytes, so the signature binds neither chain nor namespace, under the local key just as much as the remote one. Fixing it means changing the digest every verifier reconstructs, so it's a coordinated change across the batcher and espresso-streamers with a migration story for payloads already in the stream, and we'll file it as a tracked issue rather than fold it into this PR.
Where I'd push back is on the remedy. A typed, domain-separated signing method modeled on signBlockPayload is the right end state, but it belongs in espresso-kms-signer (op-signer isn't in this deployment), and it should land together with the digest-scheme change since both alter what verifiers recover over. Dropping clientSigner in the meantime wouldn't remove the capability this comment worries about; it would move the key from KMS hardware into batcher memory, which is a strict downgrade for the same attack surface. So my proposal: keep clientSigner/Sign as-is here, add a comment linking the sidecar and its non-standard semantics, and file two linked follow-ups, one for the typed domain-separated method (including the eth_sign rename/retirement) and one for the digest-scheme migration (which I will discuss with the team). Happy to talk through the typed-method design if you have opinions on the shape!
There was a problem hiding this comment.
Hi @jjeangal, I hadn't realised this was talking to the kms signer.
So yes, agreed on the followups 👍
05288e9 to
78e33c9
Compare
…ero LocalSafeL2 cannot floor the queue at genesis and re-enqueue derived history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ocalSafeL2 cannot disable the stale-batch re-anchor floor and republish derived blocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent sync-status failure cannot empty the channel manager while leaving the streamer at its old cursor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
isBatcherActive ran its CodeAt existence check on the caller's context, which is the publishing loop's lifetime killCtx, while every other RPC in the publish gate (the BatchAuthenticator reads below it and l1Tip in the fallback-auth gate) is bounded by NetworkTimeout. A stalled L1 RPC could therefore wedge the publishing loop on every publish tick and block graceful shutdown until the stop context force-cancels killCtx. Give CodeAt its own NetworkTimeout window, matching the reads below. Addresses the Codex P2 review comment on PR #459. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CLI value was never read; all runtime paths use RollupConfig.BatchAuthenticatorAddress as the single source of truth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A zero or negative --espresso.poll-interval previously passed Check() and panicked later in time.NewTicker inside espressoBatchLoadingLoop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tdown registerBatcher runs while StartBatchSubmitting holds l.mutex, and StopBatchSubmitting needs that mutex to cancel killCtx, so an unbounded receipt wait (txmgr.send-timeout defaults to 0) wedged graceful shutdown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The between-attempts backoff in espressoVerifyTransactionWorker was a plain time.Sleep, so a worker mid-backoff stalled StopBatchSubmitting's wg.Wait() for the full configured retry delay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…p-l1-url The Espresso L1 URL was parsed and validated but never dialed: light-client calls always went through the batcher's L1 client, so pointing the flag at a different RPC was silently ignored. Now a dedicated client is dialed when the URL differs from --l1-eth-rpc (reusing the batcher's client when it matches), and closed in Stop. --espresso.rollup-l1-url only ever logged a warning, so it is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…2 chain ID The CLI value was parsed and validated but never read: both runtime sites (transaction publishing and streamer construction) already derive the namespace from RollupConfig.L2ChainID, which the proof side pins too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror the zero-field guards in computeSyncActions and nextBlockRange: a transiently zeroed sync status during clearState would re-anchor the streamer at genesis and re-serve derived history. Retry the clear instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ght-l2 The comments promised a RollupConfig.EspressoOriginBatchPos() fallback that never existed; zero means no anchor floor (anchor at the current local-safe head). No default can be derived from the rollup config since espresso_time is a timestamp whose L2 height is not statically known, so handoffs from the fallback batcher must set the flag explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two comments point at a flag that was never registered. The note in espresso/cli.go claims op-batcher/flags/flags.go registers --espresso.fallback-auth-lead-time, and initEspresso's doc claims the fallback batcher reads a FallbackAuthLeadTime field on BatcherConfig. Neither the flag nor the field exists on any branch; both comments date from the TEE flag import and describe a knob the fallback batcher landed without.
The test's per-endpoint call counters, the two restarted-server flags and the recorded shutdown error are all written by httptest handler goroutines (or by throttlingLoop, for closeApp) and read directly by the test goroutine, so the package could not run under -race at all: five reports on every run. Make each of them atomic. The slices.Contains check over the counters becomes a small anyUncalled helper, since atomic.Int64 elements can't be compared by value.
* espresso: delete dead supporting code Remove code with no callers: espresso/ethclient.go (duplicates batcherL1Adapter; FetchEspressoBatcherAddress belongs to the caff node in another repo), opcrypto.Verify and its test, and the AllowEmptyAttestationService escape hatch, which nothing sets, so Check() now requires the attestation service URL unconditionally. Also drop a doc reference to DebouncingHandler, which does not exist. * espresso: gofmt cli.go after removing the config field
espresso: replace hand-copied bindings with generated op-service/bindings (#513) * Delete espresso/bindings, an abigen copy that had drifted from the contracts. * Add op-service/bindings: ABI-only bindings for BatchAuthenticator and SystemConfig, one package per contract, generated from the contracts-bedrock forge artifacts by gen.sh and kept current by the check-bindings CI job. * Add CI job to detect BatchAuthenticator + SystemConfig bindings drifts Closes #504.
* shrink txmgr seam, delete SignerFactoryFromConfig and test file * Revert deletion SignerFactoryFromConfig and test file SignerFactoryFromConfig had no callers because NewConfig had been moved onto the fork's ChainSignerFactoryFromConfig to populate Config.ChainSigner. Not only don't we want to delete upstream functions, we also want to remove the modification of the upstream NewConfig in a later commit. * op-service/txmgr: go back to upstream's SignerFactoryFromConfig NewConfig was switched onto the fork's ChainSignerFactoryFromConfig so that Config.ChainSigner could be populated for SimpleTxManager.Sign. Both of those are gone now, and txmgr only ever uses SignTransaction, which upstream's SignerFactory already provides. Switching back makes op-service/txmgr free of Espresso entirely, and gives SignerFactoryFromConfig its caller back - it only looked like a redundant copy of ChainSignerFactoryFromConfig because txmgr had been moved off it. ChainSignerFactoryFromConfig stays: the batcher needs Sign over an arbitrary hash for the Espresso transaction envelope, which a SignerFn cannot provide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * op-batcher: guard the Espresso ChainSigner against signing-config drift initChainSigner derives its own signer from cfg.TxMgrConfig, mirroring the HD-path resolution in txmgr.NewConfig. Nothing checked that the two agreed. If they ever diverged, Espresso batches would be signed by a key that is not the registered batcher, the streamer would drop all of them, and L1 submission would carry on. Check that both derivations resolve to the same address and fail startup if not. Move the call from initTxManager to initEspresso, next to initKeyPair, so initTxManager carries no Espresso logic and both signing identities are set up in one place. Also note on ChainSignerFactoryFromConfig that upstream's SignerFactoryFromConfig is not redundant with it: SignerFactory yields a SignerFn, which cannot sign an arbitrary hash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Maximilian Langenfeld <15726643+ezdac@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… once at startup (#523) op-batcher: use one BatchAuthenticator reader (#523) Replace the scattered BatchAuthenticator calls with a single batchAuthenticatorReader, bound once at startup and shared by registerBatcher, resolveTEEVerifierAddress and the publish gate. - Read the fallback batcher from the SystemConfig the BatchAuthenticator itself names, cached after the first read (closes #503). - Bound every read by NetworkTimeout. Rely on the bindings' ErrNoCode for a contract that is not deployed yet; registerBatcher keeps an explicit CodeAt before sending. - Reject a zero SystemConfig or TEE verifier address. - Refuse to start an Espresso batcher with no BatchAuthenticator. - Throttle the gate's skip and error warnings, and reset the wrong-key warning when the batcher's mode goes inactive. - Test the reader, both gates (results, call counts, warnings, throttling) and the callers' error paths.
…527) * publish op-batcher enclave images from this repo * op-batcher: redact credential flags and URL secrets in enclave argument log * op-batcher: proxy --throttle.additional-endpoints through socat in enclave entrypoint * op-batcher: redact URLs in enclave entrypoint processing logs * op-batcher: reject https for socat-proxied internal URLs in enclave entrypoint * op-service/signer: honor proxy environment variables in TLS signer client * only on dispatch + pin enclaver to prod v0.5.0 * harden enclave arg delivery and CI image tagging --------- Co-authored-by: Jean Gal <jgatbkk@gmail.com>
) * publish op-batcher enclave images from this repo * op-batcher: redact credential flags and URL secrets in enclave argument log * op-batcher: proxy --throttle.additional-endpoints through socat in enclave entrypoint * op-batcher: redact URLs in enclave entrypoint processing logs * op-batcher: reject https for socat-proxied internal URLs in enclave entrypoint * op-service/signer: honor proxy environment variables in TLS signer client * only on dispatch + pin enclaver to prod v0.5.0 * op-batcher: expose enclave Prometheus metrics via enclaver ingress Add a :7300 ingress port to the enclaver manifest and launch op-batcher with metrics enabled on 0.0.0.0:7300 so the enclaver ingress can bridge Prometheus metrics out of the Nitro enclave to the parent. The parent-side bridge (tcp<->vsock) lives in EspressoSystems/tee-image-builder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * op-batcher: force enclave metrics onto the fixed 7300 ingress Review on #536: the previous "skip if caller set --metrics.enabled" guard let a caller-supplied --metrics.port/addr bind somewhere the enclaver ingress (fixed at 7300) doesn't bridge, so scrapes failed silently. Drop the guard and append enabled/addr/port last so they win via urfave/cli's last-value-wins, guaranteeing the batcher always serves on 0.0.0.0:7300. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * trim comment --------- Co-authored-by: Keyao Shen <shenkeyao@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b79ffbd to
158237f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 158237f8bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| type=ref,event=branch | ||
| type=ref,event=pr | ||
| type=sha,prefix={{branch}}-,enable=${{ github.event_name == 'push' }} |
There was a problem hiding this comment.
Generate an image tag for tag-ref dispatches
When this workflow is manually dispatched with a tag ref—which GitHub explicitly supports for workflow_dispatch—neither the branch nor PR rule generates a Docker tag, while the SHA rule is disabled because github.event_name is workflow_dispatch, not push. The build step still enables push, so it has no named GHCR image target to publish. Add type=ref,event=tag or enable the SHA tag for manual dispatches. GitHub workflow dispatch reference metadata-action ref semantics
Useful? React with 👍 / 👎.
* op-service: add ChainSigner interface and eth_sign helper Bring in op-service/crypto/espresso.go (ChainSigner interface unifying SignTransaction and arbitrary-data signing) and op-service/signer/espresso.go (SignerClient.Sign wrapper around eth_sign). Required by the Espresso batcher to sign batch-authentication payloads with either a remote signer or a local private key, in addition to the existing transaction-signing path. Co-authored-by: OpenCode <noreply@opencode.ai> * op-node: add EspressoBatch type and converters Introduce op-node/rollup/derive/espresso_batch.go defining EspressoBatch (a SingularBatch with block number, L1 info deposit transaction, and signer address attached), along with BlockToEspressoBatch and the unmarshaler used by the streamer. Also pulls in the github.com/EspressoSystems/espresso-network/sdks/go dependency, which provides the Espresso transaction and namespace types. Consumed by the Espresso batcher (next commits) to convert L2 blocks into batches submitted to Espresso, and to round-trip those batches back through the streamer. Co-authored-by: OpenCode <noreply@opencode.ai> * espresso: add TEE batcher CLI flags, streamer interface, and L1 adapter Bring in the parts of the espresso/ shared package that are TEE-only: - espresso/cli.go: full CLI flag set for --espresso.enabled, query service URLs, light-client/L1 endpoints, batch-authenticator address, receipt-verification tuning, namespace/origin-height parameters used to construct the espresso-streamer. (The --espresso.fallback-auth-lead-time flag lives in op-batcher/flags/flags.go and was added by the fallback PR.) - espresso/interface.go: EspressoStreamer[B] interface that wraps github.com/EspressoSystems/espresso-streamers/op.BatchStreamer. - espresso/ethclient.go: AdaptL1BlockRefClient adapter (used by cli.go to construct the streamer) and FetchEspressoBatcherAddress helper. Also adds the EspressoSystems/espresso-network/sdks/go and EspressoSystems/espresso-streamers Go module dependencies. The regenerated BatchAuthenticator bindings already live in the fallback PR's espresso/bindings/. Co-authored-by: OpenCode <noreply@opencode.ai> * op-batcher: add Nitro NSM attestation helper Bring in op-batcher/enclave/attestation.go: a thin wrapper around the hf/nsm library that obtains an AWS Nitro NSM attestation document over a given public key. Used by the Espresso batcher (next commit) to attach a TEE attestation to its registration with the BatchAuthenticator. Adds the github.com/hf/nsm dependency. Builds on all platforms; NSM device access is only attempted at runtime when invoked from inside a Nitro enclave. Co-authored-by: OpenCode <noreply@opencode.ai> * op-batcher: integrate TEE batcher and Espresso submission loop Add the Espresso TEE batcher write-path on top of the fallback batcher: - op-batcher/batcher/espresso.go: Espresso submission loop (peeks the channel manager, converts each L2 block to an EspressoBatch, submits it to Espresso, waits for inclusion, and then posts the batch txs to L1 with TEE-attested BatchAuthenticator.authenticateBatchInfo calls). - op-batcher/batcher/espresso_service.go: EspressoBatcherConfig, initEspresso (Espresso client / light-client construction, optional TEE attestation gathering), and the initChainSigner hook that wraps the txmgr into a opcrypto.ChainSigner. - op-batcher/batcher/espresso_helpers_test.go and espresso_transaction_submitter_test.go: unit tests for the helpers and the TEE transaction submitter. Extends the existing fallback wiring: - op-batcher/batcher/espresso_driver.go: adds EspressoDriverSetup fields (Client/LightClient/ChainSigner/SequencerAddress/Attestation), batcherL1Adapter, setupEspressoStreamer, startEspressoLoops, resetEspressoStreamer; extends dispatchAuthenticatedSendTx with the TEE branch (always authenticates when Espresso.Enabled). - op-batcher/batcher/espresso_active.go: adds isBatcherActive (queries BatchAuthenticator.activeIsEspresso to gate publishing against this batcher's role). - op-batcher/batcher/driver.go: extends DriverSetup with the Espresso EspressoDriverSetup field; adds espressoSubmitter / espressoStreamer / teeVerifierAddress / degradedLog fields on BatchSubmitter; calls setupEspressoStreamer in NewBatchSubmitter; branches StartBatchSubmitting on Espresso.Enabled to call startEspressoLoops; calls resetEspressoStreamer in clearState. - op-batcher/batcher/service.go: BatcherConfig.Espresso field; EspressoClient / EspressoLightClient / ChainSigner / Attestation runtime fields; initEspresso / initChainSigner / applyEspressoDriverSetup call-outs. - op-batcher/batcher/config.go: thread Espresso espresso.CLIConfig through CLIConfig. - op-batcher/flags/flags.go: register espresso.CLIFlags (TEE-only flags; the --espresso.fallback-auth-lead-time flag added by the fallback PR continues to live in op-batcher/flags/flags.go). Also adds op-service/log/repeat_state.go (RepeatStateLogger) and its test, used by the Espresso submission loop's tick-driven warnings. A safeTestRecorder helper is inlined into the test to avoid pulling in the unrelated debouncer. Adds the github.com/hf/nitrite dependency (transitively required by hf/nsm for attestation document parsing). Co-authored-by: OpenCode <noreply@opencode.ai> * op-batcher: route TEE auth through ordered tx queue The TEE batcher's Espresso submission path called Txmgr.Send directly for both the authenticateBatchInfo tx and the batch inbox tx, and ran inside authGroup, so it bypassed MaxPendingTransactions, assigned nonces nondeterministically (violating Holocene's in-order L1 inclusion requirement), and never checked whether the auth tx reverted — a reverted authenticateBatchInfo emits no event, so the verifier would silently drop the batch. Submit both txs through the ordered queue.Send path on the publishing-loop goroutine (auth first, batch second) so the auth tx takes the lower nonce and is mined first, and both stay under MaxPendingTransactions. A watcher goroutine (tracked by authGroup) collects both receipts, fails the pair if the auth tx reverted, runs the lookback-window check, and emits a single synthetic receipt. This is the same fix already applied to the fallback path; extract the shared submission + receipt-watching flow into submitAuthenticatedBatch / watchAuthReceipts so both paths reuse it and differ only in how the authenticateBatchInfo calldata is built (TEE-attested signature vs empty signature). Rename fallback_auth.go to espresso_auth.go to reflect the shared scope, and restructure the tests: one suite drives the shared flow directly, plus per-path tests asserting the distinguishing auth calldata (empty sig vs a recoverable EIP-712 signature). Co-authored-by: OpenCode <noreply@opencode.ai> * op-node: add EspressoBatch roundtrip test, fix derive_test vet failure Port TestBatchRoundtrip from the integration branch and fold it into espresso_batch_test.go. It is the only test covering ToEspressoTransaction and the batcher->derivation serialization path; it asserts the decoded batch matches the original and that the recovered signer is the batcher. Also drop the decodedBlock.ExecutionWitness() comparison in TestEspressoBatchConversion: that method does not exist on the op-geth types.Block pinned in the rebase-18 base, so go vet of the derive_test package failed to build. EspressoBatch/ToBlock carries no execution witness. Co-authored-by: OpenCode <noreply@opencode.ai> * verify batches while converting to a block * start throttling loop after espresso/non espresso loops * make use of unsafeBytesUpdated * initChainSigner fallback for non espresso mode * lint and make use of isBatcherActive * request to clear state from BlockLoader * safety check for namespace and nil l1 deposit * remove duplicate files between espresso streamer and upstream * address comments * remove uneeded comments * update unsafe bytes every 100 blocks. matching non espresso path * always set the prev sync status * log at warn level if batcher isnt active * check the batcher address before publishing to avoid reverts * copy espresso.go over from our fork * proper version of streamer * address comment, remove uneeded methods and compile time assertion * use sync status interface for streamer * empty commit to trigger CI * point streamer to v2 (espresso-streamers PR 36) and adapt the batcher Addresses the stall found in the PR 459 review of peekNextBatch: the old streamer could discard a good batch when the channel manager tip and the streamer position described different blocks, stalling it for good. The v2 streamer owns the tip tracking: position and tip hash move together, repositioning is explicit via SetBatchPosition, and Reset / SetProperHead / Refresh / Update are gone. - pin espresso-streamers to 22c396aa (PR 36, op streamer v2) - construct the streamer in StartBatchSubmitting (needs a context and a synced L2 node to resolve its anchor), anchor it at the safe L2 head, and run it from its own poll loops via Start/Stop - drop peekNextBatch and the cachedSyncStatus provider; the loading loop now just Peeks batches extending the tracked tip and advances after the channel manager accepts them - re-anchor instead of skipping on ToBlock/AddL2Block failures - drop the dead BatchStreamerFromCLIConfig (no callers, v1-only API) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Check resolveTEEVerifierAddress first so that the streamer is not left running after an error. * Use LocalSafeL2 instead of SafeL2 so that Channel Manager and Streamer reset on the same heads. * Roll back running state on Espresso setup failure so that a failed start does not block later start attempts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Anchor the streamer at the local-safe head during construction so that startup does not depend on the L2 endpoint serving the historical origin height. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Floor every streamer anchor and sync comparison at the caffeination point so that the Espresso batcher never treats pre-activation blocks as its own. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Bound every raw RPC read on the Espresso startup path with the network timeout so that a stalled endpoint cannot hang StartBatchSubmitting while it holds the start mutex. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Re-anchor the streamer instead of queueing batches at or below the local-safe head so that already-derived blocks are not resubmitted after a restart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Gate startup on the caffeination point being local-safe instead of anchoring on a cached unsafe floor block, whose reorg could permanently wedge fork selection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Retry on zeroed sync-status fields in nextBlockRange so a transient zero LocalSafeL2 cannot floor the queue at genesis and re-enqueue derived history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Skip the drain tick when the sync status is out of sync so a zeroed LocalSafeL2 cannot disable the stale-batch re-anchor floor and republish derived blocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fetch the streamer re-anchor target before clearing state so a transient sync-status failure cannot empty the channel manager while leaving the streamer at its old cursor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: bound the active-check CodeAt call with NetworkTimeout isBatcherActive ran its CodeAt existence check on the caller's context, which is the publishing loop's lifetime killCtx, while every other RPC in the publish gate (the BatchAuthenticator reads below it and l1Tip in the fallback-auth gate) is bounded by NetworkTimeout. A stalled L1 RPC could therefore wedge the publishing loop on every publish tick and block graceful shutdown until the stop context force-cancels killCtx. Give CodeAt its own NetworkTimeout window, matching the reads below. Addresses the Codex P2 review comment on PR #459. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Empty commit to trigger CI * op-batcher: remove dead --espresso.batch-authenticator-addr flag The CLI value was never read; all runtime paths use RollupConfig.BatchAuthenticatorAddress as the single source of truth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * espresso: reject non-positive poll intervals in config validation A zero or negative --espresso.poll-interval previously passed Check() and panicked later in time.NewTicker inside espressoBatchLoadingLoop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: bound the registration send so startup can't deadlock shutdown registerBatcher runs while StartBatchSubmitting holds l.mutex, and StopBatchSubmitting needs that mutex to cancel killCtx, so an unbounded receipt wait (txmgr.send-timeout defaults to 0) wedged graceful shutdown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: make the verify-receipt retry delay cancellation-aware The between-attempts backoff in espressoVerifyTransactionWorker was a plain time.Sleep, so a worker mid-backoff stalled StopBatchSubmitting's wg.Wait() for the full configured retry delay. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: dial --espresso.l1-url for light-client reads, drop rollup-l1-url The Espresso L1 URL was parsed and validated but never dialed: light-client calls always went through the batcher's L1 client, so pointing the flag at a different RPC was silently ignored. Now a dedicated client is dialed when the URL differs from --l1-eth-rpc (reusing the batcher's client when it matches), and closed in Stop. --espresso.rollup-l1-url only ever logged a warning, so it is removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: remove dead --espresso.namespace flag, namespace is the L2 chain ID The CLI value was parsed and validated but never read: both runtime sites (transaction publishing and streamer construction) already derive the namespace from RollupConfig.L2ChainID, which the proof side pins too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: reject zeroed LocalSafeL2 as the streamer re-anchor target Mirror the zero-field guards in computeSyncActions and nextBlockRange: a transiently zeroed sync status during clearState would re-anchor the streamer at genesis and re-serve derived history. Retry the clear instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * op-batcher: document the real zero semantics of --espresso.origin-height-l2 The comments promised a RollupConfig.EspressoOriginBatchPos() fallback that never existed; zero means no anchor floor (anchor at the current local-safe head). No default can be derived from the rollup config since espresso_time is a timestamp whose L2 height is not statically known, so handoffs from the fallback batcher must set the flag explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * espresso: drop stale references to --espresso.fallback-auth-lead-time Two comments point at a flag that was never registered. The note in espresso/cli.go claims op-batcher/flags/flags.go registers --espresso.fallback-auth-lead-time, and initEspresso's doc claims the fallback batcher reads a FallbackAuthLeadTime field on BatcherConfig. Neither the flag nor the field exists on any branch; both comments date from the TEE flag import and describe a knob the fallback batcher landed without. * op-batcher: fix the data races in TestBatchSubmitter_ThrottlingEndpoints The test's per-endpoint call counters, the two restarted-server flags and the recorded shutdown error are all written by httptest handler goroutines (or by throttlingLoop, for closeApp) and read directly by the test goroutine, so the package could not run under -race at all: five reports on every run. Make each of them atomic. The slices.Contains check over the counters becomes a small anyUncalled helper, since atomic.Int64 elements can't be compared by value. * espresso: delete dead supporting code (#512) * espresso: delete dead supporting code Remove code with no callers: espresso/ethclient.go (duplicates batcherL1Adapter; FetchEspressoBatcherAddress belongs to the caff node in another repo), opcrypto.Verify and its test, and the AllowEmptyAttestationService escape hatch, which nothing sets, so Check() now requires the attestation service URL unconditionally. Also drop a doc reference to DebouncingHandler, which does not exist. * espresso: gofmt cli.go after removing the config field * espresso: deduplicate the generated contract bindings (#513) espresso: replace hand-copied bindings with generated op-service/bindings (#513) * Delete espresso/bindings, an abigen copy that had drifted from the contracts. * Add op-service/bindings: ABI-only bindings for BatchAuthenticator and SystemConfig, one package per contract, generated from the contracts-bedrock forge artifacts by gen.sh and kept current by the check-bindings CI job. * Add CI job to detect BatchAuthenticator + SystemConfig bindings drifts Closes #504. * espresso: remove the txmgr seam, keep upstream's signer factory (#520) * shrink txmgr seam, delete SignerFactoryFromConfig and test file * Revert deletion SignerFactoryFromConfig and test file SignerFactoryFromConfig had no callers because NewConfig had been moved onto the fork's ChainSignerFactoryFromConfig to populate Config.ChainSigner. Not only don't we want to delete upstream functions, we also want to remove the modification of the upstream NewConfig in a later commit. * op-service/txmgr: go back to upstream's SignerFactoryFromConfig NewConfig was switched onto the fork's ChainSignerFactoryFromConfig so that Config.ChainSigner could be populated for SimpleTxManager.Sign. Both of those are gone now, and txmgr only ever uses SignTransaction, which upstream's SignerFactory already provides. Switching back makes op-service/txmgr free of Espresso entirely, and gives SignerFactoryFromConfig its caller back - it only looked like a redundant copy of ChainSignerFactoryFromConfig because txmgr had been moved off it. ChainSignerFactoryFromConfig stays: the batcher needs Sign over an arbitrary hash for the Espresso transaction envelope, which a SignerFn cannot provide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * op-batcher: guard the Espresso ChainSigner against signing-config drift initChainSigner derives its own signer from cfg.TxMgrConfig, mirroring the HD-path resolution in txmgr.NewConfig. Nothing checked that the two agreed. If they ever diverged, Espresso batches would be signed by a key that is not the registered batcher, the streamer would drop all of them, and L1 submission would carry on. Check that both derivations resolve to the same address and fail startup if not. Move the call from initTxManager to initEspresso, next to initKeyPair, so initTxManager carries no Espresso logic and both signing identities are set up in one place. Also note on ChainSignerFactoryFromConfig that upstream's SignerFactoryFromConfig is not redundant with it: SignerFactory yields a SignerFn, which cannot sign an arbitrary hash. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Maximilian Langenfeld <15726643+ezdac@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * op-service: make the RPC pre-flight check proxy-aware * op-service: resolve the proxy for ws/wss URLs in the pre-flight check Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * op-service: pin the non-local availability test to no proxy Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * op-service: address review on the proxy-aware pre-flight check * op-batcher: use one BatchAuthenticator reader, bound and code-checked once at startup (#523) op-batcher: use one BatchAuthenticator reader (#523) Replace the scattered BatchAuthenticator calls with a single batchAuthenticatorReader, bound once at startup and shared by registerBatcher, resolveTEEVerifierAddress and the publish gate. - Read the fallback batcher from the SystemConfig the BatchAuthenticator itself names, cached after the first read (closes #503). - Bound every read by NetworkTimeout. Rely on the bindings' ErrNoCode for a contract that is not deployed yet; registerBatcher keeps an explicit CodeAt before sending. - Reject a zero SystemConfig or TEE verifier address. - Refuse to start an Espresso batcher with no BatchAuthenticator. - Throttle the gate's skip and error warnings, and reset the wrong-key warning when the batcher's mode goes inactive. - Test the reader, both gates (results, call counts, warnings, throttling) and the callers' error paths. * Add enclave tools / docker building for TEE image to celo repository (#527) * publish op-batcher enclave images from this repo * op-batcher: redact credential flags and URL secrets in enclave argument log * op-batcher: proxy --throttle.additional-endpoints through socat in enclave entrypoint * op-batcher: redact URLs in enclave entrypoint processing logs * op-batcher: reject https for socat-proxied internal URLs in enclave entrypoint * op-service/signer: honor proxy environment variables in TLS signer client * only on dispatch + pin enclaver to prod v0.5.0 * harden enclave arg delivery and CI image tagging --------- Co-authored-by: Jean Gal <jgatbkk@gmail.com> * op-batcher: expose enclave Prometheus metrics via enclaver ingress (#536) * publish op-batcher enclave images from this repo * op-batcher: redact credential flags and URL secrets in enclave argument log * op-batcher: proxy --throttle.additional-endpoints through socat in enclave entrypoint * op-batcher: redact URLs in enclave entrypoint processing logs * op-batcher: reject https for socat-proxied internal URLs in enclave entrypoint * op-service/signer: honor proxy environment variables in TLS signer client * only on dispatch + pin enclaver to prod v0.5.0 * op-batcher: expose enclave Prometheus metrics via enclaver ingress Add a :7300 ingress port to the enclaver manifest and launch op-batcher with metrics enabled on 0.0.0.0:7300 so the enclaver ingress can bridge Prometheus metrics out of the Nitro enclave to the parent. The parent-side bridge (tcp<->vsock) lives in EspressoSystems/tee-image-builder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * op-batcher: force enclave metrics onto the fixed 7300 ingress Review on #536: the previous "skip if caller set --metrics.enabled" guard let a caller-supplied --metrics.port/addr bind somewhere the enclaver ingress (fixed at 7300) doesn't bridge, so scrapes failed silently. Drop the guard and append enabled/addr/port last so they win via urfave/cli's last-value-wins, guaranteeing the batcher always serves on 0.0.0.0:7300. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * trim comment --------- Co-authored-by: Keyao Shen <shenkeyao@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * add reviewer requested dial tests --------- Co-authored-by: Artemii Gerasimovich <artemii@espressosys.com> Co-authored-by: OpenCode <noreply@opencode.ai> Co-authored-by: Luke Iannucci <luke.iannucci@outlook.com> Co-authored-by: Philippe Camacho <philippe@espressosys.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Paul Lange <palango@gmx.de> Co-authored-by: Paul Lange <palango@users.noreply.github.com> Co-authored-by: Jean Gal <45081726+jjeangal@users.noreply.github.com> Co-authored-by: Maximilian Langenfeld <15726643+ezdac@users.noreply.github.com> Co-authored-by: Jean Gal <jgatbkk@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ccef597df0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| # Re-populate the arguments passed through the environment | ||
| if [ -n "$ENCLAVE_BATCHER_ARGS" ]; then | ||
| eval set -- "$ENCLAVE_BATCHER_ARGS" |
There was a problem hiding this comment.
Parse enclave arguments without shell evaluation
When ENCLAVE_BATCHER_ARGS contains shell metacharacters—whether from templated deployment configuration or another external source—eval executes them inside the attested enclave instead of treating them as batcher arguments. This permits command substitution, redirection, or background processes before op-batcher starts, undermining the guarantee that the measured image is running only the configured batcher; pass arguments through the existing NUL-delimited channel or another non-evaluating array encoding.
Useful? React with 👍 / 👎.
* fix: Add network timeout ctx to Espresso client * fix: address Espresso client timeout review feedback * op-batcher: fix Espresso timeout wiring and add regression coverage Wrap the SDK client during initialization and preserve nil when disabled. Cover SDK deadlines, caller cancellation, adapter contexts, and streamer stop/start cleanup. Simplify client interfaces, naming, and comments. * fix(op-batcher): apply Espresso timeouts at call sites Preserve caller cancellation and bound SDK requests, streamer setup, and Peek calls. Add regression coverage for timeout recovery and streamer lifecycle cleanup. * fix(op-batcher): bound Espresso transaction signing by network timeout --------- Co-authored-by: Jean Gal <jgatbkk@gmail.com>
The publish gate decides before every batch transaction whether this batcher may post, but its outcome was only visible in throttled logs. With the Espresso and fallback batchers in separate pods, a chain where neither is active went unnoticed. Closes #553. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…538) The streamer, the transaction submitter and the resolved TEE verifier address are per-start state, but only the streamer was dropped on stop, so the submitter and verifier address outlived the run that created them. Bundle the three into an espressoSession that is built in full, published to l.espresso only once a start succeeds, and dropped as a whole on stop. A stale or half-built session is now unrepresentable rather than handled, and a failed start leaves nothing to roll back. Also drops the uncalled BatcherService.EspressoStreamer accessor. Closes #502. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a7cc8950c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| echo "=== Enclave Environment Debug Info ===" | ||
| echo "PATH: $PATH" | ||
| echo "Working directory: $(pwd)" | ||
| echo "Proxy: ${http_proxy:-not set}" |
There was a problem hiding this comment.
Redact credentials before logging the proxy URL
When http_proxy contains standard URL userinfo such as http://user:password@proxy:10000, this startup message writes those credentials verbatim to the enclave logs; line 67 exposes the same value again through HTTPS_PROXY. Since the script already redacts credentials from other logged URLs, apply equivalent redaction here (or log only the proxy host and port).
Useful? React with 👍 / 👎.
Based on #448
Pulls in the Espresso/TEE batcher .
op-batcher/enclave/attestation.goand modifies the driver to add an Espresso path. Bulk of the changes is inespresso_-prefixed files.This is #447, re-hosted from an in-repo branch (now properly stacked).